Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
overtake 2020-12-12 12:54:13 +04:00
commit ea20efe2e4
23 changed files with 2995 additions and 2732 deletions

View File

@ -2687,6 +2687,7 @@ Unused sets are archived when you add more.";
"Channel.AdminLogFilter.EventsEditedMessages" = "Edited Messages"; "Channel.AdminLogFilter.EventsEditedMessages" = "Edited Messages";
"Channel.AdminLogFilter.EventsPinned" = "Pinned Messages"; "Channel.AdminLogFilter.EventsPinned" = "Pinned Messages";
"Channel.AdminLogFilter.EventsLeaving" = "Members Removed"; "Channel.AdminLogFilter.EventsLeaving" = "Members Removed";
"Channel.AdminLogFilter.EventsCalls" = "Vocie Chats";
"Channel.AdminLogFilter.AdminsTitle" = "ADMINS"; "Channel.AdminLogFilter.AdminsTitle" = "ADMINS";
"Channel.AdminLogFilter.AdminsAll" = "All Admins"; "Channel.AdminLogFilter.AdminsAll" = "All Admins";

View File

@ -132,6 +132,7 @@ public protocol PresentationCall: class {
var peer: Peer? { get } var peer: Peer? { get }
var state: Signal<PresentationCallState, NoError> { get } var state: Signal<PresentationCallState, NoError> { get }
var audioLevel: Signal<Float, NoError> { get }
var isMuted: Signal<Bool, NoError> { get } var isMuted: Signal<Bool, NoError> { get }

View File

@ -93,6 +93,10 @@ public let listViewAnimationCurveLinear: (CGFloat) -> CGFloat = { t in
return t return t
} }
public let listViewAnimationCurveEaseInOut: (CGFloat) -> CGFloat = { t in
return bezierPoint(0.42, 0.0, 0.58, 1.0, t)
}
#if os(iOS) #if os(iOS)
public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIView.AnimationOptions) -> (CGFloat) -> CGFloat { public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIView.AnimationOptions) -> (CGFloat) -> CGFloat {
if animationOptions.rawValue == UInt(7 << 16) { if animationOptions.rawValue == UInt(7 << 16) {

View File

@ -119,6 +119,10 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
final let wantsScrollDynamics: Bool final let wantsScrollDynamics: Bool
open var preferredAnimationCurve: (CGFloat) -> CGFloat {
return listViewAnimationCurveSystem
}
public final var wantsTrailingItemSpaceUpdates: Bool = false public final var wantsTrailingItemSpaceUpdates: Bool = false
public final var scrollPositioningInsets: UIEdgeInsets = UIEdgeInsets() public final var scrollPositioningInsets: UIEdgeInsets = UIEdgeInsets()
@ -427,7 +431,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
} }
public func addHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) { public func addHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) {
let animation = ListViewAnimation(from: self.bounds.height, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in let animation = ListViewAnimation(from: self.bounds.height, to: value, duration: duration, curve: self.preferredAnimationCurve, beginAt: beginAt, update: { [weak self] progress, currentValue in
if let strongSelf = self { if let strongSelf = self {
let frame = strongSelf.frame let frame = strongSelf.frame
strongSelf.frame = CGRect(origin: frame.origin, size: CGSize(width: frame.width, height: currentValue)) strongSelf.frame = CGRect(origin: frame.origin, size: CGSize(width: frame.width, height: currentValue))
@ -461,7 +465,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
} }
public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) { public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) {
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: self.preferredAnimationCurve, beginAt: beginAt, update: { [weak self] progress, currentValue in
if let strongSelf = self { if let strongSelf = self {
strongSelf.apparentHeight = currentValue strongSelf.apparentHeight = currentValue
if let update = update { if let update = update {
@ -494,7 +498,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
} }
public func addTransitionOffsetAnimation(_ value: CGFloat, duration: Double, beginAt: Double) { public func addTransitionOffsetAnimation(_ value: CGFloat, duration: Double, beginAt: Double) {
let animation = ListViewAnimation(from: self.transitionOffset, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] _, currentValue in let animation = ListViewAnimation(from: self.transitionOffset, to: value, duration: duration, curve: self.preferredAnimationCurve, beginAt: beginAt, update: { [weak self] _, currentValue in
if let strongSelf = self { if let strongSelf = self {
strongSelf.transitionOffset = currentValue strongSelf.transitionOffset = currentValue
} }

View File

@ -528,7 +528,18 @@
videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer]; videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
} }
AVAssetReaderVideoCompositionOutput *output = [[AVAssetReaderVideoCompositionOutput alloc] initWithVideoTracks:[composition tracksWithMediaType:AVMediaTypeVideo] videoSettings:@{ (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) }]; NSDictionary *settings = [TGMediaVideoConversionPresetSettings videoSettingsForPreset:preset dimensions:outputDimensions];
*outputSettings = settings;
*dimensions = outputDimensions;
NSMutableDictionary *videoSettings = [[NSMutableDictionary alloc] init];
videoSettings[(id)kCVPixelBufferPixelFormatTypeKey] = @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange);
#if TARGET_IPHONE_SIMULATOR
#else
videoSettings[AVVideoColorPropertiesKey] = settings[AVVideoColorPropertiesKey];
#endif
AVAssetReaderVideoCompositionOutput *output = [[AVAssetReaderVideoCompositionOutput alloc] initWithVideoTracks:[composition tracksWithMediaType:AVMediaTypeVideo] videoSettings:videoSettings];
output.videoComposition = videoComposition; output.videoComposition = videoComposition;
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:composition]; AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:composition];
@ -543,9 +554,6 @@
return [context addImageGenerator:imageGenerator]; return [context addImageGenerator:imageGenerator];
}]; }];
*outputSettings = [TGMediaVideoConversionPresetSettings videoSettingsForPreset:preset dimensions:outputDimensions];
*dimensions = outputDimensions;
return output; return output;
} }
@ -1315,6 +1323,14 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
AVVideoPixelAspectRatioKey: videoAspectRatioSettings AVVideoPixelAspectRatioKey: videoAspectRatioSettings
}; };
NSDictionary *hdVideoProperties = @
{
AVVideoColorPrimariesKey: AVVideoColorPrimaries_ITU_R_709_2,
AVVideoTransferFunctionKey: AVVideoTransferFunction_ITU_R_709_2,
AVVideoYCbCrMatrixKey: AVVideoYCbCrMatrix_ITU_R_709_2,
};
#if TARGET_IPHONE_SIMULATOR
return @ return @
{ {
AVVideoCodecKey: AVVideoCodecH264, AVVideoCodecKey: AVVideoCodecH264,
@ -1322,6 +1338,15 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
AVVideoWidthKey: @((NSInteger)dimensions.width), AVVideoWidthKey: @((NSInteger)dimensions.width),
AVVideoHeightKey: @((NSInteger)dimensions.height) AVVideoHeightKey: @((NSInteger)dimensions.height)
}; };
#endif
return @
{
AVVideoCodecKey: AVVideoCodecH264,
AVVideoCompressionPropertiesKey: codecSettings,
AVVideoWidthKey: @((NSInteger)dimensions.width),
AVVideoHeightKey: @((NSInteger)dimensions.height),
AVVideoColorPropertiesKey: hdVideoProperties
};
} }
+ (NSInteger)_videoBitrateKbpsForPreset:(TGMediaVideoConversionPreset)preset + (NSInteger)_videoBitrateKbpsForPreset:(TGMediaVideoConversionPreset)preset

View File

@ -14,6 +14,7 @@ import AnimatedCountLabelNode
private let blue = UIColor(rgb: 0x0078ff) private let blue = UIColor(rgb: 0x0078ff)
private let lightBlue = UIColor(rgb: 0x59c7f8) private let lightBlue = UIColor(rgb: 0x59c7f8)
private let green = UIColor(rgb: 0x33c659) private let green = UIColor(rgb: 0x33c659)
private let activeBlue = UIColor(rgb: 0x00a0b9)
private class CallStatusBarBackgroundNode: ASDisplayNode { private class CallStatusBarBackgroundNode: ASDisplayNode {
private let foregroundView: UIView private let foregroundView: UIView
@ -46,7 +47,7 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
let initialColors = self.foregroundGradientLayer.colors let initialColors = self.foregroundGradientLayer.colors
let targetColors: [CGColor] let targetColors: [CGColor]
if let speaking = self.speaking { if let speaking = self.speaking {
targetColors = speaking ? [green.cgColor, blue.cgColor] : [blue.cgColor, lightBlue.cgColor] targetColors = speaking ? [green.cgColor, activeBlue.cgColor] : [blue.cgColor, lightBlue.cgColor]
} else { } else {
targetColors = [connectingColor.cgColor, connectingColor.cgColor] targetColors = [connectingColor.cgColor, connectingColor.cgColor]
} }
@ -159,6 +160,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
private let backgroundNode: CallStatusBarBackgroundNode private let backgroundNode: CallStatusBarBackgroundNode
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNode
private let subtitleNode: ImmediateAnimatedCountLabelNode private let subtitleNode: ImmediateAnimatedCountLabelNode
private let speakerNode: ImmediateTextNode
private let audioLevelDisposable = MetaDisposable() private let audioLevelDisposable = MetaDisposable()
private let stateDisposable = MetaDisposable() private let stateDisposable = MetaDisposable()
@ -175,6 +177,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
private var currentCallState: PresentationCallState? private var currentCallState: PresentationCallState?
private var currentGroupCallState: PresentationGroupCallSummaryState? private var currentGroupCallState: PresentationGroupCallSummaryState?
private var currentIsMuted = true private var currentIsMuted = true
private var currentMembers: PresentationGroupCallMembers?
private var currentIsConnected = true private var currentIsConnected = true
public override init() { public override init() {
@ -182,12 +185,14 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
self.titleNode = ImmediateTextNode() self.titleNode = ImmediateTextNode()
self.subtitleNode = ImmediateAnimatedCountLabelNode() self.subtitleNode = ImmediateAnimatedCountLabelNode()
self.subtitleNode.reverseAnimationDirection = true self.subtitleNode.reverseAnimationDirection = true
self.speakerNode = ImmediateTextNode()
super.init() super.init()
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)
self.addSubnode(self.titleNode) self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode) self.addSubnode(self.subtitleNode)
self.addSubnode(self.speakerNode)
} }
deinit { deinit {
@ -244,6 +249,13 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
strongSelf.update() strongSelf.update()
} }
})) }))
self.audioLevelDisposable.set((call.audioLevel
|> deliverOnMainQueue).start(next: { [weak self] audioLevel in
guard let strongSelf = self else {
return
}
strongSelf.backgroundNode.audioLevel = audioLevel
}))
case let .groupCall(sharedContext, account, call): case let .groupCall(sharedContext, account, call):
self.presentationData = sharedContext.currentPresentationData.with { $0 } self.presentationData = sharedContext.currentPresentationData.with { $0 }
self.presentationDataDisposable.set((sharedContext.presentationData self.presentationDataDisposable.set((sharedContext.presentationData
@ -257,16 +269,20 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
(combineLatest( (combineLatest(
account.postbox.peerView(id: call.peerId), account.postbox.peerView(id: call.peerId),
call.summaryState, call.summaryState,
call.isMuted call.isMuted,
call.members
) )
|> deliverOnMainQueue).start(next: { [weak self] view, state, isMuted in |> deliverOnMainQueue).start(next: { [weak self] view, state, isMuted, members in
if let strongSelf = self { if let strongSelf = self {
strongSelf.currentPeer = view.peers[view.peerId] strongSelf.currentPeer = view.peers[view.peerId]
strongSelf.currentGroupCallState = state strongSelf.currentGroupCallState = state
strongSelf.currentMembers = members
var isMuted = isMuted var isMuted = isMuted
if let state = state, state.callState.muteState != nil { if let state = state, let muteState = state.callState.muteState {
isMuted = true if !muteState.canUnmute {
isMuted = true
}
} }
strongSelf.currentIsMuted = isMuted strongSelf.currentIsMuted = isMuted
@ -287,17 +303,18 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
return return
} }
var effectiveLevel: Float = 0.0 var effectiveLevel: Float = 0.0
var audioLevels = audioLevels
if !strongSelf.currentIsMuted { if !strongSelf.currentIsMuted {
effectiveLevel = myAudioLevel audioLevels.append((PeerId(0), myAudioLevel, true))
} else {
effectiveLevel = audioLevels.map { $0.1 }.max() ?? 0.0
} }
effectiveLevel = audioLevels.map { $0.1 }.max() ?? 0.0
strongSelf.backgroundNode.audioLevel = effectiveLevel strongSelf.backgroundNode.audioLevel = effectiveLevel
})) }))
} }
} }
var title: String = "" var title: String = ""
var speakerSubtitle: String = ""
let textFont = Font.regular(13.0) let textFont = Font.regular(13.0)
let textColor = UIColor.white let textColor = UIColor.white
@ -314,6 +331,21 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
membersCount = 1 membersCount = 1
} }
var speakingPeer: Peer?
if let members = currentMembers {
var speakingPeers: [Peer] = []
for member in members.participants {
if members.speakingParticipants.contains(member.peer.id) {
speakingPeers.append(member.peer)
}
}
speakingPeer = speakingPeers.first
}
if let speakingPeer = speakingPeer {
speakerSubtitle = speakingPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
}
if let membersCount = membersCount { if let membersCount = membersCount {
var membersPart = presentationData.strings.VoiceChat_Status_Members(membersCount) var membersPart = presentationData.strings.VoiceChat_Status_Members(membersCount)
if let startIndex = membersPart.firstIndex(of: "["), let endIndex = membersPart.firstIndex(of: "]") { if let startIndex = membersPart.firstIndex(of: "["), let endIndex = membersPart.firstIndex(of: "]") {
@ -364,15 +396,24 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
self.backgroundNode.connectingColor = color self.backgroundNode.connectingColor = color
} }
if self.subtitleNode.segments != segments { if self.subtitleNode.segments != segments && speakerSubtitle.isEmpty {
self.subtitleNode.segments = segments self.subtitleNode.segments = segments
} }
let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.subtitleNode, alpha: !speakerSubtitle.isEmpty ? 0.0 : 1.0)
alphaTransition.updateAlpha(node: self.speakerNode, alpha: !speakerSubtitle.isEmpty ? 1.0 : 0.0)
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(13.0), textColor: .white) self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(13.0), textColor: .white)
if !speakerSubtitle.isEmpty {
self.speakerNode.attributedText = NSAttributedString(string: speakerSubtitle, font: Font.regular(13.0), textColor: .white)
}
let spacing: CGFloat = 5.0 let spacing: CGFloat = 5.0
let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: size.height)) let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: size.height))
let subtitleSize = self.subtitleNode.updateLayout(size: CGSize(width: 160.0, height: size.height), animated: true) let subtitleSize = self.subtitleNode.updateLayout(size: CGSize(width: 160.0, height: size.height), animated: true)
let speakerSize = self.speakerNode.updateLayout(CGSize(width: 160.0, height: size.height))
let totalWidth = titleSize.width + spacing + subtitleSize.width let totalWidth = titleSize.width + spacing + subtitleSize.width
let horizontalOrigin: CGFloat = floor((size.width - totalWidth) / 2.0) let horizontalOrigin: CGFloat = floor((size.width - totalWidth) / 2.0)
@ -384,6 +425,10 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin + floor((contentHeight - titleSize.height) / 2.0)), size: titleSize)) transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin + floor((contentHeight - titleSize.height) / 2.0)), size: titleSize))
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - subtitleSize.height) / 2.0)), size: subtitleSize)) transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - subtitleSize.height) / 2.0)), size: subtitleSize))
if !speakerSubtitle.isEmpty {
self.speakerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - speakerSize.height) / 2.0)), size: speakerSize)
}
self.backgroundNode.speaking = self.currentIsConnected ? !self.currentIsMuted : nil self.backgroundNode.speaking = self.currentIsConnected ? !self.currentIsMuted : nil
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 18.0)) self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 18.0))
} }

View File

@ -93,7 +93,7 @@ final class PresentationCallToneRenderer {
var takenCount = 0 var takenCount = 0
while takenCount < frameSize { while takenCount < frameSize {
let dataOffset = (takeOffset + takenCount) % toneData.count let dataOffset = (takeOffset + takenCount) % toneData.count
let dataCount = min(frameSize, toneData.count - dataOffset) let dataCount = min(frameSize - takenCount, toneData.count - dataOffset)
//print("take from \(dataOffset) count: \(dataCount)") //print("take from \(dataOffset) count: \(dataCount)")
memcpy(bytes.advanced(by: takenCount), dataBytes.advanced(by: dataOffset), dataCount) memcpy(bytes.advanced(by: takenCount), dataBytes.advanced(by: dataOffset), dataCount)
takenCount += dataCount takenCount += dataCount
@ -200,6 +200,7 @@ public final class PresentationCallImpl: PresentationCall {
private var requestedVideoAspect: Float? private var requestedVideoAspect: Float?
private var reception: Int32? private var reception: Int32?
private var receptionDisposable: Disposable? private var receptionDisposable: Disposable?
private var audioLevelDisposable: Disposable?
private var reportedIncomingCall = false private var reportedIncomingCall = false
private var batteryLevelDisposable: Disposable? private var batteryLevelDisposable: Disposable?
@ -219,6 +220,11 @@ public final class PresentationCallImpl: PresentationCall {
return self.statePromise.get() return self.statePromise.get()
} }
private let audioLevelPromise = ValuePromise<Float>(0.0)
public var audioLevel: Signal<Float, NoError> {
return self.audioLevelPromise.get()
}
private let isMutedPromise = ValuePromise<Bool>(false) private let isMutedPromise = ValuePromise<Bool>(false)
private var isMutedValue = false private var isMutedValue = false
public var isMuted: Signal<Bool, NoError> { public var isMuted: Signal<Bool, NoError> {
@ -436,6 +442,7 @@ public final class PresentationCallImpl: PresentationCall {
self.sessionStateDisposable?.dispose() self.sessionStateDisposable?.dispose()
self.ongoingContextStateDisposable?.dispose() self.ongoingContextStateDisposable?.dispose()
self.receptionDisposable?.dispose() self.receptionDisposable?.dispose()
self.audioLevelDisposable?.dispose()
self.batteryLevelDisposable?.dispose() self.batteryLevelDisposable?.dispose()
self.audioSessionDisposable?.dispose() self.audioSessionDisposable?.dispose()
@ -656,6 +663,13 @@ public final class PresentationCallImpl: PresentationCall {
} }
}) })
self.audioLevelDisposable = (ongoingContext.audioLevel
|> deliverOnMainQueue).start(next: { [weak self] level in
if let strongSelf = self {
strongSelf.audioLevelPromise.set(level)
}
})
func batteryLevelIsLowSignal() -> Signal<Bool, NoError> { func batteryLevelIsLowSignal() -> Signal<Bool, NoError> {
return Signal { subscriber in return Signal { subscriber in
let device = UIDevice.current let device = UIDevice.current

View File

@ -578,7 +578,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
switch update { switch update {
case let .state(update): case let .state(update):
for participantUpdate in update.participantUpdates { for participantUpdate in update.participantUpdates {
if participantUpdate.isRemoved { if case .left = participantUpdate.participationStatusChange {
removedSsrc.append(participantUpdate.ssrc) removedSsrc.append(participantUpdate.ssrc)
if participantUpdate.peerId == strongSelf.accountContext.account.peerId { if participantUpdate.peerId == strongSelf.accountContext.account.peerId {
@ -590,6 +590,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if case let .estabilished(_, _, ssrc, _) = strongSelf.internalState, ssrc != participantUpdate.ssrc { if case let .estabilished(_, _, ssrc, _) = strongSelf.internalState, ssrc != participantUpdate.ssrc {
strongSelf._canBeRemoved.set(.single(true)) strongSelf._canBeRemoved.set(.single(true))
} }
} else if case .joined = participantUpdate.participationStatusChange {
} }
} }
case let .call(isTerminated, _): case let .call(isTerminated, _):

View File

@ -14,6 +14,7 @@ private let secondaryGreyColor = UIColor(rgb: 0x1c1c1e)
private let blue = UIColor(rgb: 0x0078ff) private let blue = UIColor(rgb: 0x0078ff)
private let lightBlue = UIColor(rgb: 0x59c7f8) private let lightBlue = UIColor(rgb: 0x59c7f8)
private let green = UIColor(rgb: 0x33c659) private let green = UIColor(rgb: 0x33c659)
private let activeBlue = UIColor(rgb: 0x00a0b9)
private let areaSize = CGSize(width: 440.0, height: 440.0) private let areaSize = CGSize(width: 440.0, height: 440.0)
private let blobSize = CGSize(width: 244.0, height: 244.0) private let blobSize = CGSize(width: 244.0, height: 244.0)
@ -205,8 +206,6 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
let iconSize = CGSize(width: 90.0, height: 90.0) let iconSize = CGSize(width: 90.0, height: 90.0)
self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconSize) self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconSize)
self.iconNode.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0) self.iconNode.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
self.wasActiveWhenPressed = false
} }
private func applyIconParams() { private func applyIconParams() {
@ -520,8 +519,10 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
} else { } else {
let previousValue = self.foregroundGradientLayer.startPoint let previousValue = self.foregroundGradientLayer.startPoint
let newValue: CGPoint let newValue: CGPoint
if self.maskBlobView.presentationAudioLevel > 0.15 { if self.maskBlobView.presentationAudioLevel > 0.22 {
newValue = CGPoint(x: CGFloat.random(in: 0.8 ..< 1.0), y: CGFloat.random(in: 0.1 ..< 0.45)) newValue = CGPoint(x: CGFloat.random(in: 0.9 ..< 1.0), y: CGFloat.random(in: 0.1 ..< 0.35))
} else if self.maskBlobView.presentationAudioLevel > 0.01 {
newValue = CGPoint(x: CGFloat.random(in: 0.77 ..< 0.95), y: CGFloat.random(in: 0.1 ..< 0.35))
} else { } else {
newValue = CGPoint(x: CGFloat.random(in: 0.65 ..< 0.85), y: CGFloat.random(in: 0.1 ..< 0.45)) newValue = CGPoint(x: CGFloat.random(in: 0.65 ..< 0.85), y: CGFloat.random(in: 0.1 ..< 0.45))
} }
@ -626,7 +627,7 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
let targetScale: CGFloat let targetScale: CGFloat
if let active = active { if let active = active {
if active { if active {
targetColors = [blue.cgColor, green.cgColor] targetColors = [activeBlue.cgColor, green.cgColor]
targetScale = 0.89 targetScale = 0.89
outerColor = UIColor(rgb: 0x21674f) outerColor = UIColor(rgb: 0x21674f)
} else { } else {

View File

@ -257,4 +257,8 @@ class VoiceChatActionItemNode: ListViewItemNode {
override public func header() -> ListViewItemHeader? { override public func header() -> ListViewItemHeader? {
return nil return nil
} }
override var preferredAnimationCurve: (CGFloat) -> CGFloat {
return listViewAnimationCurveEaseInOut
}
} }

View File

@ -127,6 +127,8 @@ public final class VoiceChatController: ViewController {
let isLoading: Bool let isLoading: Bool
let isEmpty: Bool let isEmpty: Bool
let crossFade: Bool let crossFade: Bool
let count: Int
let isExpanded: Bool
let animated: Bool let animated: Bool
} }
@ -361,14 +363,14 @@ public final class VoiceChatController: ViewController {
} }
} }
private func preparedTransition(from fromEntries: [ListEntry], to toEntries: [ListEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, context: AccountContext, presentationData: PresentationData, interaction: Interaction) -> ListTransition { private func preparedTransition(from fromEntries: [ListEntry], to toEntries: [ListEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, context: AccountContext, presentationData: PresentationData, interaction: Interaction, isExpanded: Bool) -> ListTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) } let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) } let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, interaction: interaction), directionHint: nil) }
return ListTransition(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, crossFade: crossFade, animated: fromEntries.count != toEntries.count) return ListTransition(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, crossFade: crossFade, count: toEntries.count, isExpanded: isExpanded, animated: true)
} }
private weak var controller: VoiceChatController? private weak var controller: VoiceChatController?
@ -412,6 +414,9 @@ public final class VoiceChatController: ViewController {
private var currentCallMembers: [GroupCallParticipantsContext.Participant]? private var currentCallMembers: [GroupCallParticipantsContext.Participant]?
private var currentInvitedPeers: [Peer]? private var currentInvitedPeers: [Peer]?
private var currentSpeakingPeers: Set<PeerId>? private var currentSpeakingPeers: Set<PeerId>?
private var currentIsExpanded: Bool = false
private var currentContentOffset: CGFloat?
private var ignoreScrolling = false
private var accountPeer: Peer? private var accountPeer: Peer?
private var currentAudioButtonColor: UIColor? private var currentAudioButtonColor: UIColor?
@ -466,12 +471,11 @@ public final class VoiceChatController: ViewController {
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = secondaryPanelBackgroundColor self.backgroundNode.backgroundColor = secondaryPanelBackgroundColor
self.backgroundNode.clipsToBounds = false
self.listNode = ListView() self.listNode = ListView()
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3) self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
self.listNode.clipsToBounds = true self.listNode.clipsToBounds = true
self.listNode.stackFromBottom = true
self.listNode.keepMinimalScrollHeightWithTopInset = 0
self.topPanelNode = ASDisplayNode() self.topPanelNode = ASDisplayNode()
self.topPanelNode.clipsToBounds = false self.topPanelNode.clipsToBounds = false
@ -482,8 +486,11 @@ public final class VoiceChatController: ViewController {
self.topPanelEdgeNode = ASDisplayNode() self.topPanelEdgeNode = ASDisplayNode()
self.topPanelEdgeNode.backgroundColor = panelBackgroundColor self.topPanelEdgeNode.backgroundColor = panelBackgroundColor
self.topPanelEdgeNode.layer.cornerRadius = 12.0 self.topPanelEdgeNode.cornerRadius = 12.0
self.topPanelEdgeNode.isUserInteractionEnabled = false self.topPanelEdgeNode.isUserInteractionEnabled = false
if #available(iOS 11.0, *) {
self.topPanelEdgeNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
}
self.optionsButton = VoiceChatHeaderButton() self.optionsButton = VoiceChatHeaderButton()
self.optionsButton.setImage(optionsButtonImage(dark: false)) self.optionsButton.setImage(optionsButtonImage(dark: false))
@ -516,10 +523,12 @@ public final class VoiceChatController: ViewController {
self.leftBorderNode = ASDisplayNode() self.leftBorderNode = ASDisplayNode()
self.leftBorderNode.backgroundColor = panelBackgroundColor self.leftBorderNode.backgroundColor = panelBackgroundColor
self.leftBorderNode.isUserInteractionEnabled = false self.leftBorderNode.isUserInteractionEnabled = false
self.leftBorderNode.clipsToBounds = false
self.rightBorderNode = ASDisplayNode() self.rightBorderNode = ASDisplayNode()
self.rightBorderNode.backgroundColor = panelBackgroundColor self.rightBorderNode.backgroundColor = panelBackgroundColor
self.rightBorderNode.isUserInteractionEnabled = false self.rightBorderNode.isUserInteractionEnabled = false
self.rightBorderNode.clipsToBounds = false
super.init() super.init()
@ -877,7 +886,7 @@ public final class VoiceChatController: ViewController {
} }
} }
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: callMembers?.participants ?? [], invitedPeers: invitedPeers, speakingPeers: callMembers?.speakingParticipants ?? []) strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: callMembers?.participants ?? [], invitedPeers: invitedPeers, speakingPeers: callMembers?.speakingParticipants ?? [], isExpanded: strongSelf.currentIsExpanded)
let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers?.totalCount ?? 0))) let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers?.totalCount ?? 0)))
strongSelf.currentSubtitle = subtitle strongSelf.currentSubtitle = subtitle
@ -898,7 +907,7 @@ public final class VoiceChatController: ViewController {
} }
if !strongSelf.didSetDataReady { if !strongSelf.didSetDataReady {
strongSelf.accountPeer = accountPeer strongSelf.accountPeer = accountPeer
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set()) strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set(), isExpanded: strongSelf.currentIsExpanded)
if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel { if let peer = peerViewMainPeer(view), let channel = peer as? TelegramChannel {
let addressName = channel.addressName ?? "" let addressName = channel.addressName ?? ""
@ -1090,6 +1099,7 @@ public final class VoiceChatController: ViewController {
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
if let strongSelf = self { if let strongSelf = self {
strongSelf.currentContentOffset = offset
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition) strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
} }
} }
@ -1098,10 +1108,27 @@ public final class VoiceChatController: ViewController {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
switch strongSelf.listNode.visibleContentOffset() { if strongSelf.ignoreScrolling {
Queue.mainQueue().after(0.5) {
strongSelf.ignoreScrolling = false
}
}
}
self.listNode.visibleContentOffsetChanged = { [weak self] offset in
guard let strongSelf = self else {
return
}
switch offset {
case let .known(value): case let .known(value):
if value <= -10.0 { // strongSelf.updateFloatingHeaderOffset(offset: -value + strongSelf.listNode.insets.top, transition: strongSelf.listNode.isTracking ? .immediate : .animated(duration: 0.4, curve: .linear))
// strongSelf.controller?.dismiss()
if value > 5, !strongSelf.currentIsExpanded && strongSelf.listNode.isTracking && !strongSelf.ignoreScrolling {
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers:strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set(), isExpanded: true)
strongSelf.ignoreScrolling = true
} else if value < -5, strongSelf.currentIsExpanded && strongSelf.listNode.isTracking && !strongSelf.ignoreScrolling {
strongSelf.updateMembers(muteState: strongSelf.effectiveMuteState, callMembers: strongSelf.currentCallMembers ?? [], invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? [], isExpanded: false)
strongSelf.ignoreScrolling = true
} }
default: default:
break break
@ -1196,7 +1223,7 @@ public final class VoiceChatController: ViewController {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring)) self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
} }
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set()) self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set(), isExpanded: self.currentIsExpanded)
} }
@objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) { @objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) {
@ -1241,7 +1268,7 @@ public final class VoiceChatController: ViewController {
if let (layout, navigationHeight) = self.validLayout { if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring)) self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .spring))
} }
self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set()) self.updateMembers(muteState: self.effectiveMuteState, callMembers: self.currentCallMembers ?? [], invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set(), isExpanded: self.currentIsExpanded)
default: default:
break break
} }
@ -1315,82 +1342,82 @@ public final class VoiceChatController: ViewController {
} }
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
guard let (validLayout, _) = self.validLayout else { guard let (layout, _) = self.validLayout else {
return return
} }
self.floatingHeaderOffset = offset let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
let layoutTopInset: CGFloat = max(validLayout.statusBarHeight ?? 0.0, validLayout.safeInsets.top)
let topPanelHeight: CGFloat = 63.0 let topPanelHeight: CGFloat = 63.0
let listTopInset = layoutTopInset + topPanelHeight let listTopInset = layoutTopInset + topPanelHeight
let bottomAreaHeight: CGFloat = 268.0
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
let topInset = self.topInset ?? listSize.height
var offset = offset + topInset
self.floatingHeaderOffset = offset
let rawPanelOffset = offset + listTopInset - topPanelHeight let rawPanelOffset = offset + listTopInset - topPanelHeight
let panelOffset = max(layoutTopInset, rawPanelOffset) let panelOffset = max(layoutTopInset, rawPanelOffset)
let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: validLayout.size.width, height: topPanelHeight)) let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: layout.size.width, height: topPanelHeight))
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: layout.size.width, height: layout.size.height))
let sideInset: CGFloat = 16.0
let leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: layout.size.height))
let rightBorderFrame = CGRect(origin: CGPoint(x: layout.size.width - sideInset, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: layout.size.height))
let previousTopPanelFrame = self.topPanelNode.frame let previousTopPanelFrame = self.topPanelNode.frame
let previousBackgroundFrame = self.backgroundNode.frame
let previousLeftBorderFrame = self.leftBorderNode.frame
let previousRightBorderFrame = self.rightBorderNode.frame
if !topPanelFrame.equalTo(previousTopPanelFrame) { if !topPanelFrame.equalTo(previousTopPanelFrame) {
self.topPanelNode.frame = topPanelFrame self.topPanelNode.frame = topPanelFrame
let positionDelta = CGPoint(x: 0.0, y: topPanelFrame.minY - previousTopPanelFrame.minY)
let positionDelta = CGPoint(x: topPanelFrame.minX - previousTopPanelFrame.minX, y: topPanelFrame.minY - previousTopPanelFrame.minY)
transition.animateOffsetAdditive(node: self.topPanelNode, offset: positionDelta.y) transition.animateOffsetAdditive(node: self.topPanelNode, offset: positionDelta.y)
self.backgroundNode.frame = backgroundFrame
let backgroundPositionDelta = CGPoint(x: 0.0, y: previousBackgroundFrame.minY - backgroundFrame.minY)
transition.animatePositionAdditive(node: self.backgroundNode, offset: backgroundPositionDelta)
self.leftBorderNode.frame = leftBorderFrame
let leftBorderPositionDelta = CGPoint(x: 0.0, y: previousLeftBorderFrame.minY - leftBorderFrame.minY)
transition.animatePositionAdditive(node: self.leftBorderNode, offset: leftBorderPositionDelta)
self.rightBorderNode.frame = rightBorderFrame
let rightBorderPositionDelta = CGPoint(x: 0.0, y: previousRightBorderFrame.minY - rightBorderFrame.minY)
transition.animatePositionAdditive(node: self.rightBorderNode, offset: rightBorderPositionDelta)
} }
self.topPanelBackgroundNode.frame = CGRect(x: 0.0, y: topPanelHeight - 24.0, width: validLayout.size.width, height: 24.0) self.topPanelBackgroundNode.frame = CGRect(x: 0.0, y: topPanelHeight - 24.0, width: layout.size.width, height: 24.0)
self.topPanelEdgeNode.frame = CGRect(x: 0.0, y: 0.0, width: validLayout.size.width, height: topPanelHeight)
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: validLayout.size.width, height: validLayout.size.height))
let sideInset: CGFloat = 16.0
let leftBorderFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: validLayout.size.height))
let rightBorderFrame = CGRect(origin: CGPoint(x: validLayout.size.width - sideInset, y: topPanelFrame.maxY - 16.0), size: CGSize(width: sideInset, height: validLayout.size.height))
var bottomEdge: CGFloat = 0.0 var bottomEdge: CGFloat = 0.0
self.listNode.forEachItemNode { itemNode in self.listNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ListViewItemNode { if let itemNode = itemNode as? ListViewItemNode {
let convertedFrame = self.listNode.view.convert(itemNode.apparentFrame, to: self.view) let convertedFrame = self.listNode.view.convert(itemNode.frame, to: self.view)
if convertedFrame.maxY > bottomEdge { if convertedFrame.maxY > bottomEdge {
bottomEdge = convertedFrame.maxY bottomEdge = convertedFrame.maxY
} }
} }
} }
let listMaxY = listTopInset + listSize.height
if bottomEdge.isZero {
bottomEdge = listMaxY
}
var bottomOffset: CGFloat = 0.0 var bottomOffset: CGFloat = 0.0
if bottomEdge < self.listNode.frame.maxY { if bottomEdge < listMaxY {
bottomOffset = bottomEdge - self.listNode.frame.maxY bottomOffset = bottomEdge - listMaxY
} }
let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset, y: -50.0 + bottomOffset), size: CGSize(width: validLayout.size.width - sideInset * 2.0, height: 50.0)) let bottomCornersFrame = CGRect(origin: CGPoint(x: sideInset, y: -50.0 + bottomOffset), size: CGSize(width: layout.size.width - sideInset * 2.0, height: 50.0))
let previousBottomCornersFrame = self.bottomCornersNode.frame let previousBottomCornersFrame = self.bottomCornersNode.frame
if !bottomCornersFrame.equalTo(previousBottomCornersFrame) { if !bottomCornersFrame.equalTo(previousBottomCornersFrame) {
self.bottomCornersNode.frame = bottomCornersFrame self.bottomCornersNode.frame = bottomCornersFrame
self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset, width: validLayout.size.width, height: 2000.0) self.bottomPanelBackgroundNode.frame = CGRect(x: 0.0, y: bottomOffset, width: layout.size.width, height: 2000.0)
let positionDelta = CGPoint(x: bottomCornersFrame.minX - previousBottomCornersFrame.minX, y: bottomCornersFrame.minY - previousBottomCornersFrame.minY) let positionDelta = CGPoint(x: 0.0, y: previousBottomCornersFrame.minY - bottomCornersFrame.minY)
transition.animateOffsetAdditive(node: self.bottomCornersNode, offset: positionDelta.y) transition.animatePositionAdditive(node: self.bottomCornersNode, offset: positionDelta)
transition.animateOffsetAdditive(node: self.bottomPanelBackgroundNode, offset: positionDelta.y) transition.animatePositionAdditive(node: self.bottomPanelBackgroundNode, offset: positionDelta)
}
let previousBackgroundFrame = self.backgroundNode.frame
let previousLeftBorderFrame = self.leftBorderNode.frame
let previousRightBorderFrame = self.rightBorderNode.frame
self.updateColors(fullscreen: abs(panelOffset - layoutTopInset) < 1.0)
if !backgroundFrame.equalTo(previousBackgroundFrame) {
self.backgroundNode.frame = backgroundFrame
self.leftBorderNode.frame = leftBorderFrame
self.rightBorderNode.frame = rightBorderFrame
let backgroundPositionDelta = CGPoint(x: backgroundFrame.minX - previousBackgroundFrame.minX, y: backgroundFrame.minY - previousBackgroundFrame.minY)
transition.animateOffsetAdditive(node: self.backgroundNode, offset: backgroundPositionDelta.y)
let leftBorderPositionDelta = CGPoint(x: leftBorderFrame.minX - previousLeftBorderFrame.minX, y: leftBorderFrame.minY - previousLeftBorderFrame.minY)
transition.animateOffsetAdditive(node: self.leftBorderNode, offset: leftBorderPositionDelta.y)
let rightBorderPositionDelta = CGPoint(x: rightBorderFrame.minX - previousRightBorderFrame.minX, y: rightBorderFrame.minY - previousRightBorderFrame.minY)
transition.animateOffsetAdditive(node: self.rightBorderNode, offset: rightBorderPositionDelta.y)
} }
} }
@ -1405,8 +1432,22 @@ public final class VoiceChatController: ViewController {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear) let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear)
// transition.updateCornerRadius(node: self.topPanelEdgeNode, cornerRadius: fullscreen ? layout.deviceMetrics.screenCornerRadius : 12.0) let topPanelHeight: CGFloat = 63.0
transition.updateBackgroundColor(node: self.dimNode, color: fullscreen ? fullscreenBackgroundColor : dimColor) let topEdgeFrame: CGRect
if self.isFullscreen {
let offset: CGFloat
if let statusBarHeight = layout.statusBarHeight {
offset = statusBarHeight
} else {
offset = 44.0
}
topEdgeFrame = CGRect(x: 0.0, y: -offset, width: layout.size.width, height: topPanelHeight + offset)
} else {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: topPanelHeight)
}
transition.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
transition.updateCornerRadius(node: self.topPanelEdgeNode, cornerRadius: fullscreen ? layout.deviceMetrics.screenCornerRadius - 0.5 : 12.0)
// transition.updateBackgroundColor(node: self.dimNode, color: fullscreen ? fullscreenBackgroundColor : dimColor)
transition.updateBackgroundColor(node: self.topPanelBackgroundNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor) transition.updateBackgroundColor(node: self.topPanelBackgroundNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.topPanelEdgeNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor) transition.updateBackgroundColor(node: self.topPanelEdgeNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
transition.updateBackgroundColor(node: self.backgroundNode, color: fullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor) transition.updateBackgroundColor(node: self.backgroundNode, color: fullscreen ? panelBackgroundColor : secondaryPanelBackgroundColor)
@ -1541,13 +1582,26 @@ public final class VoiceChatController: ViewController {
insets.left = layout.safeInsets.left + sideInset insets.left = layout.safeInsets.left + sideInset
insets.right = layout.safeInsets.right + sideInset insets.right = layout.safeInsets.right + sideInset
let topPanelHeight: CGFloat = 63.0
let topEdgeFrame: CGRect
if self.isFullscreen {
let offset: CGFloat
if let statusBarHeight = layout.statusBarHeight {
offset = statusBarHeight
} else {
offset = 44.0
}
topEdgeFrame = CGRect(x: 0.0, y: -offset, width: layout.size.width, height: topPanelHeight + offset)
} else {
topEdgeFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: topPanelHeight)
}
transition.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let listTopInset = layoutTopInset + 63.0 let listTopInset = layoutTopInset + topPanelHeight
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight) let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
insets.top = max(0.0, listSize.height - 44.0 - floor(56.0 * 3.5)) transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + (self.topInset ?? listSize.height)), size: listSize))
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset), size: listSize))
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition) let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve) let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
@ -1671,7 +1725,7 @@ public final class VoiceChatController: ViewController {
let topPanelFrame = self.topPanelNode.view.convert(self.topPanelNode.bounds, to: self.view) let topPanelFrame = self.topPanelNode.view.convert(self.topPanelNode.bounds, to: self.view)
self.contentContainer.layer.animateBoundsOriginYAdditive(from: self.contentContainer.bounds.origin.y, to: -(layout.size.height - topPanelFrame.minY), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in self.contentContainer.layer.animateBoundsOriginYAdditive(from: self.contentContainer.bounds.origin.y, to: -(layout.size.height - topPanelFrame.minY) - 44.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
offsetCompleted = true offsetCompleted = true
internalCompletion() internalCompletion()
}) })
@ -1688,15 +1742,18 @@ public final class VoiceChatController: ViewController {
} }
} }
private var topInset: CGFloat?
private var isFirstTime = true private var isFirstTime = true
private func dequeueTransition() { private func dequeueTransition() {
guard let _ = self.validLayout, let transition = self.enqueuedTransitions.first else { guard let (layout, _) = self.validLayout, let transition = self.enqueuedTransitions.first else {
return return
} }
self.enqueuedTransitions.remove(at: 0) self.enqueuedTransitions.remove(at: 0)
var options = ListViewDeleteAndInsertOptions() var options = ListViewDeleteAndInsertOptions()
if !isFirstTime { if self.isFirstTime {
self.isFirstTime = false
} else {
if transition.crossFade { if transition.crossFade {
options.insert(.AnimateCrossfade) options.insert(.AnimateCrossfade)
} }
@ -1707,13 +1764,38 @@ public final class VoiceChatController: ViewController {
options.insert(.LowLatency) options.insert(.LowLatency)
options.insert(.PreferSynchronousResourceLoading) options.insert(.PreferSynchronousResourceLoading)
var scrollToItem: ListViewScrollToItem? var itemsHeight: CGFloat = 46.0 + CGFloat(transition.count - 1) * 56.0
if self.isFirstTime {
self.isFirstTime = false let bottomAreaHeight: CGFloat = 268.0
scrollToItem = ListViewScrollToItem(index: 0, position: .bottom(0), animated: false, curve: .Default(duration: nil), directionHint: .Up) let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
}
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in let sideInset: CGFloat = 16.0
var insets = UIEdgeInsets()
insets.left = layout.safeInsets.left + sideInset
insets.right = layout.safeInsets.right + sideInset
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
let listTopInset = layoutTopInset + 63.0
let listSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
let previousIsExpanded = self.currentIsExpanded
self.currentIsExpanded = transition.isExpanded
self.topInset = max(0.0, transition.isExpanded ? 0.0 : max(listSize.height - itemsHeight, listSize.height - 46.0 - floor(56.0 * 3.5)))
let frameTransition: ContainedViewLayoutTransition
if previousIsExpanded != self.currentIsExpanded {
frameTransition = .animated(duration: 0.4, curve: .spring)
} else {
frameTransition = .animated(duration: 0.4, curve: .easeInOut)
}
frameTransition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + (self.topInset ?? listSize.height)), size: listSize))
let (duration, curve) = listViewAnimationDurationAndCurve(transition: frameTransition)
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listSize, insets: insets, duration: duration, curve: curve)
self.updateColors(fullscreen: transition.isExpanded)
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, updateOpaqueState: nil, completion: { [weak self] _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1722,9 +1804,12 @@ public final class VoiceChatController: ViewController {
strongSelf.controller?.contentsReady.set(true) strongSelf.controller?.contentsReady.set(true)
} }
}) })
if previousIsExpanded != self.currentIsExpanded {
self.updateFloatingHeaderOffset(offset: self.currentContentOffset ?? 0.0, transition: frameTransition)
}
} }
private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: [GroupCallParticipantsContext.Participant], invitedPeers: [Peer], speakingPeers: Set<PeerId>) { private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: [GroupCallParticipantsContext.Participant], invitedPeers: [Peer], speakingPeers: Set<PeerId>, isExpanded: Bool) {
self.currentCallMembers = callMembers self.currentCallMembers = callMembers
self.currentSpeakingPeers = speakingPeers self.currentSpeakingPeers = speakingPeers
self.currentInvitedPeers = invitedPeers self.currentInvitedPeers = invitedPeers
@ -1789,7 +1874,7 @@ public final class VoiceChatController: ViewController {
self.currentEntries = entries self.currentEntries = entries
let presentationData = self.presentationData.withUpdated(theme: self.darkTheme) let presentationData = self.presentationData.withUpdated(theme: self.darkTheme)
let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!) let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!, isExpanded: isExpanded ?? self.currentIsExpanded)
self.enqueueTransition(transition) self.enqueueTransition(transition)
} }

View File

@ -56,20 +56,24 @@ public final class VoiceChatOverlayController: ViewController {
transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint(x: slideOffset, y: 0.0)) transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint(x: slideOffset, y: 0.0))
} else { } else {
actionButton.layer.removeAllAnimations() actionButton.layer.removeAllAnimations()
actionButton.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak actionButton] _ in actionButton.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak actionButton] finished in
actionButton?.isHidden = true if finished {
actionButton?.isHidden = true
}
}) })
} }
} else { } else {
actionButton.isHidden = false
if slide { if slide {
transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint()) transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint())
} else { } else {
actionButton.layer.removeAllAnimations() actionButton.layer.removeAllAnimations()
actionButton.isHidden = false
actionButton.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4) actionButton.layer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4)
} }
} }
} else { } else {
actionButton.isHidden = hidden
actionButton.layer.removeAllAnimations()
if hidden { if hidden {
if slide { if slide {
actionButton.layer.sublayerTransform = CATransform3DMakeTranslation(slideOffset, 0.0, 0.0) actionButton.layer.sublayerTransform = CATransform3DMakeTranslation(slideOffset, 0.0, 0.0)

View File

@ -18,8 +18,8 @@ import AccountContext
import LegacyComponents import LegacyComponents
import AudioBlob import AudioBlob
public final class VoiceChatParticipantItem: ListViewItem { final class VoiceChatParticipantItem: ListViewItem {
public enum ParticipantText { enum ParticipantText {
public enum TextColor { public enum TextColor {
case generic case generic
case accent case accent
@ -31,25 +31,25 @@ public final class VoiceChatParticipantItem: ListViewItem {
case none case none
} }
public enum Icon { enum Icon {
case none case none
case microphone(Bool, UIColor) case microphone(Bool, UIColor)
case invite(Bool) case invite(Bool)
} }
public struct RevealOption { struct RevealOption {
public enum RevealOptionType { enum RevealOptionType {
case neutral case neutral
case warning case warning
case destructive case destructive
case accent case accent
} }
public var type: RevealOptionType var type: RevealOptionType
public var title: String var title: String
public var action: () -> Void var action: () -> Void
public init(type: RevealOptionType, title: String, action: @escaping () -> Void) { init(type: RevealOptionType, title: String, action: @escaping () -> Void) {
self.type = type self.type = type
self.title = title self.title = title
self.action = action self.action = action
@ -138,7 +138,7 @@ public final class VoiceChatParticipantItem: ListViewItem {
private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0)) private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0))
public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode { class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
private let topStripeNode: ASDisplayNode private let topStripeNode: ASDisplayNode
private let bottomStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode
@ -170,7 +170,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
private var peerPresenceManager: PeerPresenceStatusManager? private var peerPresenceManager: PeerPresenceStatusManager?
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)? private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
public init() { init() {
self.topStripeNode = ASDisplayNode() self.topStripeNode = ASDisplayNode()
self.topStripeNode.isLayerBacked = true self.topStripeNode.isLayerBacked = true
@ -281,7 +281,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
self.audioLevelDisposable.dispose() self.audioLevelDisposable.dispose()
} }
public func asyncLayout() -> (_ item: VoiceChatParticipantItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) { func asyncLayout() -> (_ item: VoiceChatParticipantItem, _ params: ListViewItemLayoutParams, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, (Bool, Bool) -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeStatusLayout = TextNode.asyncLayout(self.statusNode) let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
var currentDisabledOverlayNode = self.disabledOverlayNode var currentDisabledOverlayNode = self.disabledOverlayNode
@ -706,7 +706,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} }
} }
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated) super.setHighlighted(highlighted, at: point, animated: animated)
self.isHighlighted = highlighted self.isHighlighted = highlighted
@ -714,20 +714,19 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
self.updateIsHighlighted(transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate) self.updateIsHighlighted(transition: (animated && !highlighted) ? .animated(duration: 0.3, curve: .easeInOut) : .immediate)
} }
override public func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
} }
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) { override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
} }
override func header() -> ListViewItemHeader? {
override public func header() -> ListViewItemHeader? {
return nil return nil
} }
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
var rect = rect var rect = rect
rect.origin.y += self.insets.top rect.origin.y += self.insets.top
self.absoluteLocation = (rect, containerSize) self.absoluteLocation = (rect, containerSize)
@ -739,7 +738,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} }
} }
override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition) super.updateRevealOffset(offset: offset, transition: transition)
if let _ = self.layoutParams?.0, let params = self.layoutParams?.1 { if let _ = self.layoutParams?.0, let params = self.layoutParams?.1 {
@ -761,19 +760,19 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
} }
} }
override public func revealOptionsInteractivelyOpened() { override func revealOptionsInteractivelyOpened() {
if let item = self.layoutParams?.0 { if let item = self.layoutParams?.0 {
item.setPeerIdWithRevealedOptions(item.peer.id, nil) item.setPeerIdWithRevealedOptions(item.peer.id, nil)
} }
} }
override public func revealOptionsInteractivelyClosed() { override func revealOptionsInteractivelyClosed() {
if let item = self.layoutParams?.0 { if let item = self.layoutParams?.0 {
item.setPeerIdWithRevealedOptions(nil, item.peer.id) item.setPeerIdWithRevealedOptions(nil, item.peer.id)
} }
} }
override public func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
if let item = self.layoutParams?.0 { if let item = self.layoutParams?.0 {
item.revealOptions[Int(option.key)].action() item.revealOptions[Int(option.key)].action()
} }
@ -781,4 +780,8 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
self.setRevealOptionsOpened(false, animated: true) self.setRevealOptionsOpened(false, animated: true)
self.revealOptionsInteractivelyClosed() self.revealOptionsInteractivelyClosed()
} }
override var preferredAnimationCurve: (CGFloat) -> CGFloat {
return listViewAnimationCurveEaseInOut
}
} }

View File

@ -614,12 +614,18 @@ public final class GroupCallParticipantsContext {
public enum Update { public enum Update {
public struct StateUpdate { public struct StateUpdate {
public struct ParticipantUpdate { public struct ParticipantUpdate {
public enum ParticipationStatusChange {
case none
case joined
case left
}
public var peerId: PeerId public var peerId: PeerId
public var ssrc: UInt32 public var ssrc: UInt32
public var joinTimestamp: Int32 public var joinTimestamp: Int32
public var activityTimestamp: Double? public var activityTimestamp: Double?
public var muteState: Participant.MuteState? public var muteState: Participant.MuteState?
public var isRemoved: Bool public var participationStatusChange: ParticipationStatusChange
} }
public var participantUpdates: [ParticipantUpdate] public var participantUpdates: [ParticipantUpdate]
@ -911,7 +917,7 @@ public final class GroupCallParticipantsContext {
var updatedTotalCount = strongSelf.stateValue.state.totalCount var updatedTotalCount = strongSelf.stateValue.state.totalCount
for participantUpdate in update.participantUpdates { for participantUpdate in update.participantUpdates {
if participantUpdate.isRemoved { if case .left = participantUpdate.participationStatusChange {
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) { if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) {
updatedParticipants.remove(at: index) updatedParticipants.remove(at: index)
updatedTotalCount = max(0, updatedTotalCount - 1) updatedTotalCount = max(0, updatedTotalCount - 1)
@ -927,7 +933,7 @@ public final class GroupCallParticipantsContext {
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) { if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) {
previousActivityTimestamp = updatedParticipants[index].activityTimestamp previousActivityTimestamp = updatedParticipants[index].activityTimestamp
updatedParticipants.remove(at: index) updatedParticipants.remove(at: index)
} else { } else if case .left = participantUpdate.participationStatusChange {
updatedTotalCount += 1 updatedTotalCount += 1
} }
@ -1107,13 +1113,24 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute) muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
} }
let isRemoved = (flags & (1 << 1)) != 0 let isRemoved = (flags & (1 << 1)) != 0
let justJoined = (flags & (1 << 4)) != 0
let participationStatusChange: GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate.ParticipationStatusChange
if isRemoved {
participationStatusChange = .left
} else if justJoined {
participationStatusChange = .joined
} else {
participationStatusChange = .none
}
self.init( self.init(
peerId: peerId, peerId: peerId,
ssrc: ssrc, ssrc: ssrc,
joinTimestamp: date, joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init), activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState, muteState: muteState,
isRemoved: isRemoved participationStatusChange: participationStatusChange
) )
} }
} }
@ -1133,13 +1150,24 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute) muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
} }
let isRemoved = (flags & (1 << 1)) != 0 let isRemoved = (flags & (1 << 1)) != 0
let justJoined = (flags & (1 << 4)) != 0
let participationStatusChange: GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate.ParticipationStatusChange
if isRemoved {
participationStatusChange = .left
} else if justJoined {
participationStatusChange = .joined
} else {
participationStatusChange = .none
}
participantUpdates.append(GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate( participantUpdates.append(GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate(
peerId: peerId, peerId: peerId,
ssrc: ssrc, ssrc: ssrc,
joinTimestamp: date, joinTimestamp: date,
activityTimestamp: activeDate.flatMap(Double.init), activityTimestamp: activeDate.flatMap(Double.init),
muteState: muteState, muteState: muteState,
isRemoved: isRemoved participationStatusChange: participationStatusChange
)) ))
} }
} }

View File

@ -7071,7 +7071,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let controller = voiceChatOverlayController { if let controller = voiceChatOverlayController {
var hidden = false var hidden = false
if self.presentationInterfaceState.interfaceState.editMessage != nil || self.presentationInterfaceState.interfaceState.composeInputState.inputText.string.count > 0 { if self.presentationInterfaceState.interfaceState.editMessage != nil || self.presentationInterfaceState.interfaceState.forwardMessageIds != nil || self.presentationInterfaceState.interfaceState.composeInputState.inputText.string.count > 0 {
hidden = true hidden = true
} }
controller.update(hidden: hidden, slide: false, animated: true) controller.update(hidden: hidden, slide: false, animated: true)

View File

@ -12,6 +12,7 @@ import AccountContext
import StickerResources import StickerResources
import ContextUI import ContextUI
import Markdown import Markdown
import ShimmerEffect
private let nameFont = Font.medium(14.0) private let nameFont = Font.medium(14.0)
private let inlineBotPrefixFont = Font.regular(14.0) private let inlineBotPrefixFont = Font.regular(14.0)
@ -21,6 +22,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
private let contextSourceNode: ContextExtractedContentContainingNode private let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode private let containerNode: ContextControllerSourceNode
let imageNode: TransformImageNode let imageNode: TransformImageNode
private var placeholderNode: StickerShimmerEffectNode?
var textNode: TextNode? var textNode: TextNode?
private var swipeToReplyNode: ChatMessageSwipeToReplyNode? private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
@ -50,6 +52,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
self.contextSourceNode = ContextExtractedContentContainingNode() self.contextSourceNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode() self.containerNode = ContextControllerSourceNode()
self.imageNode = TransformImageNode() self.imageNode = TransformImageNode()
self.placeholderNode = StickerShimmerEffectNode()
self.placeholderNode?.isUserInteractionEnabled = false
self.dateAndStatusNode = ChatMessageDateAndStatusNode() self.dateAndStatusNode = ChatMessageDateAndStatusNode()
super.init(layerBacked: false) super.init(layerBacked: false)
@ -96,6 +100,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
self.containerNode.addSubnode(self.contextSourceNode) self.containerNode.addSubnode(self.contextSourceNode)
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
self.addSubnode(self.containerNode) self.addSubnode(self.containerNode)
if let placeholderNode = self.placeholderNode {
self.contextSourceNode.contentNode.addSubnode(placeholderNode)
}
self.contextSourceNode.contentNode.addSubnode(self.imageNode) self.contextSourceNode.contentNode.addSubnode(self.imageNode)
self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode) self.contextSourceNode.contentNode.addSubnode(self.dateAndStatusNode)
@ -115,6 +122,21 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
self.fetchDisposable.dispose() self.fetchDisposable.dispose()
} }
private func removePlaceholder(animated: Bool) {
if let placeholderNode = self.placeholderNode {
self.placeholderNode = nil
if !animated {
placeholderNode.removeFromSupernode()
} else {
placeholderNode.alpha = 0.0
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
placeholderNode?.removeFromSupernode()
})
}
}
}
override func didLoad() { override func didLoad() {
super.didLoad() super.didLoad()

View File

@ -293,6 +293,7 @@ private func channelRecentActionsFilterControllerEntries(presentationData: Prese
([.editMessages], presentationData.strings.Channel_AdminLogFilter_EventsEditedMessages), ([.editMessages], presentationData.strings.Channel_AdminLogFilter_EventsEditedMessages),
([.pinnedMessages], presentationData.strings.Channel_AdminLogFilter_EventsPinned), ([.pinnedMessages], presentationData.strings.Channel_AdminLogFilter_EventsPinned),
([.leave], presentationData.strings.Channel_AdminLogFilter_EventsLeaving), ([.leave], presentationData.strings.Channel_AdminLogFilter_EventsLeaving),
([.calls], presentationData.strings.Channel_AdminLogFilter_EventsCalls)
] ]
} else { } else {
order = [ order = [

View File

@ -559,6 +559,11 @@ public final class OngoingCallContext {
return self.receptionPromise.get() return self.receptionPromise.get()
} }
private let audioLevelPromise = Promise<Float>(0.0)
public var audioLevel: Signal<Float, NoError> {
return self.audioLevelPromise.get()
}
private let audioSessionDisposable = MetaDisposable() private let audioSessionDisposable = MetaDisposable()
private var networkTypeDisposable: Disposable? private var networkTypeDisposable: Disposable?
@ -687,6 +692,9 @@ public final class OngoingCallContext {
context.signalBarsChanged = { signalBars in context.signalBarsChanged = { signalBars in
self?.receptionPromise.set(.single(signalBars)) self?.receptionPromise.set(.single(signalBars))
} }
context.audioLevelUpdated = { level in
self?.audioLevelPromise.set(.single(level))
}
strongSelf.networkTypeDisposable = (updatedNetworkType strongSelf.networkTypeDisposable = (updatedNetworkType
|> deliverOn(queue)).start(next: { networkType in |> deliverOn(queue)).start(next: { networkType in

View File

@ -127,6 +127,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc, OngoingCallRemoteAudioStateWebrtc, OngoingCallRemoteBatteryLevelWebrtc, float); @property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc, OngoingCallRemoteAudioStateWebrtc, OngoingCallRemoteBatteryLevelWebrtc, float);
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); @property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
@property (nonatomic, copy) void (^ _Nullable audioLevelUpdated)(float);
- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P allowTCP:(BOOL)allowTCP enableStunMarking:(BOOL)enableStunMarking logPath:(NSString * _Nonnull)logPath statsLogPath:(NSString * _Nonnull)statsLogPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec audioInputDeviceId: (NSString * _Nonnull)audioInputDeviceId; - (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P allowTCP:(BOOL)allowTCP enableStunMarking:(BOOL)enableStunMarking logPath:(NSString * _Nonnull)logPath statsLogPath:(NSString * _Nonnull)statsLogPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec audioInputDeviceId: (NSString * _Nonnull)audioInputDeviceId;

View File

@ -463,6 +463,16 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
} }
}]; }];
}, },
.audioLevelUpdated = [weakSelf, queue](float level) {
[queue dispatch:^{
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
if (strongSelf) {
if (strongSelf->_audioLevelUpdated) {
strongSelf->_audioLevelUpdated(level);
}
}
}];
},
.remoteMediaStateUpdated = [weakSelf, queue](tgcalls::AudioState audioState, tgcalls::VideoState videoState) { .remoteMediaStateUpdated = [weakSelf, queue](tgcalls::AudioState audioState, tgcalls::VideoState videoState) {
[queue dispatch:^{ [queue dispatch:^{
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;

@ -1 +1 @@
Subproject commit 58f3f89352d164540067e197ad735c0717546172 Subproject commit 6ae73f4c388854d86a0ce66bf243561a11d9e719