mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 17:30:12 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
811a763ef5
@ -474,9 +474,10 @@ public final class NavigateToChatControllerParams {
|
|||||||
public let changeColors: Bool
|
public let changeColors: Bool
|
||||||
public let setupController: (ChatController) -> Void
|
public let setupController: (ChatController) -> Void
|
||||||
public let completion: (ChatController) -> Void
|
public let completion: (ChatController) -> Void
|
||||||
|
public let chatListCompletion: ((ChatListController) -> Void)?
|
||||||
public let pushController: ((ChatController, Bool, @escaping () -> Void) -> Void)?
|
public let pushController: ((ChatController, Bool, @escaping () -> Void) -> Void)?
|
||||||
|
|
||||||
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: Location, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: ChatControllerActivateInput? = nil, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, useBackAnimation: Bool = false, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, pushController: ((ChatController, Bool, @escaping () -> Void) -> Void)? = nil, completion: @escaping (ChatController) -> Void = { _ in }) {
|
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: Location, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, botAppStart: ChatControllerInitialBotAppStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: ChatControllerActivateInput? = nil, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, useBackAnimation: Bool = false, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [ChatNavigationStackItem] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, pushController: ((ChatController, Bool, @escaping () -> Void) -> Void)? = nil, completion: @escaping (ChatController) -> Void = { _ in }, chatListCompletion: @escaping (ChatListController) -> Void = { _ in }) {
|
||||||
self.navigationController = navigationController
|
self.navigationController = navigationController
|
||||||
self.chatController = chatController
|
self.chatController = chatController
|
||||||
self.chatLocationContextHolder = chatLocationContextHolder
|
self.chatLocationContextHolder = chatLocationContextHolder
|
||||||
@ -506,6 +507,7 @@ public final class NavigateToChatControllerParams {
|
|||||||
self.setupController = setupController
|
self.setupController = setupController
|
||||||
self.pushController = pushController
|
self.pushController = pushController
|
||||||
self.completion = completion
|
self.completion = completion
|
||||||
|
self.chatListCompletion = chatListCompletion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -913,6 +913,7 @@ public protocol ChatController: ViewController {
|
|||||||
func activateSearch(domain: ChatSearchDomain, query: String)
|
func activateSearch(domain: ChatSearchDomain, query: String)
|
||||||
func beginClearHistory(type: InteractiveHistoryClearingType)
|
func beginClearHistory(type: InteractiveHistoryClearingType)
|
||||||
|
|
||||||
|
func performScrollToTop() -> Bool
|
||||||
func transferScrollingVelocity(_ velocity: CGFloat)
|
func transferScrollingVelocity(_ velocity: CGFloat)
|
||||||
func updateIsScrollingLockedAtTop(isScrollingLockedAtTop: Bool)
|
func updateIsScrollingLockedAtTop(isScrollingLockedAtTop: Bool)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ public let savedMessagesIcon = generateTintedImage(image: UIImage(bundleImageNam
|
|||||||
public let repostStoryIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepostStoryIcon"), color: .white)
|
public let repostStoryIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepostStoryIcon"), color: .white)
|
||||||
private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed()
|
private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed()
|
||||||
private let repliesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepliesMessagesIcon"), color: .white)
|
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 {
|
public func avatarPlaceholderFont(size: CGFloat) -> UIFont {
|
||||||
return Font.with(size: size, design: .round, weight: .bold)
|
return Font.with(size: size, design: .round, weight: .bold)
|
||||||
@ -660,7 +661,7 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
icon = .repliesIcon
|
icon = .repliesIcon
|
||||||
case .anonymousSavedMessagesIcon:
|
case .anonymousSavedMessagesIcon:
|
||||||
representation = nil
|
representation = nil
|
||||||
icon = .repliesIcon
|
icon = .anonymousSavedMessagesIcon
|
||||||
case let .archivedChatsIcon(hiddenByDefault):
|
case let .archivedChatsIcon(hiddenByDefault):
|
||||||
representation = nil
|
representation = nil
|
||||||
icon = .archivedChatsIcon(hiddenByDefault: hiddenByDefault)
|
icon = .archivedChatsIcon(hiddenByDefault: hiddenByDefault)
|
||||||
@ -897,8 +898,8 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
context.scaleBy(x: factor, y: -factor)
|
context.scaleBy(x: factor, y: -factor)
|
||||||
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
|
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
|
||||||
|
|
||||||
if let repliesIcon = repliesIcon {
|
if let anonymousSavedMessagesIcon = anonymousSavedMessagesIcon {
|
||||||
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))
|
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 {
|
} 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)
|
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
|
}, peerSelected: { [weak self] peer, chatPeer, threadId, _ in
|
||||||
interaction.dismissInput()
|
interaction.dismissInput()
|
||||||
interaction.openPeer(peer, chatPeer, threadId, false)
|
interaction.openPeer(peer, chatPeer, threadId, false)
|
||||||
|
switch location {
|
||||||
|
case .chatList, .forum:
|
||||||
let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone()
|
let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone()
|
||||||
|
case .savedMessagesChats:
|
||||||
|
break
|
||||||
|
}
|
||||||
self?.listNode.clearHighlightAnimated(true)
|
self?.listNode.clearHighlightAnimated(true)
|
||||||
}, disabledPeerSelected: { _, _ in
|
}, disabledPeerSelected: { _, _ in
|
||||||
}, togglePeerSelected: { _, _ 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
|
let transition = chatListSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries, context: context, presentationData: presentationData, filter: peersFilter, peerSelected: { peer, threadId in
|
||||||
interaction.openPeer(peer, nil, threadId, true)
|
interaction.openPeer(peer, nil, threadId, true)
|
||||||
if threadId == nil {
|
if threadId == nil {
|
||||||
|
switch location {
|
||||||
|
case .chatList, .forum:
|
||||||
let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone()
|
let _ = context.engine.peers.addRecentlySearchedPeer(peerId: peer.id).startStandalone()
|
||||||
|
case .savedMessagesChats:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self?.recentListNode.clearHighlightAnimated(true)
|
self?.recentListNode.clearHighlightAnimated(true)
|
||||||
}, disabledPeerSelected: { peer, threadId in
|
}, disabledPeerSelected: { peer, threadId in
|
||||||
|
|||||||
@ -44,7 +44,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
case iPadPro
|
case iPadPro
|
||||||
case iPadPro3rdGen
|
case iPadPro3rdGen
|
||||||
case iPadMini6thGen
|
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()
|
public static let performance = Performance()
|
||||||
|
|
||||||
@ -111,14 +111,24 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return
|
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 {
|
public var type: DeviceType {
|
||||||
switch self {
|
switch self {
|
||||||
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen:
|
case .iPad, .iPad102Inch, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen:
|
||||||
return .tablet
|
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
|
return .tablet
|
||||||
default:
|
default:
|
||||||
return .phone
|
return .phone
|
||||||
@ -175,7 +185,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return CGSize(width: 1024.0, height: 1366.0)
|
return CGSize(width: 1024.0, height: 1366.0)
|
||||||
case .iPadMini6thGen:
|
case .iPadMini6thGen:
|
||||||
return CGSize(width: 744.0, height: 1133.0)
|
return CGSize(width: 744.0, height: 1133.0)
|
||||||
case let .unknown(screenSize, _, _):
|
case let .unknown(screenSize, _, _, _):
|
||||||
return screenSize
|
return screenSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,12 +204,8 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return 53.0 + UIScreenPixel
|
return 53.0 + UIScreenPixel
|
||||||
case .iPhone14Pro, .iPhone14ProMax:
|
case .iPhone14Pro, .iPhone14ProMax:
|
||||||
return 55.0
|
return 55.0
|
||||||
case let .unknown(_, _, onScreenNavigationHeight):
|
case let .unknown(_, _, _, screenCornerRadius):
|
||||||
if let _ = onScreenNavigationHeight {
|
return screenCornerRadius
|
||||||
return 39.0
|
|
||||||
} else {
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
@ -230,7 +236,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case let .unknown(_, _, onScreenNavigationHeight):
|
case let .unknown(_, _, onScreenNavigationHeight, _):
|
||||||
return onScreenNavigationHeight
|
return onScreenNavigationHeight
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
@ -260,7 +266,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
|
|||||||
return 44.0
|
return 44.0
|
||||||
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
case .iPadPro11Inch, .iPadPro3rdGen, .iPadMini, .iPadMini6thGen:
|
||||||
return 24.0
|
return 24.0
|
||||||
case let .unknown(_, statusBarHeight, _):
|
case let .unknown(_, statusBarHeight, _, _):
|
||||||
return statusBarHeight
|
return statusBarHeight
|
||||||
default:
|
default:
|
||||||
return 20.0
|
return 20.0
|
||||||
|
|||||||
@ -415,7 +415,7 @@ final class InstantPageSlideshowNode: ASDisplayNode, InstantPageNode {
|
|||||||
super.layout()
|
super.layout()
|
||||||
|
|
||||||
self.pagerNode.frame = self.bounds
|
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.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))
|
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() {
|
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"] {
|
if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_callui_v2"] {
|
||||||
useV2 = false
|
useV2 = false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,6 +42,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
private var didInitializeIsReady: Bool = false
|
private var didInitializeIsReady: Bool = false
|
||||||
|
|
||||||
private var callStartTimestamp: Double?
|
private var callStartTimestamp: Double?
|
||||||
|
private var smoothSignalQuality: Double?
|
||||||
|
private var smoothSignalQualityTarget: Double?
|
||||||
|
|
||||||
private var callState: PresentationCallState?
|
private var callState: PresentationCallState?
|
||||||
var isMuted: Bool = false
|
var isMuted: Bool = false
|
||||||
@ -77,6 +79,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
private var panGestureState: PanGestureState?
|
private var panGestureState: PanGestureState?
|
||||||
private var notifyDismissedInteractivelyOnPanGestureApply: Bool = false
|
private var notifyDismissedInteractivelyOnPanGestureApply: Bool = false
|
||||||
|
|
||||||
|
private var signalQualityTimer: Foundation.Timer?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
sharedContext: SharedAccountContext,
|
sharedContext: SharedAccountContext,
|
||||||
account: Account,
|
account: Account,
|
||||||
@ -190,6 +194,22 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
})
|
})
|
||||||
|
|
||||||
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
|
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 {
|
deinit {
|
||||||
@ -197,6 +217,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
self.isMicrophoneMutedDisposable?.dispose()
|
self.isMicrophoneMutedDisposable?.dispose()
|
||||||
self.audioLevelDisposable?.dispose()
|
self.audioLevelDisposable?.dispose()
|
||||||
self.audioOutputCheckTimer?.invalidate()
|
self.audioOutputCheckTimer?.invalidate()
|
||||||
|
self.signalQualityTimer?.invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) {
|
func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) {
|
||||||
@ -211,8 +232,24 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
mappedOutput = .internalSpeaker
|
mappedOutput = .internalSpeaker
|
||||||
case .speaker:
|
case .speaker:
|
||||||
mappedOutput = .speaker
|
mappedOutput = .speaker
|
||||||
case .headphones, .port:
|
case .headphones:
|
||||||
mappedOutput = .speaker
|
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 {
|
} else {
|
||||||
mappedOutput = .internalSpeaker
|
mappedOutput = .internalSpeaker
|
||||||
@ -342,10 +379,16 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
case .connecting:
|
case .connecting:
|
||||||
mappedLifecycleState = .connecting
|
mappedLifecycleState = .connecting
|
||||||
case let .active(startTime, signalQuality, keyData):
|
case let .active(startTime, signalQuality, keyData):
|
||||||
self.callStartTimestamp = startTime
|
var signalQuality = signalQuality.flatMap(Int.init)
|
||||||
|
self.smoothSignalQualityTarget = Double(signalQuality ?? 4)
|
||||||
|
|
||||||
var signalQuality = signalQuality
|
if let smoothSignalQuality = self.smoothSignalQuality {
|
||||||
|
signalQuality = Int(round(smoothSignalQuality))
|
||||||
|
} else {
|
||||||
signalQuality = 4
|
signalQuality = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
self.callStartTimestamp = startTime
|
||||||
|
|
||||||
let _ = keyData
|
let _ = keyData
|
||||||
mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState(
|
mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState(
|
||||||
@ -354,6 +397,9 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
emojiKey: self.resolvedEmojiKey(data: keyData)
|
emojiKey: self.resolvedEmojiKey(data: keyData)
|
||||||
))
|
))
|
||||||
case let .reconnecting(startTime, _, keyData):
|
case let .reconnecting(startTime, _, keyData):
|
||||||
|
self.smoothSignalQuality = nil
|
||||||
|
self.smoothSignalQualityTarget = nil
|
||||||
|
|
||||||
if self.callStartTimestamp != nil {
|
if self.callStartTimestamp != nil {
|
||||||
mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState(
|
mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState(
|
||||||
startTime: startTime + kCFAbsoluteTimeIntervalSince1970,
|
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.name = peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
|
||||||
callScreenState.shortName = peer.compactDisplayTitle
|
callScreenState.shortName = peer.compactDisplayTitle
|
||||||
|
|
||||||
if self.currentPeer?.smallProfileImage != peer.smallProfileImage {
|
if (self.currentPeer?.smallProfileImage != peer.smallProfileImage) || self.callScreenState?.avatarImage == nil {
|
||||||
self.peerAvatarDisposable?.dispose()
|
self.peerAvatarDisposable?.dispose()
|
||||||
|
|
||||||
if let smallProfileImage = peer.largeProfileImage, let peerReference = PeerReference(peer._asPeer()) {
|
let size = CGSize(width: 128.0, height: 128.0)
|
||||||
if let thumbnailImage = smallProfileImage.immediateThumbnailData.flatMap(decodeTinyThumbnail).flatMap(UIImage.init(data:)), let cgImage = thumbnailImage.cgImage {
|
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) {
|
||||||
callScreenState.avatarImage = generateImage(CGSize(width: 128.0, height: 128.0), contextGenerator: { size, context in
|
self.peerAvatarDisposable = (signal
|
||||||
context.draw(cgImage, in: CGRect(origin: CGPoint(), size: size))
|
|> deliverOnMainQueue).startStrict(next: { [weak self] imageVersions in
|
||||||
}, 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
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if var callScreenState = self.callScreenState {
|
let image = imageVersions?.0
|
||||||
|
if let image {
|
||||||
callScreenState.avatarImage = image
|
callScreenState.avatarImage = image
|
||||||
self.callScreenState = callScreenState
|
self.callScreenState = callScreenState
|
||||||
self.update(transition: .immediate)
|
self.update(transition: .immediate)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.peerAvatarDisposable?.dispose()
|
let image = generateImage(size, rotatedContext: { size, context in
|
||||||
self.peerAvatarDisposable = nil
|
|
||||||
|
|
||||||
callScreenState.avatarImage = generateImage(CGSize(width: 512, height: 512), scale: 1.0, rotatedContext: { size, context in
|
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
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
|
self.currentPeer = peer
|
||||||
@ -893,6 +919,19 @@ private final class AdaptedCallVideoSource: VideoSource {
|
|||||||
let width = i420Buffer.width
|
let width = i420Buffer.width
|
||||||
let height = i420Buffer.height
|
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 _ = width
|
||||||
let _ = height
|
let _ = height
|
||||||
return
|
return
|
||||||
|
|||||||
@ -157,7 +157,7 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class CallStatusBarNodeImpl: CallStatusBarNode {
|
public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||||
public enum Content {
|
public enum Content: Equatable {
|
||||||
case call(SharedAccountContext, Account, PresentationCall)
|
case call(SharedAccountContext, Account, PresentationCall)
|
||||||
case groupCall(SharedAccountContext, Account, PresentationGroupCall)
|
case groupCall(SharedAccountContext, Account, PresentationGroupCall)
|
||||||
|
|
||||||
@ -167,6 +167,23 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
return sharedContext
|
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
|
private let backgroundNode: CallStatusBarBackgroundNode
|
||||||
@ -236,12 +253,14 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func update(content: Content) {
|
public func update(content: Content) {
|
||||||
|
if self.currentContent != content {
|
||||||
self.currentContent = content
|
self.currentContent = content
|
||||||
self.backgroundNode.animationsEnabled = content.sharedContext.energyUsageSettings.fullTranslucency
|
self.backgroundNode.animationsEnabled = content.sharedContext.energyUsageSettings.fullTranslucency
|
||||||
if self.isCurrentlyInHierarchy {
|
if self.isCurrentlyInHierarchy {
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override func update(size: CGSize) {
|
public override func update(size: CGSize) {
|
||||||
self.currentSize = size
|
self.currentSize = size
|
||||||
|
|||||||
@ -93,6 +93,7 @@ enum AccountStateMutationOperation {
|
|||||||
case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32)
|
case ReadSecretOutbox(peerId: PeerId, maxTimestamp: Int32, actionTimestamp: Int32)
|
||||||
case AddPeerInputActivity(chatPeerId: PeerActivitySpace, peerId: PeerId?, activity: PeerInputActivity?)
|
case AddPeerInputActivity(chatPeerId: PeerActivitySpace, peerId: PeerId?, activity: PeerInputActivity?)
|
||||||
case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation)
|
case UpdatePinnedItemIds(PeerGroupId, AccountStateUpdatePinnedItemIdsOperation)
|
||||||
|
case UpdatePinnedSavedItemIds(AccountStateUpdatePinnedItemIdsOperation)
|
||||||
case UpdatePinnedTopic(peerId: PeerId, threadId: Int64, isPinned: Bool)
|
case UpdatePinnedTopic(peerId: PeerId, threadId: Int64, isPinned: Bool)
|
||||||
case UpdatePinnedTopicOrder(peerId: PeerId, threadIds: [Int64])
|
case UpdatePinnedTopicOrder(peerId: PeerId, threadIds: [Int64])
|
||||||
case ReadMessageContents(peerIdsAndMessageIds: (PeerId?, [Int32]), date: Int32?)
|
case ReadMessageContents(peerIdsAndMessageIds: (PeerId?, [Int32]), date: Int32?)
|
||||||
@ -571,6 +572,10 @@ struct AccountMutableState {
|
|||||||
self.addOperation(.UpdatePinnedItemIds(groupId, operation))
|
self.addOperation(.UpdatePinnedItemIds(groupId, operation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutating func addUpdatePinnedSavedItemIds(operation: AccountStateUpdatePinnedItemIdsOperation) {
|
||||||
|
self.addOperation(.UpdatePinnedSavedItemIds(operation))
|
||||||
|
}
|
||||||
|
|
||||||
mutating func addUpdatePinnedTopic(peerId: PeerId, threadId: Int64, isPinned: Bool) {
|
mutating func addUpdatePinnedTopic(peerId: PeerId, threadId: Int64, isPinned: Bool) {
|
||||||
self.addOperation(.UpdatePinnedTopic(peerId: peerId, threadId: threadId, isPinned: isPinned))
|
self.addOperation(.UpdatePinnedTopic(peerId: peerId, threadId: threadId, isPinned: isPinned))
|
||||||
}
|
}
|
||||||
@ -665,7 +670,7 @@ struct AccountMutableState {
|
|||||||
|
|
||||||
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
mutating func addOperation(_ operation: AccountStateMutationOperation) {
|
||||||
switch operation {
|
switch operation {
|
||||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization:
|
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization:
|
||||||
break
|
break
|
||||||
case let .AddMessages(messages, location):
|
case let .AddMessages(messages, location):
|
||||||
for message in messages {
|
for message in messages {
|
||||||
|
|||||||
@ -839,6 +839,8 @@ func revalidateMediaResourceReference(accountPeerId: PeerId, postbox: Postbox, n
|
|||||||
}
|
}
|
||||||
if let updatedResource = findUpdatedMediaResource(media: media, previousMedia: nil, resource: resource) {
|
if let updatedResource = findUpdatedMediaResource(media: media, previousMedia: nil, resource: resource) {
|
||||||
return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil))
|
return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil))
|
||||||
|
} else if let alternativeMedia = item.alternativeMedia, let updatedResource = findUpdatedMediaResource(media: alternativeMedia, previousMedia: nil, resource: resource) {
|
||||||
|
return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil))
|
||||||
} else {
|
} else {
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1490,6 +1490,27 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
|||||||
} else {
|
} else {
|
||||||
updatedState.addUpdatePinnedItemIds(groupId: groupId, operation: .sync)
|
updatedState.addUpdatePinnedItemIds(groupId: groupId, operation: .sync)
|
||||||
}
|
}
|
||||||
|
case let .updateSavedDialogPinned(flags, peer):
|
||||||
|
if case let .dialogPeer(peer) = peer {
|
||||||
|
if (flags & (1 << 0)) != 0 {
|
||||||
|
updatedState.addUpdatePinnedSavedItemIds(operation: .pin(.peer(peer.peerId)))
|
||||||
|
} else {
|
||||||
|
updatedState.addUpdatePinnedSavedItemIds(operation: .unpin(.peer(peer.peerId)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .updatePinnedSavedDialogs(_, order):
|
||||||
|
if let order = order {
|
||||||
|
updatedState.addUpdatePinnedSavedItemIds(operation: .reorder(order.compactMap {
|
||||||
|
switch $0 {
|
||||||
|
case let .dialogPeer(peer):
|
||||||
|
return .peer(peer.peerId)
|
||||||
|
case .dialogPeerFolder:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
updatedState.addUpdatePinnedSavedItemIds(operation: .sync)
|
||||||
|
}
|
||||||
case let .updateChannelPinnedTopic(flags, channelId, topicId):
|
case let .updateChannelPinnedTopic(flags, channelId, topicId):
|
||||||
let isPinned = (flags & (1 << 0)) != 0
|
let isPinned = (flags & (1 << 0)) != 0
|
||||||
updatedState.addUpdatePinnedTopic(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topicId), isPinned: isPinned)
|
updatedState.addUpdatePinnedTopic(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)), threadId: Int64(topicId), isPinned: isPinned)
|
||||||
@ -3231,7 +3252,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
|
|||||||
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
var currentAddScheduledMessages: OptimizeAddMessagesState?
|
||||||
for operation in operations {
|
for operation in operations {
|
||||||
switch operation {
|
switch operation {
|
||||||
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper:
|
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper:
|
||||||
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
|
||||||
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
|
||||||
}
|
}
|
||||||
@ -4228,6 +4249,44 @@ func replayFinalState(
|
|||||||
case .sync:
|
case .sync:
|
||||||
addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId)
|
addSynchronizePinnedChatsOperation(transaction: transaction, groupId: groupId)
|
||||||
}
|
}
|
||||||
|
case let .UpdatePinnedSavedItemIds(pinnedOperation):
|
||||||
|
switch pinnedOperation {
|
||||||
|
case let .pin(itemId):
|
||||||
|
switch itemId {
|
||||||
|
case let .peer(peerId):
|
||||||
|
var currentItemIds = transaction.getPeerPinnedThreads(peerId: accountPeerId)
|
||||||
|
if !currentItemIds.contains(peerId.toInt64()) {
|
||||||
|
currentItemIds.insert(peerId.toInt64(), at: 0)
|
||||||
|
transaction.setPeerPinnedThreads(peerId: accountPeerId, threadIds: currentItemIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .unpin(itemId):
|
||||||
|
switch itemId {
|
||||||
|
case let .peer(peerId):
|
||||||
|
var currentItemIds = transaction.getPeerPinnedThreads(peerId: accountPeerId)
|
||||||
|
if let index = currentItemIds.firstIndex(of: peerId.toInt64()) {
|
||||||
|
currentItemIds.remove(at: index)
|
||||||
|
transaction.setPeerPinnedThreads(peerId: accountPeerId, threadIds: currentItemIds)
|
||||||
|
} else {
|
||||||
|
addSynchronizePinnedSavedChatsOperation(transaction: transaction, accountPeerId: accountPeerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case let .reorder(itemIds):
|
||||||
|
let itemIds = itemIds.compactMap({
|
||||||
|
switch $0 {
|
||||||
|
case let .peer(peerId):
|
||||||
|
return peerId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let currentItemIds = transaction.getPeerPinnedThreads(peerId: accountPeerId)
|
||||||
|
if Set(itemIds) == Set(currentItemIds.map(PeerId.init)) {
|
||||||
|
transaction.setPeerPinnedThreads(peerId: accountPeerId, threadIds: itemIds.map { $0.toInt64() })
|
||||||
|
} else {
|
||||||
|
addSynchronizePinnedSavedChatsOperation(transaction: transaction, accountPeerId: accountPeerId)
|
||||||
|
}
|
||||||
|
case .sync:
|
||||||
|
addSynchronizePinnedSavedChatsOperation(transaction: transaction, accountPeerId: accountPeerId)
|
||||||
|
}
|
||||||
case let .UpdatePinnedTopic(peerId, threadId, isPinned):
|
case let .UpdatePinnedTopic(peerId, threadId, isPinned):
|
||||||
var currentThreadIds = transaction.getPeerPinnedThreads(peerId: peerId)
|
var currentThreadIds = transaction.getPeerPinnedThreads(peerId: peerId)
|
||||||
if isPinned {
|
if isPinned {
|
||||||
|
|||||||
@ -69,7 +69,8 @@ final class AccountTaskManager {
|
|||||||
tasks.add(managedSynchronizeGroupMessageStats(network: self.stateManager.network, postbox: self.stateManager.postbox, stateManager: self.stateManager).start())
|
tasks.add(managedSynchronizeGroupMessageStats(network: self.stateManager.network, postbox: self.stateManager.postbox, stateManager: self.stateManager).start())
|
||||||
|
|
||||||
tasks.add(managedGlobalNotificationSettings(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
|
tasks.add(managedGlobalNotificationSettings(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
|
||||||
tasks.add(managedSynchronizePinnedChatsOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId, stateManager: self.stateManager).start())
|
tasks.add(managedSynchronizePinnedChatsOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId, stateManager: self.stateManager, tag: OperationLogTags.SynchronizePinnedChats).start())
|
||||||
|
tasks.add(managedSynchronizePinnedChatsOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId, stateManager: self.stateManager, tag: OperationLogTags.SynchronizePinnedSavedChats).start())
|
||||||
tasks.add(managedSynchronizeGroupedPeersOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start())
|
tasks.add(managedSynchronizeGroupedPeersOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start())
|
||||||
tasks.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager, namespace: .stickers).start())
|
tasks.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager, namespace: .stickers).start())
|
||||||
tasks.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager, namespace: .masks).start())
|
tasks.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager, namespace: .masks).start())
|
||||||
|
|||||||
@ -1860,6 +1860,23 @@ public final class AccountViewTracker {
|
|||||||
return transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self)
|
return transaction.getMessageHistoryThreadInfo(peerId: peerId, threadId: threadId)?.data.get(MessageHistoryThreadData.self)
|
||||||
}
|
}
|
||||||
|> mapToSignal { threadInfo -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in
|
|> mapToSignal { threadInfo -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> in
|
||||||
|
if peerId == account.peerId {
|
||||||
|
return account.postbox.aroundMessageHistoryViewForLocation(
|
||||||
|
chatLocation,
|
||||||
|
anchor: .upperBound,
|
||||||
|
ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange,
|
||||||
|
count: count,
|
||||||
|
fixedCombinedReadStates: .peer([peerId: CombinedPeerReadState(states: [
|
||||||
|
(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: Int32.max - 1, maxOutgoingReadId: Int32.max - 1, maxKnownId: Int32.max - 1, count: 0, markedUnread: false))
|
||||||
|
])]),
|
||||||
|
topTaggedMessageIdNamespaces: [],
|
||||||
|
tagMask: tagMask,
|
||||||
|
appendMessagesFromTheSameGroup: false,
|
||||||
|
namespaces: .not(Namespaces.Message.allScheduled),
|
||||||
|
orderStatistics: orderStatistics,
|
||||||
|
additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
if let threadInfo = threadInfo {
|
if let threadInfo = threadInfo {
|
||||||
let anchor: HistoryViewInputAnchor
|
let anchor: HistoryViewInputAnchor
|
||||||
if threadInfo.maxIncomingReadId <= 1 {
|
if threadInfo.maxIncomingReadId <= 1 {
|
||||||
@ -1885,6 +1902,7 @@ public final class AccountViewTracker {
|
|||||||
additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)
|
additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, customUnreadMessageId: nil, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
return account.postbox.aroundMessageOfInterestHistoryViewForChatLocation(chatLocation, ignoreMessagesInTimestampRange: ignoreMessagesInTimestampRange, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, appendMessagesFromTheSameGroup: appendMessagesFromTheSameGroup, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, customUnreadMessageId: nil, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import SwiftSignalKit
|
|||||||
import TelegramApi
|
import TelegramApi
|
||||||
import MtProtoKit
|
import MtProtoKit
|
||||||
|
|
||||||
|
|
||||||
private final class ManagedSynchronizePinnedChatsOperationsHelper {
|
private final class ManagedSynchronizePinnedChatsOperationsHelper {
|
||||||
var operationDisposables: [Int32: Disposable] = [:]
|
var operationDisposables: [Int32: Disposable] = [:]
|
||||||
|
|
||||||
@ -49,10 +48,10 @@ private final class ManagedSynchronizePinnedChatsOperationsHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func withTakenOperation(postbox: Postbox, peerId: PeerId, tagLocalIndex: Int32, _ f: @escaping (Transaction, PeerMergedOperationLogEntry?) -> Signal<Void, NoError>) -> Signal<Void, NoError> {
|
private func withTakenOperation(postbox: Postbox, tag: PeerOperationLogTag, peerId: PeerId, tagLocalIndex: Int32, _ f: @escaping (Transaction, PeerMergedOperationLogEntry?) -> Signal<Void, NoError>) -> Signal<Void, NoError> {
|
||||||
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
var result: PeerMergedOperationLogEntry?
|
var result: PeerMergedOperationLogEntry?
|
||||||
transaction.operationLogUpdateEntry(peerId: peerId, tag: OperationLogTags.SynchronizePinnedChats, tagLocalIndex: tagLocalIndex, { entry in
|
transaction.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, { entry in
|
||||||
if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizePinnedChatsOperation {
|
if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizePinnedChatsOperation {
|
||||||
result = entry.mergedEntry!
|
result = entry.mergedEntry!
|
||||||
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
||||||
@ -65,11 +64,11 @@ private func withTakenOperation(postbox: Postbox, peerId: PeerId, tagLocalIndex:
|
|||||||
} |> switchToLatest
|
} |> switchToLatest
|
||||||
}
|
}
|
||||||
|
|
||||||
func managedSynchronizePinnedChatsOperations(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
func managedSynchronizePinnedChatsOperations(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, tag: PeerOperationLogTag) -> Signal<Void, NoError> {
|
||||||
return Signal { _ in
|
return Signal { _ in
|
||||||
let helper = Atomic<ManagedSynchronizePinnedChatsOperationsHelper>(value: ManagedSynchronizePinnedChatsOperationsHelper())
|
let helper = Atomic<ManagedSynchronizePinnedChatsOperationsHelper>(value: ManagedSynchronizePinnedChatsOperationsHelper())
|
||||||
|
|
||||||
let disposable = postbox.mergedOperationLogView(tag: OperationLogTags.SynchronizePinnedChats, limit: 10).start(next: { view in
|
let disposable = postbox.mergedOperationLogView(tag: tag, limit: 10).start(next: { view in
|
||||||
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in
|
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in
|
||||||
return helper.update(view.entries)
|
return helper.update(view.entries)
|
||||||
}
|
}
|
||||||
@ -79,10 +78,14 @@ func managedSynchronizePinnedChatsOperations(postbox: Postbox, network: Network,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (entry, disposable) in beginOperations {
|
for (entry, disposable) in beginOperations {
|
||||||
let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal<Void, NoError> in
|
let signal = withTakenOperation(postbox: postbox, tag: tag, peerId: entry.peerId, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal<Void, NoError> in
|
||||||
if let entry = entry {
|
if let entry = entry {
|
||||||
if let operation = entry.contents as? SynchronizePinnedChatsOperation {
|
if let operation = entry.contents as? SynchronizePinnedChatsOperation {
|
||||||
|
if tag == OperationLogTags.SynchronizePinnedChats {
|
||||||
return synchronizePinnedChats(transaction: transaction, postbox: postbox, network: network, accountPeerId: accountPeerId, stateManager: stateManager, groupId: PeerGroupId(rawValue: Int32(entry.peerId.id._internalGetInt64Value())), operation: operation)
|
return synchronizePinnedChats(transaction: transaction, postbox: postbox, network: network, accountPeerId: accountPeerId, stateManager: stateManager, groupId: PeerGroupId(rawValue: Int32(entry.peerId.id._internalGetInt64Value())), operation: operation)
|
||||||
|
} else if tag == OperationLogTags.SynchronizePinnedSavedChats {
|
||||||
|
return synchronizePinnedSavedChats(transaction: transaction, postbox: postbox, network: network, accountPeerId: accountPeerId, stateManager: stateManager, operation: operation)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
}
|
}
|
||||||
@ -90,7 +93,7 @@ func managedSynchronizePinnedChatsOperations(postbox: Postbox, network: Network,
|
|||||||
return .complete()
|
return .complete()
|
||||||
})
|
})
|
||||||
|> then(postbox.transaction { transaction -> Void in
|
|> then(postbox.transaction { transaction -> Void in
|
||||||
let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: OperationLogTags.SynchronizePinnedChats, tagLocalIndex: entry.tagLocalIndex)
|
let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex)
|
||||||
})
|
})
|
||||||
|
|
||||||
disposable.set((signal |> delay(2.0, queue: Queue.concurrentDefaultQueue())).start())
|
disposable.set((signal |> delay(2.0, queue: Queue.concurrentDefaultQueue())).start())
|
||||||
@ -279,3 +282,65 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox,
|
|||||||
|> switchToLatest
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func synchronizePinnedSavedChats(transaction: Transaction, postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, operation: SynchronizePinnedChatsOperation) -> Signal<Void, NoError> {
|
||||||
|
return network.request(Api.functions.messages.getPinnedSavedDialogs())
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.messages.SavedDialogs?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { dialogs -> Signal<Void, NoError> in
|
||||||
|
guard let dialogs = dialogs else {
|
||||||
|
return .never()
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = dialogs
|
||||||
|
|
||||||
|
/*return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
|
var storeMessages: [StoreMessage] = []
|
||||||
|
var remoteItemIds: [PeerId] = []
|
||||||
|
|
||||||
|
let parsedPeers: AccumulatedPeers
|
||||||
|
|
||||||
|
switch dialogs {
|
||||||
|
case .savedDialogs(let dialogs, let messages, let chats, let users), .savedDialogs(_, let dialogs, let messages, let chats, let users):
|
||||||
|
parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||||
|
|
||||||
|
loop: for dialog in dialogs {
|
||||||
|
switch dialog {
|
||||||
|
case let .savedDialog(_, peer, _):
|
||||||
|
remoteItemIds.append(peer.peerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for message in messages {
|
||||||
|
var peerIsForum = false
|
||||||
|
if let peerId = message.peerId, let peer = parsedPeers.get(peerId), peer.isForum {
|
||||||
|
peerIsForum = true
|
||||||
|
}
|
||||||
|
if let storeMessage = StoreMessage(apiMessage: message, accountPeerId: accountPeerId, peerIsForum: peerIsForum) {
|
||||||
|
storeMessages.append(storeMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .savedDialogsNotModified:
|
||||||
|
parsedPeers = AccumulatedPeers(transaction: transaction, chats: [], users: [])
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultingItemIds: [PeerId] = remoteItemIds
|
||||||
|
|
||||||
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
||||||
|
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
||||||
|
|
||||||
|
transaction.setPeerPinnedThreads(peerId: accountPeerId, threadIds: resultingItemIds.map { $0.toInt64() })
|
||||||
|
|
||||||
|
let _ = transaction.addMessages(storeMessages, location: .UpperHistoryBlock)
|
||||||
|
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
|> switchToLatest
|
||||||
|
}
|
||||||
|
|> switchToLatest*/
|
||||||
|
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -198,6 +198,7 @@ public struct OperationLogTags {
|
|||||||
public static let SynchronizeAutosaveItems = PeerOperationLogTag(value: 23)
|
public static let SynchronizeAutosaveItems = PeerOperationLogTag(value: 23)
|
||||||
public static let SynchronizeViewStories = PeerOperationLogTag(value: 24)
|
public static let SynchronizeViewStories = PeerOperationLogTag(value: 24)
|
||||||
public static let SynchronizePeerStories = PeerOperationLogTag(value: 25)
|
public static let SynchronizePeerStories = PeerOperationLogTag(value: 25)
|
||||||
|
public static let SynchronizePinnedSavedChats = PeerOperationLogTag(value: 26)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
public struct LegacyPeerSummaryCounterTags: OptionSet, Sequence, Hashable {
|
||||||
|
|||||||
@ -61,3 +61,21 @@ public func addSynchronizePinnedChatsOperation(transaction: Transaction, groupId
|
|||||||
}
|
}
|
||||||
transaction.operationLogAddEntry(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(Int64(rawId))), tag: OperationLogTags.SynchronizePinnedChats, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: operationContents)
|
transaction.operationLogAddEntry(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(Int64(rawId))), tag: OperationLogTags.SynchronizePinnedChats, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: operationContents)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func addSynchronizePinnedSavedChatsOperation(transaction: Transaction, accountPeerId: PeerId) {
|
||||||
|
var previousItemIds = transaction.getPeerPinnedThreads(peerId: accountPeerId).map { PinnedItemId.peer(PeerId($0)) }
|
||||||
|
var updateLocalIndex: Int32?
|
||||||
|
|
||||||
|
transaction.operationLogEnumerateEntries(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)), tag: OperationLogTags.SynchronizePinnedSavedChats, { entry in
|
||||||
|
updateLocalIndex = entry.tagLocalIndex
|
||||||
|
if let contents = entry.contents as? SynchronizePinnedChatsOperation {
|
||||||
|
previousItemIds = contents.previousItemIds
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
let operationContents = SynchronizePinnedChatsOperation(previousItemIds: previousItemIds)
|
||||||
|
if let updateLocalIndex = updateLocalIndex {
|
||||||
|
let _ = transaction.operationLogRemoveEntry(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)), tag: OperationLogTags.SynchronizePinnedSavedChats, tagLocalIndex: updateLocalIndex)
|
||||||
|
}
|
||||||
|
transaction.operationLogAddEntry(peerId: PeerId(namespace: PeerId.Namespace._internalFromInt32Value(0), id: PeerId.Id._internalFromInt64Value(0)), tag: OperationLogTags.SynchronizePinnedSavedChats, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: operationContents)
|
||||||
|
}
|
||||||
|
|||||||
@ -296,6 +296,18 @@ func locallyRenderedMessage(message: StoreMessage, peers: AccumulatedPeers, asso
|
|||||||
public extension Message {
|
public extension Message {
|
||||||
func effectivelyIncoming(_ accountPeerId: PeerId) -> Bool {
|
func effectivelyIncoming(_ accountPeerId: PeerId) -> Bool {
|
||||||
if self.id.peerId == accountPeerId {
|
if self.id.peerId == accountPeerId {
|
||||||
|
if let sourceAuthorInfo = self.sourceAuthorInfo {
|
||||||
|
if sourceAuthorInfo.originalOutgoing {
|
||||||
|
return false
|
||||||
|
} else if let originalAuthor = sourceAuthorInfo.originalAuthor, originalAuthor == accountPeerId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if let forwardInfo = self.forwardInfo {
|
||||||
|
if let author = forwardInfo.author, author.id == accountPeerId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.forwardInfo != nil {
|
if self.forwardInfo != nil {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -419,6 +419,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen",
|
"//submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen",
|
||||||
"//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen",
|
"//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen",
|
||||||
"//submodules/TelegramUI/Components/Settings/WallpaperGridScreen",
|
"//submodules/TelegramUI/Components/Settings/WallpaperGridScreen",
|
||||||
|
"//submodules/TelegramUI/Components/Chat/ChatMessageNotificationItem",
|
||||||
] + select({
|
] + select({
|
||||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||||
"//build-system:ios_sim_arm64": [],
|
"//build-system:ios_sim_arm64": [],
|
||||||
|
|||||||
@ -15,7 +15,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
case end
|
case end
|
||||||
}
|
}
|
||||||
|
|
||||||
case speaker(isActive: Bool)
|
case speaker(audioOutput: PrivateCallScreen.State.AudioOutput)
|
||||||
case flipCamera
|
case flipCamera
|
||||||
case video(isActive: Bool)
|
case video(isActive: Bool)
|
||||||
case microphone(isMuted: Bool)
|
case microphone(isMuted: Bool)
|
||||||
@ -215,10 +215,37 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
let isActive: Bool
|
let isActive: Bool
|
||||||
var isDestructive: Bool = false
|
var isDestructive: Bool = false
|
||||||
switch button.content {
|
switch button.content {
|
||||||
case let .speaker(isActiveValue):
|
case let .speaker(audioOutput):
|
||||||
|
switch audioOutput {
|
||||||
|
case .internalSpeaker, .speaker:
|
||||||
title = "speaker"
|
title = "speaker"
|
||||||
|
default:
|
||||||
|
title = "audio"
|
||||||
|
}
|
||||||
|
|
||||||
|
switch audioOutput {
|
||||||
|
case .internalSpeaker:
|
||||||
image = UIImage(bundleImageName: "Call/Speaker")
|
image = UIImage(bundleImageName: "Call/Speaker")
|
||||||
isActive = isActiveValue
|
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:
|
case .flipCamera:
|
||||||
title = "flip"
|
title = "flip"
|
||||||
image = UIImage(bundleImageName: "Call/Flip")
|
image = UIImage(bundleImageName: "Call/Flip")
|
||||||
|
|||||||
@ -60,6 +60,11 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
public enum AudioOutput: Equatable {
|
public enum AudioOutput: Equatable {
|
||||||
case internalSpeaker
|
case internalSpeaker
|
||||||
case speaker
|
case speaker
|
||||||
|
case headphones
|
||||||
|
case airpods
|
||||||
|
case airpodsPro
|
||||||
|
case airpodsMax
|
||||||
|
case bluetooth
|
||||||
}
|
}
|
||||||
|
|
||||||
public var lifecycleState: LifecycleState
|
public var lifecycleState: LifecycleState
|
||||||
@ -354,6 +359,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
if !self.isUpdating {
|
if !self.isUpdating {
|
||||||
let wereControlsHidden = self.areControlsHidden
|
let wereControlsHidden = self.areControlsHidden
|
||||||
self.areControlsHidden = true
|
self.areControlsHidden = true
|
||||||
|
self.displayEmojiTooltip = false
|
||||||
self.update(transition: .immediate)
|
self.update(transition: .immediate)
|
||||||
|
|
||||||
if !wereControlsHidden {
|
if !wereControlsHidden {
|
||||||
@ -417,6 +423,24 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
if self.activeRemoteVideoSource != nil || self.activeLocalVideoSource != nil {
|
if self.activeRemoteVideoSource != nil || self.activeLocalVideoSource != nil {
|
||||||
self.areControlsHidden = !self.areControlsHidden
|
self.areControlsHidden = !self.areControlsHidden
|
||||||
update = true
|
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 {
|
if update {
|
||||||
@ -515,7 +539,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
if let previousParams = self.params, case .active = params.state.lifecycleState {
|
if let previousParams = self.params, case .active = params.state.lifecycleState {
|
||||||
switch previousParams.state.lifecycleState {
|
switch previousParams.state.lifecycleState {
|
||||||
case .requesting, .ringing, .connecting, .reconnecting:
|
case .requesting, .ringing, .connecting, .reconnecting:
|
||||||
if self.hideEmojiTooltipTimer == nil {
|
if self.hideEmojiTooltipTimer == nil && !self.areControlsHidden {
|
||||||
self.displayEmojiTooltip = true
|
self.displayEmojiTooltip = true
|
||||||
|
|
||||||
self.hideEmojiTooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false, block: { [weak self] _ in
|
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 {
|
if !self.areControlsHidden {
|
||||||
self.areControlsHidden = true
|
self.areControlsHidden = true
|
||||||
|
self.displayEmojiTooltip = false
|
||||||
self.update(transition: .spring(duration: 0.4))
|
self.update(transition: .spring(duration: 0.4))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -704,7 +729,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
self.flipCameraAction?()
|
self.flipCameraAction?()
|
||||||
}), at: 0)
|
}), at: 0)
|
||||||
} else {
|
} 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 {
|
guard let self else {
|
||||||
return
|
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.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))
|
transition.setBounds(layer: self.blobLayer, bounds: CGRect(origin: CGPoint(), size: blobFrame.size))
|
||||||
|
|
||||||
|
let displayAudioLevelBlob: Bool
|
||||||
let titleString: String
|
let titleString: String
|
||||||
switch params.state.lifecycleState {
|
switch params.state.lifecycleState {
|
||||||
case let .terminated(terminatedState):
|
case let .terminated(terminatedState):
|
||||||
|
displayAudioLevelBlob = false
|
||||||
|
|
||||||
self.titleView.contentMode = .center
|
self.titleView.contentMode = .center
|
||||||
|
|
||||||
switch terminatedState.reason {
|
switch terminatedState.reason {
|
||||||
@ -1174,6 +1202,14 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
case .missed:
|
case .missed:
|
||||||
titleString = "Call 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.setScale(layer: self.blobLayer, scale: 0.3)
|
||||||
genericAlphaTransition.setAlpha(layer: self.blobLayer, alpha: 0.0)
|
genericAlphaTransition.setAlpha(layer: self.blobLayer, alpha: 0.0)
|
||||||
self.canAnimateAudioLevel = false
|
self.canAnimateAudioLevel = false
|
||||||
@ -1181,11 +1217,12 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
self.currentAvatarAudioScale = 1.0
|
self.currentAvatarAudioScale = 1.0
|
||||||
transition.setScale(layer: self.avatarTransformLayer, scale: 1.0)
|
transition.setScale(layer: self.avatarTransformLayer, scale: 1.0)
|
||||||
transition.setScale(layer: self.blobTransformLayer, scale: 1.0)
|
transition.setScale(layer: self.blobTransformLayer, scale: 1.0)
|
||||||
default:
|
} else {
|
||||||
self.titleView.contentMode = .scaleToFill
|
|
||||||
titleString = params.state.name
|
|
||||||
genericAlphaTransition.setAlpha(layer: self.blobLayer, alpha: (expandedEmojiKeyOverlapsAvatar && !havePrimaryVideo) ? 0.0 : 1.0)
|
genericAlphaTransition.setAlpha(layer: self.blobLayer, alpha: (expandedEmojiKeyOverlapsAvatar && !havePrimaryVideo) ? 0.0 : 1.0)
|
||||||
transition.setScale(layer: self.blobLayer, scale: expandedEmojiKeyOverlapsAvatar ? 0.001 : 1.0)
|
transition.setScale(layer: self.blobLayer, scale: expandedEmojiKeyOverlapsAvatar ? 0.001 : 1.0)
|
||||||
|
if !havePrimaryVideo {
|
||||||
|
self.canAnimateAudioLevel = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleSize = self.titleView.update(
|
let titleSize = self.titleView.update(
|
||||||
|
|||||||
@ -183,7 +183,17 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing
|
let messageTheme = incoming ? presentationData.theme.theme.chat.message.incoming : presentationData.theme.theme.chat.message.outgoing
|
||||||
let author = message.author
|
|
||||||
|
var author = message.effectiveAuthor
|
||||||
|
|
||||||
|
if let forwardInfo = message.forwardInfo {
|
||||||
|
if let peer = forwardInfo.author {
|
||||||
|
author = peer
|
||||||
|
} else if let authorSignature = forwardInfo.authorSignature {
|
||||||
|
author = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let nameColors = author?.nameColor.flatMap { context.peerNameColors.get($0, dark: presentationData.theme.theme.overallDarkAppearance) }
|
let nameColors = author?.nameColor.flatMap { context.peerNameColors.get($0, dark: presentationData.theme.theme.overallDarkAppearance) }
|
||||||
|
|
||||||
let mainColor: UIColor
|
let mainColor: UIColor
|
||||||
|
|||||||
@ -1645,8 +1645,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
if case .admin = authorRank {
|
if case .admin = authorRank {
|
||||||
} else if case .owner = authorRank {
|
} else if case .owner = authorRank {
|
||||||
} else if authorRank == nil {
|
} else if authorRank == nil {
|
||||||
|
if case let .replyThread(replyThreadMessage) = item.chatLocation, replyThreadMessage.peerId == item.context.account.peerId {
|
||||||
|
} else {
|
||||||
enableAutoRank = true
|
enableAutoRank = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if enableAutoRank {
|
if enableAutoRank {
|
||||||
if let topicAuthorId = item.associatedData.topicAuthorId, topicAuthorId == message.author?.id {
|
if let topicAuthorId = item.associatedData.topicAuthorId, topicAuthorId == message.author?.id {
|
||||||
authorRank = .custom(item.presentationData.strings.Chat_Message_TopicAuthorBadge)
|
authorRank = .custom(item.presentationData.strings.Chat_Message_TopicAuthorBadge)
|
||||||
|
|||||||
@ -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
|
import MultiAnimationRenderer
|
||||||
|
|
||||||
public final class ChatMessageNotificationItem: NotificationItem {
|
public final class ChatMessageNotificationItem: NotificationItem {
|
||||||
let context: AccountContext
|
public let context: AccountContext
|
||||||
let strings: PresentationStrings
|
public let strings: PresentationStrings
|
||||||
let dateTimeFormat: PresentationDateTimeFormat
|
public let dateTimeFormat: PresentationDateTimeFormat
|
||||||
let nameDisplayOrder: PresentationPersonNameOrder
|
public let nameDisplayOrder: PresentationPersonNameOrder
|
||||||
let messages: [Message]
|
public let messages: [Message]
|
||||||
let threadData: MessageHistoryThreadData?
|
public let threadData: MessageHistoryThreadData?
|
||||||
let tapAction: () -> Bool
|
public let tapAction: () -> Bool
|
||||||
let expandAction: (@escaping () -> (ASDisplayNode?, () -> Void)) -> Void
|
public let expandAction: (@escaping () -> (ASDisplayNode?, () -> Void)) -> Void
|
||||||
|
|
||||||
public var groupingKey: AnyHashable? {
|
public var groupingKey: AnyHashable? {
|
||||||
return messages.first?.id.peerId
|
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
|
self.validLayout = width
|
||||||
let compact = self.compact ?? false
|
let compact = self.compact ?? false
|
||||||
|
|
||||||
@ -13,7 +13,9 @@ public protocol NotificationItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class NotificationItemNode: ASDisplayNode {
|
public class NotificationItemNode: ASDisplayNode {
|
||||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
return 32.0
|
return 32.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var acceptsTouches: Bool = false
|
||||||
}
|
}
|
||||||
@ -203,7 +203,15 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
|||||||
var dashSecondaryColor: UIColor?
|
var dashSecondaryColor: UIColor?
|
||||||
var dashTertiaryColor: UIColor?
|
var dashTertiaryColor: UIColor?
|
||||||
|
|
||||||
let author = arguments.message?.effectiveAuthor
|
var author = arguments.message?.effectiveAuthor
|
||||||
|
|
||||||
|
if let forwardInfo = arguments.message?.forwardInfo {
|
||||||
|
if let peer = forwardInfo.author {
|
||||||
|
author = peer
|
||||||
|
} else if let authorSignature = forwardInfo.authorSignature {
|
||||||
|
author = TelegramUser(id: PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(Int64(authorSignature.persistentHashValue % 32))), accessHash: nil, firstName: authorSignature, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil, nameColor: nil, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let colors = author?.nameColor.flatMap { arguments.context.peerNameColors.get($0, dark: arguments.presentationData.theme.theme.overallDarkAppearance) }
|
let colors = author?.nameColor.flatMap { arguments.context.peerNameColors.get($0, dark: arguments.presentationData.theme.theme.overallDarkAppearance) }
|
||||||
authorNameColor = colors?.main
|
authorNameColor = colors?.main
|
||||||
|
|||||||
@ -293,6 +293,8 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func scrollToTop() -> Bool {
|
public func scrollToTop() -> Bool {
|
||||||
|
self.chatListNode.scrollToPosition(.top(adjustForTempInset: false))
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -87,7 +87,7 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func scrollToTop() -> Bool {
|
public func scrollToTop() -> Bool {
|
||||||
return false
|
return self.chatController.performScrollToTop()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func hitTestResultForScrolling() -> UIView? {
|
public func hitTestResultForScrolling() -> UIView? {
|
||||||
|
|||||||
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)?
|
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)
|
setPresentationCall?(call)
|
||||||
}, navigateToChat: { accountId, peerId, messageId in
|
}, navigateToChat: { accountId, peerId, messageId in
|
||||||
self.openChatWhenReady(accountId: accountId, peerId: peerId, threadId: nil, messageId: messageId, storyId: nil)
|
self.openChatWhenReady(accountId: accountId, peerId: peerId, threadId: nil, messageId: messageId, storyId: nil)
|
||||||
@ -1202,6 +1202,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
self.mainWindow.topLevelOverlayControllers = [context.sharedApplicationContext.overlayMediaController, context.notificationController]
|
self.mainWindow.topLevelOverlayControllers = [context.sharedApplicationContext.overlayMediaController, context.notificationController]
|
||||||
|
(context.context.sharedContext as? SharedAccountContextImpl)?.notificationController = context.notificationController
|
||||||
var authorizeNotifications = true
|
var authorizeNotifications = true
|
||||||
if #available(iOS 10.0, *) {
|
if #available(iOS 10.0, *) {
|
||||||
authorizeNotifications = false
|
authorizeNotifications = false
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import TelegramCallsUI
|
|||||||
import AuthorizationUI
|
import AuthorizationUI
|
||||||
import ChatListUI
|
import ChatListUI
|
||||||
import StoryContainerScreen
|
import StoryContainerScreen
|
||||||
|
import ChatMessageNotificationItem
|
||||||
|
|
||||||
final class UnauthorizedApplicationContext {
|
final class UnauthorizedApplicationContext {
|
||||||
let sharedContext: SharedAccountContextImpl
|
let sharedContext: SharedAccountContextImpl
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import ChatControllerInteraction
|
|||||||
import OverlayStatusController
|
import OverlayStatusController
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
|
import UndoUI
|
||||||
|
|
||||||
extension ChatControllerImpl {
|
extension ChatControllerImpl {
|
||||||
func navigateToMessage(
|
func navigateToMessage(
|
||||||
@ -149,8 +150,13 @@ extension ChatControllerImpl {
|
|||||||
}
|
}
|
||||||
if let navigationController = self.effectiveNavigationController {
|
if let navigationController = self.effectiveNavigationController {
|
||||||
var chatLocation: NavigateToChatControllerParams.Location = .peer(peer)
|
var chatLocation: NavigateToChatControllerParams.Location = .peer(peer)
|
||||||
if case let .channel(channel) = peer, channel.flags.contains(.isForum), let message = message, let threadId = message.threadId {
|
var displayMessageNotFoundToast = false
|
||||||
|
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
||||||
|
if let message = message, let threadId = message.threadId {
|
||||||
chatLocation = .replyThread(ChatReplyThreadMessage(peerId: peer.id, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
|
chatLocation = .replyThread(ChatReplyThreadMessage(peerId: peer.id, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
|
||||||
|
} else {
|
||||||
|
displayMessageNotFoundToast = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var quote: ChatControllerSubject.MessageHighlight.Quote?
|
var quote: ChatControllerSubject.MessageHighlight.Quote?
|
||||||
@ -158,7 +164,15 @@ extension ChatControllerImpl {
|
|||||||
quote = params.quote.flatMap { quote in ChatControllerSubject.MessageHighlight.Quote(string: quote.string, offset: quote.offset) }
|
quote = params.quote.flatMap { quote in ChatControllerSubject.MessageHighlight.Quote(string: quote.string, offset: quote.offset) }
|
||||||
}
|
}
|
||||||
|
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: quote), timecode: nil), keepStack: .always))
|
let context = self.context
|
||||||
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: chatLocation, subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: quote), timecode: nil), keepStack: .always, chatListCompletion: { chatListController in
|
||||||
|
if displayMessageNotFoundToast {
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||||
|
chatListController.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Conversation_MessageDoesntExist, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in
|
||||||
|
return true
|
||||||
|
}), in: .current)
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
completion?()
|
completion?()
|
||||||
@ -393,7 +407,7 @@ extension ChatControllerImpl {
|
|||||||
quote = params.quote.flatMap { quote in ChatControllerSubject.MessageHighlight.Quote(string: quote.string, offset: quote.offset) }
|
quote = params.quote.flatMap { quote in ChatControllerSubject.MessageHighlight.Quote(string: quote.string, offset: quote.offset) }
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: messageLocation.messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: quote), timecode: nil) }))
|
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), subject: messageLocation.messageId.flatMap { .message(id: .id($0), highlight: ChatControllerSubject.MessageHighlight(quote: quote), timecode: nil) }, keepStack: .always))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
completion?()
|
completion?()
|
||||||
|
|||||||
@ -3019,13 +3019,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation, replyThreadMessage.effectiveMessageId == message.id {
|
if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation, replyThreadMessage.effectiveMessageId == message.id {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
if case let .replyThread(replyThreadMessage) = strongSelf.chatLocation, replyThreadMessage.peerId == strongSelf.context.account.peerId {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
if case .peer = strongSelf.chatLocation, let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
if case .peer = strongSelf.chatLocation, let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||||
if message.threadId == nil {
|
if message.threadId == nil {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if canReplyInChat(strongSelf.presentationInterfaceState) {
|
if canReplyInChat(strongSelf.presentationInterfaceState, accountPeerId: strongSelf.context.account.peerId) {
|
||||||
return .reply
|
return .reply
|
||||||
} else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
|
} else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
|
||||||
}
|
}
|
||||||
@ -17499,7 +17502,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
if options.contains(.deleteLocally) {
|
if options.contains(.deleteLocally) {
|
||||||
var localOptionText = self.presentationData.strings.Conversation_DeleteMessagesForMe
|
var localOptionText = self.presentationData.strings.Conversation_DeleteMessagesForMe
|
||||||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
if self.chatLocation.peerId == self.context.account.peerId {
|
||||||
|
//TODO:localize
|
||||||
|
localOptionText = "Remove from Saved Messages"
|
||||||
|
} else if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||||
localOptionText = messageIds.count > 1 ? self.presentationData.strings.ScheduledMessages_DeleteMany : self.presentationData.strings.ScheduledMessages_Delete
|
localOptionText = messageIds.count > 1 ? self.presentationData.strings.ScheduledMessages_DeleteMany : self.presentationData.strings.ScheduledMessages_Delete
|
||||||
} else {
|
} else {
|
||||||
if options.contains(.unsendPersonal) {
|
if options.contains(.unsendPersonal) {
|
||||||
@ -18035,7 +18041,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if canReplyInChat(self.presentationInterfaceState) {
|
if canReplyInChat(self.presentationInterfaceState, accountPeerId: self.context.account.peerId) {
|
||||||
inputShortcuts.append(
|
inputShortcuts.append(
|
||||||
KeyShortcut(
|
KeyShortcut(
|
||||||
input: UIKeyCommand.inputUpArrow,
|
input: UIKeyCommand.inputUpArrow,
|
||||||
@ -19061,6 +19067,17 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
public func transferScrollingVelocity(_ velocity: CGFloat) {
|
public func transferScrollingVelocity(_ velocity: CGFloat) {
|
||||||
self.chatDisplayNode.historyNode.transferVelocity(velocity)
|
self.chatDisplayNode.historyNode.transferVelocity(velocity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func performScrollToTop() -> Bool {
|
||||||
|
let offset = self.chatDisplayNode.historyNode.visibleContentOffset()
|
||||||
|
switch offset {
|
||||||
|
case let .known(value) where value <= CGFloat.ulpOfOne:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
self.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChatContextControllerContentSourceImpl: ContextControllerContentSource {
|
final class ChatContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||||
|
|||||||
@ -249,7 +249,7 @@ private func canViewReadStats(message: Message, participantCount: Int?, isMessag
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState) -> Bool {
|
func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, accountPeerId: PeerId) -> Bool {
|
||||||
guard let peer = chatPresentationInterfaceState.renderedPeer?.peer else {
|
guard let peer = chatPresentationInterfaceState.renderedPeer?.peer else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -272,6 +272,9 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation, replyThreadMessage.peerId == accountPeerId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||||
if let threadData = chatPresentationInterfaceState.threadData {
|
if let threadData = chatPresentationInterfaceState.threadData {
|
||||||
@ -611,7 +614,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var canReply = canReplyInChat(chatPresentationInterfaceState)
|
var canReply = canReplyInChat(chatPresentationInterfaceState, accountPeerId: context.account.peerId)
|
||||||
var canPin = false
|
var canPin = false
|
||||||
let canSelect = !isAction
|
let canSelect = !isAction
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ public func makeTempContext(
|
|||||||
encryptionParameters: encryptionParameters,
|
encryptionParameters: encryptionParameters,
|
||||||
accountManager: accountManager,
|
accountManager: accountManager,
|
||||||
appLockContext: appLockContext,
|
appLockContext: appLockContext,
|
||||||
|
notificationController: nil,
|
||||||
applicationBindings: applicationBindings,
|
applicationBindings: applicationBindings,
|
||||||
initialPresentationDataAndSettings: initialPresentationDataAndSettings,
|
initialPresentationDataAndSettings: initialPresentationDataAndSettings,
|
||||||
networkArguments: networkArguments,
|
networkArguments: networkArguments,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import MediaEditorScreen
|
|||||||
import ChatControllerInteraction
|
import ChatControllerInteraction
|
||||||
import SavedMessagesScreen
|
import SavedMessagesScreen
|
||||||
import WallpaperGalleryScreen
|
import WallpaperGalleryScreen
|
||||||
|
import ChatMessageNotificationItem
|
||||||
|
|
||||||
public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) {
|
public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParams) {
|
||||||
if case let .peer(peer) = params.chatLocation {
|
if case let .peer(peer) = params.chatLocation {
|
||||||
@ -75,23 +76,23 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
|||||||
let controller = ChatListControllerImpl(context: params.context, location: .forum(peerId: peer.id), controlsHistoryPreload: false, enableDebugActions: false)
|
let controller = ChatListControllerImpl(context: params.context, location: .forum(peerId: peer.id), controlsHistoryPreload: false, enableDebugActions: false)
|
||||||
|
|
||||||
let activateMessageSearch = params.activateMessageSearch
|
let activateMessageSearch = params.activateMessageSearch
|
||||||
|
let chatListCompletion = params.chatListCompletion
|
||||||
params.navigationController.pushViewController(controller, completion: { [weak controller] in
|
params.navigationController.pushViewController(controller, completion: { [weak controller] in
|
||||||
guard let controller, let activateMessageSearch else {
|
guard let controller else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if let activateMessageSearch {
|
||||||
controller.activateSearch(query: activateMessageSearch.1)
|
controller.activateSearch(query: activateMessageSearch.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let chatListCompletion {
|
||||||
|
chatListCompletion(controller)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if case let .peer(peer) = params.chatLocation, peer.id == params.context.account.peerId {
|
|
||||||
let savedMessagesScreen = SavedMessagesScreen(context: params.context)
|
|
||||||
params.navigationController.pushViewController(savedMessagesScreen, completion: {
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}*/
|
|
||||||
|
|
||||||
var found = false
|
var found = false
|
||||||
var isFirst = true
|
var isFirst = true
|
||||||
if params.useExisting {
|
if params.useExisting {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import TelegramCore
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ChatMessageNotificationItem
|
||||||
|
|
||||||
public final class NotificationContainerController: ViewController {
|
public final class NotificationContainerController: ViewController {
|
||||||
private var controllerNode: NotificationContainerControllerNode {
|
private var controllerNode: NotificationContainerControllerNode {
|
||||||
@ -97,6 +98,10 @@ public final class NotificationContainerController: ViewController {
|
|||||||
self.controllerNode.enqueue(item)
|
self.controllerNode.enqueue(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func setBlocking(_ item: NotificationItem?) {
|
||||||
|
self.controllerNode.setBlocking(item)
|
||||||
|
}
|
||||||
|
|
||||||
public func removeItems(_ f: (NotificationItem) -> Bool) {
|
public func removeItems(_ f: (NotificationItem) -> Bool) {
|
||||||
self.controllerNode.removeItems(f)
|
self.controllerNode.removeItems(f)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import Display
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import ChatMessageNotificationItem
|
||||||
|
|
||||||
private final class NotificationContainerControllerNodeView: UITracingLayerView {
|
private final class NotificationContainerControllerNodeView: UITracingLayerView {
|
||||||
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
|
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
|
||||||
@ -16,6 +17,7 @@ private final class NotificationContainerControllerNodeView: UITracingLayerView
|
|||||||
final class NotificationContainerControllerNode: ASDisplayNode {
|
final class NotificationContainerControllerNode: ASDisplayNode {
|
||||||
private var validLayout: ContainerViewLayout?
|
private var validLayout: ContainerViewLayout?
|
||||||
private var topItemAndNode: (NotificationItem, NotificationItemContainerNode)?
|
private var topItemAndNode: (NotificationItem, NotificationItemContainerNode)?
|
||||||
|
private var blockingItemAndNode: (NotificationItem, NotificationItemContainerNode)?
|
||||||
|
|
||||||
var displayingItemsUpdated: ((Bool) -> Void)?
|
var displayingItemsUpdated: ((Bool) -> Void)?
|
||||||
|
|
||||||
@ -49,6 +51,9 @@ final class NotificationContainerControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
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 {
|
if let (_, topItemNode) = self.topItemAndNode {
|
||||||
return topItemNode.hitTest(point, with: event)
|
return topItemNode.hitTest(point, with: event)
|
||||||
}
|
}
|
||||||
@ -77,6 +82,10 @@ final class NotificationContainerControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func enqueue(_ item: NotificationItem) {
|
func enqueue(_ item: NotificationItem) {
|
||||||
|
if self.blockingItemAndNode != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if let (_, topItemNode) = self.topItemAndNode {
|
if let (_, topItemNode) = self.topItemAndNode {
|
||||||
topItemNode.animateOut(completion: { [weak topItemNode] in
|
topItemNode.animateOut(completion: { [weak topItemNode] in
|
||||||
topItemNode?.removeFromSupernode()
|
topItemNode?.removeFromSupernode()
|
||||||
@ -89,9 +98,8 @@ final class NotificationContainerControllerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let itemNode = item.node(compact: useCompactLayout)
|
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.item = item
|
||||||
containerNode.contentNode = itemNode
|
|
||||||
containerNode.dismissed = { [weak self] item in
|
containerNode.dismissed = { [weak self] item in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let (topItem, topItemNode) = strongSelf.topItemAndNode, topItem.groupingKey != nil && topItem.groupingKey == item.groupingKey {
|
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.topItemAndNode = (item, containerNode)
|
||||||
|
|
||||||
|
if let blockingItemAndNode = self.blockingItemAndNode {
|
||||||
|
self.insertSubnode(containerNode, belowSubnode: blockingItemAndNode.1)
|
||||||
|
} else {
|
||||||
self.addSubnode(containerNode)
|
self.addSubnode(containerNode)
|
||||||
|
}
|
||||||
|
|
||||||
if let validLayout = self.validLayout {
|
if let validLayout = self.validLayout {
|
||||||
containerNode.updateLayout(layout: validLayout, transition: .immediate)
|
containerNode.updateLayout(layout: validLayout, transition: .immediate)
|
||||||
@ -133,6 +146,70 @@ final class NotificationContainerControllerNode: ASDisplayNode {
|
|||||||
self.resetTimeoutTimer()
|
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) {
|
func removeItems(_ f: (NotificationItem) -> Bool) {
|
||||||
if let (topItem, topItemNode) = self.topItemAndNode {
|
if let (topItem, topItemNode) = self.topItemAndNode {
|
||||||
if f(topItem) {
|
if f(topItem) {
|
||||||
|
|||||||
@ -140,7 +140,7 @@ public final class NotificationViewControllerImpl {
|
|||||||
return nil
|
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)
|
presentationDataPromise.set(sharedAccountContext!.presentationData)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import UIKit
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import ChatMessageNotificationItem
|
||||||
|
|
||||||
final class NotificationItemContainerNode: ASDisplayNode {
|
final class NotificationItemContainerNode: ASDisplayNode {
|
||||||
private let backgroundNode: ASImageNode
|
private let backgroundNode: ASImageNode
|
||||||
@ -45,7 +46,9 @@ final class NotificationItemContainerNode: ASDisplayNode {
|
|||||||
|
|
||||||
var cancelledTimeout = false
|
var cancelledTimeout = false
|
||||||
|
|
||||||
init(theme: PresentationTheme) {
|
init(theme: PresentationTheme, contentNode: NotificationItemNode?) {
|
||||||
|
self.contentNode = contentNode
|
||||||
|
|
||||||
self.backgroundNode = ASImageNode()
|
self.backgroundNode = ASImageNode()
|
||||||
self.backgroundNode.displayWithoutProcessing = true
|
self.backgroundNode.displayWithoutProcessing = true
|
||||||
self.backgroundNode.displaysAsynchronously = false
|
self.backgroundNode.displaysAsynchronously = false
|
||||||
@ -54,17 +57,22 @@ final class NotificationItemContainerNode: ASDisplayNode {
|
|||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
|
if let contentNode {
|
||||||
|
self.addSubnode(contentNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
|
if let contentNode = self.contentNode, !contentNode.acceptsTouches {
|
||||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||||
panRecognizer.delaysTouchesBegan = false
|
panRecognizer.delaysTouchesBegan = false
|
||||||
panRecognizer.cancelsTouchesInView = false
|
panRecognizer.cancelsTouchesInView = false
|
||||||
self.view.addGestureRecognizer(panRecognizer)
|
self.view.addGestureRecognizer(panRecognizer)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func animateIn() {
|
func animateIn() {
|
||||||
if let _ = self.validLayout {
|
if let _ = self.validLayout {
|
||||||
@ -113,6 +121,11 @@ final class NotificationItemContainerNode: ASDisplayNode {
|
|||||||
|
|
||||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
if let contentNode = self.contentNode, contentNode.frame.contains(point) {
|
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 self.view
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -50,6 +50,7 @@ import ChatRecentActionsController
|
|||||||
import PeerInfoScreen
|
import PeerInfoScreen
|
||||||
import ChatQrCodeScreen
|
import ChatQrCodeScreen
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
import ChatMessageNotificationItem
|
||||||
|
|
||||||
private final class AccountUserInterfaceInUseContext {
|
private final class AccountUserInterfaceInUseContext {
|
||||||
let subscribers = Bag<(Bool) -> Void>()
|
let subscribers = Bag<(Bool) -> Void>()
|
||||||
@ -87,6 +88,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
public let basePath: String
|
public let basePath: String
|
||||||
public let accountManager: AccountManager<TelegramAccountManagerTypes>
|
public let accountManager: AccountManager<TelegramAccountManagerTypes>
|
||||||
public let appLockContext: AppLockContext
|
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
|
private let navigateToChatImpl: (AccountRecordId, PeerId, MessageId?) -> Void
|
||||||
|
|
||||||
@ -137,6 +147,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
public let hasOngoingCall = ValuePromise<Bool>(false)
|
public let hasOngoingCall = ValuePromise<Bool>(false)
|
||||||
private let callState = Promise<PresentationCallState?>(nil)
|
private let callState = Promise<PresentationCallState?>(nil)
|
||||||
private var awaitingCallConnectionDisposable: Disposable?
|
private var awaitingCallConnectionDisposable: Disposable?
|
||||||
|
private var callPeerDisposable: Disposable?
|
||||||
|
|
||||||
private var groupCallController: VoiceChatController?
|
private var groupCallController: VoiceChatController?
|
||||||
public var currentGroupCallController: ViewController? {
|
public var currentGroupCallController: ViewController? {
|
||||||
@ -216,7 +227,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
|
|
||||||
private let energyUsageAutomaticDisposable = MetaDisposable()
|
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())
|
assert(Queue.mainQueue().isCurrent())
|
||||||
|
|
||||||
precondition(!testHasInstance)
|
precondition(!testHasInstance)
|
||||||
@ -231,6 +242,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
self.navigateToChatImpl = navigateToChat
|
self.navigateToChatImpl = navigateToChat
|
||||||
self.displayUpgradeProgress = displayUpgradeProgress
|
self.displayUpgradeProgress = displayUpgradeProgress
|
||||||
self.appLockContext = appLockContext
|
self.appLockContext = appLockContext
|
||||||
|
self.notificationController = notificationController
|
||||||
self.hasInAppPurchases = hasInAppPurchases
|
self.hasInAppPurchases = hasInAppPurchases
|
||||||
|
|
||||||
self.accountManager.mediaBox.fetchCachedResourceRepresentation = { (resource, representation) -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
self.accountManager.mediaBox.fetchCachedResourceRepresentation = { (resource, representation) -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
||||||
@ -767,18 +779,52 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
self.callController = nil
|
self.callController = nil
|
||||||
self.hasOngoingCall.set(false)
|
self.hasOngoingCall.set(false)
|
||||||
|
|
||||||
|
self.notificationController?.setBlocking(nil)
|
||||||
|
|
||||||
|
self.callPeerDisposable?.dispose()
|
||||||
|
self.callPeerDisposable = nil
|
||||||
|
|
||||||
if let call {
|
if let call {
|
||||||
self.callState.set(call.state
|
self.callState.set(call.state
|
||||||
|> map(Optional.init))
|
|> map(Optional.init))
|
||||||
self.hasOngoingCall.set(true)
|
self.hasOngoingCall.set(true)
|
||||||
setNotificationCall(call)
|
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
|
self.awaitingCallConnectionDisposable = (call.state
|
||||||
|> filter { state in
|
|> filter { state in
|
||||||
switch state.state {
|
switch state.state {
|
||||||
case .ringing:
|
case .ringing:
|
||||||
return false
|
return false
|
||||||
|
case .terminating, .terminated:
|
||||||
|
return false
|
||||||
default:
|
default:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -788,10 +834,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
self.notificationController?.setBlocking(nil)
|
||||||
self.presentControllerWithCurrentCall()
|
self.presentControllerWithCurrentCall()
|
||||||
|
|
||||||
|
self.callPeerDisposable?.dispose()
|
||||||
|
self.callPeerDisposable = nil
|
||||||
})
|
})
|
||||||
} else{
|
|
||||||
self.presentControllerWithCurrentCall()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.callState.set(.single(nil))
|
self.callState.set(.single(nil))
|
||||||
@ -861,6 +909,29 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
let callSignal: Signal<PresentationCall?, NoError> = .single(nil)
|
let callSignal: Signal<PresentationCall?, NoError> = .single(nil)
|
||||||
|> then(
|
|> then(
|
||||||
callManager.currentCallSignal
|
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)
|
let groupCallSignal: Signal<PresentationGroupCall?, NoError> = .single(nil)
|
||||||
|> then(
|
|> then(
|
||||||
@ -1002,6 +1073,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
self.groupCallDisposable?.dispose()
|
self.groupCallDisposable?.dispose()
|
||||||
self.callStateDisposable?.dispose()
|
self.callStateDisposable?.dispose()
|
||||||
self.awaitingCallConnectionDisposable?.dispose()
|
self.awaitingCallConnectionDisposable?.dispose()
|
||||||
|
self.callPeerDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var didPerformAccountSettingsImport = false
|
private var didPerformAccountSettingsImport = false
|
||||||
|
|||||||
@ -25,6 +25,17 @@ final class ContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueueWebrtc
|
|||||||
func isCurrent() -> Bool {
|
func isCurrent() -> Bool {
|
||||||
return self.queue.isCurrent()
|
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 {
|
enum BroadcastPartSubject {
|
||||||
|
|||||||
@ -213,7 +213,7 @@ public struct OngoingCallContextState: Equatable {
|
|||||||
public let remoteBatteryLevel: RemoteBatteryLevel
|
public let remoteBatteryLevel: RemoteBatteryLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc /*, OngoingCallThreadLocalContextQueueWebrtcCustom*/ {
|
private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCallThreadLocalContextQueue, OngoingCallThreadLocalContextQueueWebrtc {
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
|
|
||||||
init(queue: Queue) {
|
init(queue: Queue) {
|
||||||
@ -235,6 +235,17 @@ private final class OngoingCallThreadLocalContextQueueImpl: NSObject, OngoingCal
|
|||||||
func isCurrent() -> Bool {
|
func isCurrent() -> Bool {
|
||||||
return self.queue.isCurrent()
|
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 {
|
private func ongoingNetworkTypeForType(_ type: NetworkType) -> OngoingCallNetworkType {
|
||||||
|
|||||||
@ -98,11 +98,20 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
|||||||
OngoingCallDataSavingAlways
|
OngoingCallDataSavingAlways
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@interface GroupCallDisposable : NSObject
|
||||||
|
|
||||||
|
- (instancetype _Nonnull)initWithBlock:(dispatch_block_t _Nonnull)block;
|
||||||
|
- (void)dispose;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
@protocol OngoingCallThreadLocalContextQueueWebrtc <NSObject>
|
@protocol OngoingCallThreadLocalContextQueueWebrtc <NSObject>
|
||||||
|
|
||||||
- (void)dispatch:(void (^ _Nonnull)())f;
|
- (void)dispatch:(void (^ _Nonnull)())f;
|
||||||
- (bool)isCurrent;
|
- (bool)isCurrent;
|
||||||
|
|
||||||
|
- (GroupCallDisposable * _Nonnull)scheduleBlock:(void (^ _Nonnull)())f after:(double)timeout;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface VoipProxyServerWebrtc : NSObject
|
@interface VoipProxyServerWebrtc : NSObject
|
||||||
@ -133,13 +142,6 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
|||||||
#endif
|
#endif
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GroupCallDisposable : NSObject
|
|
||||||
|
|
||||||
- (instancetype _Nonnull)initWithBlock:(dispatch_block_t _Nonnull)block;
|
|
||||||
- (void)dispose;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@protocol CallVideoFrameBuffer
|
@protocol CallVideoFrameBuffer
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@ -925,7 +925,11 @@ tgcalls::VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(tgcalls:
|
|||||||
std::unique_ptr<tgcalls::Instance> _tgVoip;
|
std::unique_ptr<tgcalls::Instance> _tgVoip;
|
||||||
bool _didStop;
|
bool _didStop;
|
||||||
|
|
||||||
|
OngoingCallStateWebrtc _pendingState;
|
||||||
OngoingCallStateWebrtc _state;
|
OngoingCallStateWebrtc _state;
|
||||||
|
bool _didPushStateOnce;
|
||||||
|
GroupCallDisposable *_pushStateDisposable;
|
||||||
|
|
||||||
OngoingCallVideoStateWebrtc _videoState;
|
OngoingCallVideoStateWebrtc _videoState;
|
||||||
bool _connectedOnce;
|
bool _connectedOnce;
|
||||||
OngoingCallRemoteBatteryLevelWebrtc _remoteBatteryLevel;
|
OngoingCallRemoteBatteryLevelWebrtc _remoteBatteryLevel;
|
||||||
@ -1356,6 +1360,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
|||||||
.directConnectionChannel = directConnectionChannel
|
.directConnectionChannel = directConnectionChannel
|
||||||
});
|
});
|
||||||
_state = OngoingCallStateInitializing;
|
_state = OngoingCallStateInitializing;
|
||||||
|
_pendingState = OngoingCallStateInitializing;
|
||||||
_signalBars = 4;
|
_signalBars = 4;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
@ -1374,6 +1379,8 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
|||||||
_currentAudioDeviceModuleThread = nullptr;
|
_currentAudioDeviceModuleThread = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[_pushStateDisposable dispose];
|
||||||
|
|
||||||
if (_tgVoip != NULL) {
|
if (_tgVoip != NULL) {
|
||||||
[self stop:nil];
|
[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 {
|
- (void)controllerStateChanged:(tgcalls::State)state {
|
||||||
OngoingCallStateWebrtc callState = OngoingCallStateInitializing;
|
OngoingCallStateWebrtc callState = OngoingCallStateInitializing;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
@ -1485,11 +1504,32 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_state != callState) {
|
if (_pendingState != callState) {
|
||||||
_state = callState;
|
_pendingState = callState;
|
||||||
|
|
||||||
if (_stateChanged) {
|
[_pushStateDisposable dispose];
|
||||||
_stateChanged(_state, _videoState, _remoteVideoState, _remoteAudioState, _remoteBatteryLevel, _remotePreferredAspectRatio);
|
_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