mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
ea20efe2e4
@ -2687,6 +2687,7 @@ Unused sets are archived when you add more.";
|
||||
"Channel.AdminLogFilter.EventsEditedMessages" = "Edited Messages";
|
||||
"Channel.AdminLogFilter.EventsPinned" = "Pinned Messages";
|
||||
"Channel.AdminLogFilter.EventsLeaving" = "Members Removed";
|
||||
"Channel.AdminLogFilter.EventsCalls" = "Vocie Chats";
|
||||
"Channel.AdminLogFilter.AdminsTitle" = "ADMINS";
|
||||
"Channel.AdminLogFilter.AdminsAll" = "All Admins";
|
||||
|
||||
|
@ -132,6 +132,7 @@ public protocol PresentationCall: class {
|
||||
var peer: Peer? { get }
|
||||
|
||||
var state: Signal<PresentationCallState, NoError> { get }
|
||||
var audioLevel: Signal<Float, NoError> { get }
|
||||
|
||||
var isMuted: Signal<Bool, NoError> { get }
|
||||
|
||||
|
@ -93,6 +93,10 @@ public let listViewAnimationCurveLinear: (CGFloat) -> CGFloat = { t in
|
||||
return t
|
||||
}
|
||||
|
||||
public let listViewAnimationCurveEaseInOut: (CGFloat) -> CGFloat = { t in
|
||||
return bezierPoint(0.42, 0.0, 0.58, 1.0, t)
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIView.AnimationOptions) -> (CGFloat) -> CGFloat {
|
||||
if animationOptions.rawValue == UInt(7 << 16) {
|
||||
|
@ -119,6 +119,10 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
||||
|
||||
final let wantsScrollDynamics: Bool
|
||||
|
||||
open var preferredAnimationCurve: (CGFloat) -> CGFloat {
|
||||
return listViewAnimationCurveSystem
|
||||
}
|
||||
|
||||
public final var wantsTrailingItemSpaceUpdates: Bool = false
|
||||
|
||||
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) {
|
||||
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 {
|
||||
let frame = strongSelf.frame
|
||||
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) {
|
||||
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 {
|
||||
strongSelf.apparentHeight = currentValue
|
||||
if let update = update {
|
||||
@ -494,7 +498,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
||||
}
|
||||
|
||||
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 {
|
||||
strongSelf.transitionOffset = currentValue
|
||||
}
|
||||
|
@ -528,7 +528,18 @@
|
||||
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;
|
||||
|
||||
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:composition];
|
||||
@ -543,9 +554,6 @@
|
||||
return [context addImageGenerator:imageGenerator];
|
||||
}];
|
||||
|
||||
*outputSettings = [TGMediaVideoConversionPresetSettings videoSettingsForPreset:preset dimensions:outputDimensions];
|
||||
*dimensions = outputDimensions;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
@ -1315,6 +1323,14 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
|
||||
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 @
|
||||
{
|
||||
AVVideoCodecKey: AVVideoCodecH264,
|
||||
@ -1322,6 +1338,15 @@ static CGFloat progressOfSampleBufferInTimeRange(CMSampleBufferRef sampleBuffer,
|
||||
AVVideoWidthKey: @((NSInteger)dimensions.width),
|
||||
AVVideoHeightKey: @((NSInteger)dimensions.height)
|
||||
};
|
||||
#endif
|
||||
return @
|
||||
{
|
||||
AVVideoCodecKey: AVVideoCodecH264,
|
||||
AVVideoCompressionPropertiesKey: codecSettings,
|
||||
AVVideoWidthKey: @((NSInteger)dimensions.width),
|
||||
AVVideoHeightKey: @((NSInteger)dimensions.height),
|
||||
AVVideoColorPropertiesKey: hdVideoProperties
|
||||
};
|
||||
}
|
||||
|
||||
+ (NSInteger)_videoBitrateKbpsForPreset:(TGMediaVideoConversionPreset)preset
|
||||
|
@ -14,6 +14,7 @@ import AnimatedCountLabelNode
|
||||
private let blue = UIColor(rgb: 0x0078ff)
|
||||
private let lightBlue = UIColor(rgb: 0x59c7f8)
|
||||
private let green = UIColor(rgb: 0x33c659)
|
||||
private let activeBlue = UIColor(rgb: 0x00a0b9)
|
||||
|
||||
private class CallStatusBarBackgroundNode: ASDisplayNode {
|
||||
private let foregroundView: UIView
|
||||
@ -46,7 +47,7 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
||||
let initialColors = self.foregroundGradientLayer.colors
|
||||
let targetColors: [CGColor]
|
||||
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 {
|
||||
targetColors = [connectingColor.cgColor, connectingColor.cgColor]
|
||||
}
|
||||
@ -159,6 +160,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
private let backgroundNode: CallStatusBarBackgroundNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let subtitleNode: ImmediateAnimatedCountLabelNode
|
||||
private let speakerNode: ImmediateTextNode
|
||||
|
||||
private let audioLevelDisposable = MetaDisposable()
|
||||
private let stateDisposable = MetaDisposable()
|
||||
@ -175,6 +177,7 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
private var currentCallState: PresentationCallState?
|
||||
private var currentGroupCallState: PresentationGroupCallSummaryState?
|
||||
private var currentIsMuted = true
|
||||
private var currentMembers: PresentationGroupCallMembers?
|
||||
private var currentIsConnected = true
|
||||
|
||||
public override init() {
|
||||
@ -182,12 +185,14 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.subtitleNode = ImmediateAnimatedCountLabelNode()
|
||||
self.subtitleNode.reverseAnimationDirection = true
|
||||
self.speakerNode = ImmediateTextNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.subtitleNode)
|
||||
self.addSubnode(self.speakerNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -244,6 +249,13 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
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):
|
||||
self.presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationDataDisposable.set((sharedContext.presentationData
|
||||
@ -257,16 +269,20 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
(combineLatest(
|
||||
account.postbox.peerView(id: call.peerId),
|
||||
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 {
|
||||
strongSelf.currentPeer = view.peers[view.peerId]
|
||||
strongSelf.currentGroupCallState = state
|
||||
strongSelf.currentMembers = members
|
||||
|
||||
var isMuted = isMuted
|
||||
if let state = state, state.callState.muteState != nil {
|
||||
isMuted = true
|
||||
if let state = state, let muteState = state.callState.muteState {
|
||||
if !muteState.canUnmute {
|
||||
isMuted = true
|
||||
}
|
||||
}
|
||||
strongSelf.currentIsMuted = isMuted
|
||||
|
||||
@ -287,17 +303,18 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
return
|
||||
}
|
||||
var effectiveLevel: Float = 0.0
|
||||
var audioLevels = audioLevels
|
||||
if !strongSelf.currentIsMuted {
|
||||
effectiveLevel = myAudioLevel
|
||||
} else {
|
||||
effectiveLevel = audioLevels.map { $0.1 }.max() ?? 0.0
|
||||
audioLevels.append((PeerId(0), myAudioLevel, true))
|
||||
}
|
||||
effectiveLevel = audioLevels.map { $0.1 }.max() ?? 0.0
|
||||
strongSelf.backgroundNode.audioLevel = effectiveLevel
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
var title: String = ""
|
||||
var speakerSubtitle: String = ""
|
||||
|
||||
let textFont = Font.regular(13.0)
|
||||
let textColor = UIColor.white
|
||||
@ -314,6 +331,21 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
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 {
|
||||
var membersPart = presentationData.strings.VoiceChat_Status_Members(membersCount)
|
||||
if let startIndex = membersPart.firstIndex(of: "["), let endIndex = membersPart.firstIndex(of: "]") {
|
||||
@ -364,15 +396,24 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
self.backgroundNode.connectingColor = color
|
||||
}
|
||||
|
||||
if self.subtitleNode.segments != segments {
|
||||
if self.subtitleNode.segments != segments && speakerSubtitle.isEmpty {
|
||||
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)
|
||||
|
||||
|
||||
if !speakerSubtitle.isEmpty {
|
||||
self.speakerNode.attributedText = NSAttributedString(string: speakerSubtitle, font: Font.regular(13.0), textColor: .white)
|
||||
}
|
||||
|
||||
let spacing: CGFloat = 5.0
|
||||
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 speakerSize = self.speakerNode.updateLayout(CGSize(width: 160.0, height: size.height))
|
||||
|
||||
let totalWidth = titleSize.width + spacing + subtitleSize.width
|
||||
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.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.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 18.0))
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ final class PresentationCallToneRenderer {
|
||||
var takenCount = 0
|
||||
while takenCount < frameSize {
|
||||
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)")
|
||||
memcpy(bytes.advanced(by: takenCount), dataBytes.advanced(by: dataOffset), dataCount)
|
||||
takenCount += dataCount
|
||||
@ -200,6 +200,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
private var requestedVideoAspect: Float?
|
||||
private var reception: Int32?
|
||||
private var receptionDisposable: Disposable?
|
||||
private var audioLevelDisposable: Disposable?
|
||||
private var reportedIncomingCall = false
|
||||
|
||||
private var batteryLevelDisposable: Disposable?
|
||||
@ -219,6 +220,11 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
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 var isMutedValue = false
|
||||
public var isMuted: Signal<Bool, NoError> {
|
||||
@ -436,6 +442,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
self.sessionStateDisposable?.dispose()
|
||||
self.ongoingContextStateDisposable?.dispose()
|
||||
self.receptionDisposable?.dispose()
|
||||
self.audioLevelDisposable?.dispose()
|
||||
self.batteryLevelDisposable?.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> {
|
||||
return Signal { subscriber in
|
||||
let device = UIDevice.current
|
||||
|
@ -578,7 +578,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
switch update {
|
||||
case let .state(update):
|
||||
for participantUpdate in update.participantUpdates {
|
||||
if participantUpdate.isRemoved {
|
||||
if case .left = participantUpdate.participationStatusChange {
|
||||
removedSsrc.append(participantUpdate.ssrc)
|
||||
|
||||
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 {
|
||||
strongSelf._canBeRemoved.set(.single(true))
|
||||
}
|
||||
} else if case .joined = participantUpdate.participationStatusChange {
|
||||
}
|
||||
}
|
||||
case let .call(isTerminated, _):
|
||||
|
@ -14,6 +14,7 @@ private let secondaryGreyColor = UIColor(rgb: 0x1c1c1e)
|
||||
private let blue = UIColor(rgb: 0x0078ff)
|
||||
private let lightBlue = UIColor(rgb: 0x59c7f8)
|
||||
private let green = UIColor(rgb: 0x33c659)
|
||||
private let activeBlue = UIColor(rgb: 0x00a0b9)
|
||||
|
||||
private let areaSize = CGSize(width: 440.0, height: 440.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)
|
||||
self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconSize)
|
||||
self.iconNode.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
|
||||
self.wasActiveWhenPressed = false
|
||||
}
|
||||
|
||||
private func applyIconParams() {
|
||||
@ -520,8 +519,10 @@ private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||
} else {
|
||||
let previousValue = self.foregroundGradientLayer.startPoint
|
||||
let newValue: CGPoint
|
||||
if self.maskBlobView.presentationAudioLevel > 0.15 {
|
||||
newValue = CGPoint(x: CGFloat.random(in: 0.8 ..< 1.0), y: CGFloat.random(in: 0.1 ..< 0.45))
|
||||
if self.maskBlobView.presentationAudioLevel > 0.22 {
|
||||
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 {
|
||||
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
|
||||
if let active = active {
|
||||
if active {
|
||||
targetColors = [blue.cgColor, green.cgColor]
|
||||
targetColors = [activeBlue.cgColor, green.cgColor]
|
||||
targetScale = 0.89
|
||||
outerColor = UIColor(rgb: 0x21674f)
|
||||
} else {
|
||||
|
@ -257,4 +257,8 @@ class VoiceChatActionItemNode: ListViewItemNode {
|
||||
override public func header() -> ListViewItemHeader? {
|
||||
return nil
|
||||
}
|
||||
|
||||
override var preferredAnimationCurve: (CGFloat) -> CGFloat {
|
||||
return listViewAnimationCurveEaseInOut
|
||||
}
|
||||
}
|
||||
|
@ -127,6 +127,8 @@ public final class VoiceChatController: ViewController {
|
||||
let isLoading: Bool
|
||||
let isEmpty: Bool
|
||||
let crossFade: Bool
|
||||
let count: Int
|
||||
let isExpanded: 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 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 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?
|
||||
@ -412,6 +414,9 @@ public final class VoiceChatController: ViewController {
|
||||
private var currentCallMembers: [GroupCallParticipantsContext.Participant]?
|
||||
private var currentInvitedPeers: [Peer]?
|
||||
private var currentSpeakingPeers: Set<PeerId>?
|
||||
private var currentIsExpanded: Bool = false
|
||||
private var currentContentOffset: CGFloat?
|
||||
private var ignoreScrolling = false
|
||||
private var accountPeer: Peer?
|
||||
private var currentAudioButtonColor: UIColor?
|
||||
|
||||
@ -466,12 +471,11 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.backgroundColor = secondaryPanelBackgroundColor
|
||||
self.backgroundNode.clipsToBounds = false
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3)
|
||||
self.listNode.clipsToBounds = true
|
||||
self.listNode.stackFromBottom = true
|
||||
self.listNode.keepMinimalScrollHeightWithTopInset = 0
|
||||
|
||||
self.topPanelNode = ASDisplayNode()
|
||||
self.topPanelNode.clipsToBounds = false
|
||||
@ -482,8 +486,11 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.topPanelEdgeNode = ASDisplayNode()
|
||||
self.topPanelEdgeNode.backgroundColor = panelBackgroundColor
|
||||
self.topPanelEdgeNode.layer.cornerRadius = 12.0
|
||||
self.topPanelEdgeNode.cornerRadius = 12.0
|
||||
self.topPanelEdgeNode.isUserInteractionEnabled = false
|
||||
if #available(iOS 11.0, *) {
|
||||
self.topPanelEdgeNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
}
|
||||
|
||||
self.optionsButton = VoiceChatHeaderButton()
|
||||
self.optionsButton.setImage(optionsButtonImage(dark: false))
|
||||
@ -516,10 +523,12 @@ public final class VoiceChatController: ViewController {
|
||||
self.leftBorderNode = ASDisplayNode()
|
||||
self.leftBorderNode.backgroundColor = panelBackgroundColor
|
||||
self.leftBorderNode.isUserInteractionEnabled = false
|
||||
self.leftBorderNode.clipsToBounds = false
|
||||
|
||||
self.rightBorderNode = ASDisplayNode()
|
||||
self.rightBorderNode.backgroundColor = panelBackgroundColor
|
||||
self.rightBorderNode.isUserInteractionEnabled = false
|
||||
self.rightBorderNode.clipsToBounds = false
|
||||
|
||||
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)))
|
||||
strongSelf.currentSubtitle = subtitle
|
||||
@ -898,7 +907,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
if !strongSelf.didSetDataReady {
|
||||
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 {
|
||||
let addressName = channel.addressName ?? ""
|
||||
@ -1090,6 +1099,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in
|
||||
if let strongSelf = self {
|
||||
strongSelf.currentContentOffset = offset
|
||||
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
|
||||
}
|
||||
}
|
||||
@ -1098,10 +1108,27 @@ public final class VoiceChatController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
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):
|
||||
if value <= -10.0 {
|
||||
// strongSelf.controller?.dismiss()
|
||||
// strongSelf.updateFloatingHeaderOffset(offset: -value + strongSelf.listNode.insets.top, transition: strongSelf.listNode.isTracking ? .immediate : .animated(duration: 0.4, curve: .linear))
|
||||
|
||||
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:
|
||||
break
|
||||
@ -1196,7 +1223,7 @@ public final class VoiceChatController: ViewController {
|
||||
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) {
|
||||
@ -1241,7 +1268,7 @@ public final class VoiceChatController: ViewController {
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
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:
|
||||
break
|
||||
}
|
||||
@ -1315,82 +1342,82 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
guard let (validLayout, _) = self.validLayout else {
|
||||
guard let (layout, _) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
self.floatingHeaderOffset = offset
|
||||
|
||||
let layoutTopInset: CGFloat = max(validLayout.statusBarHeight ?? 0.0, validLayout.safeInsets.top)
|
||||
|
||||
|
||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||
let topPanelHeight: CGFloat = 63.0
|
||||
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 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 previousBackgroundFrame = self.backgroundNode.frame
|
||||
let previousLeftBorderFrame = self.leftBorderNode.frame
|
||||
let previousRightBorderFrame = self.rightBorderNode.frame
|
||||
|
||||
if !topPanelFrame.equalTo(previousTopPanelFrame) {
|
||||
self.topPanelNode.frame = topPanelFrame
|
||||
|
||||
let positionDelta = CGPoint(x: topPanelFrame.minX - previousTopPanelFrame.minX, y: topPanelFrame.minY - previousTopPanelFrame.minY)
|
||||
let positionDelta = CGPoint(x: 0.0, y: topPanelFrame.minY - previousTopPanelFrame.minY)
|
||||
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.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))
|
||||
|
||||
self.topPanelBackgroundNode.frame = CGRect(x: 0.0, y: topPanelHeight - 24.0, width: layout.size.width, height: 24.0)
|
||||
|
||||
var bottomEdge: CGFloat = 0.0
|
||||
self.listNode.forEachItemNode { itemNode in
|
||||
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 {
|
||||
bottomEdge = convertedFrame.maxY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let listMaxY = listTopInset + listSize.height
|
||||
if bottomEdge.isZero {
|
||||
bottomEdge = listMaxY
|
||||
}
|
||||
var bottomOffset: CGFloat = 0.0
|
||||
if bottomEdge < self.listNode.frame.maxY {
|
||||
bottomOffset = bottomEdge - self.listNode.frame.maxY
|
||||
if bottomEdge < listMaxY {
|
||||
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
|
||||
if !bottomCornersFrame.equalTo(previousBottomCornersFrame) {
|
||||
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)
|
||||
transition.animateOffsetAdditive(node: self.bottomCornersNode, offset: positionDelta.y)
|
||||
transition.animateOffsetAdditive(node: self.bottomPanelBackgroundNode, offset: positionDelta.y)
|
||||
}
|
||||
|
||||
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)
|
||||
let positionDelta = CGPoint(x: 0.0, y: previousBottomCornersFrame.minY - bottomCornersFrame.minY)
|
||||
transition.animatePositionAdditive(node: self.bottomCornersNode, offset: positionDelta)
|
||||
transition.animatePositionAdditive(node: self.bottomPanelBackgroundNode, offset: positionDelta)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1405,8 +1432,22 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .linear)
|
||||
|
||||
// transition.updateCornerRadius(node: self.topPanelEdgeNode, cornerRadius: fullscreen ? layout.deviceMetrics.screenCornerRadius : 12.0)
|
||||
transition.updateBackgroundColor(node: self.dimNode, color: fullscreen ? fullscreenBackgroundColor : dimColor)
|
||||
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)
|
||||
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.topPanelEdgeNode, color: fullscreen ? fullscreenBackgroundColor : panelBackgroundColor)
|
||||
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.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 listTopInset = layoutTopInset + 63.0
|
||||
let listTopInset = layoutTopInset + topPanelHeight
|
||||
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), size: listSize))
|
||||
|
||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset + (self.topInset ?? listSize.height)), size: listSize))
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
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)
|
||||
|
||||
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
|
||||
internalCompletion()
|
||||
})
|
||||
@ -1688,15 +1742,18 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private var topInset: CGFloat?
|
||||
private var isFirstTime = true
|
||||
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
|
||||
}
|
||||
self.enqueuedTransitions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
if !isFirstTime {
|
||||
if self.isFirstTime {
|
||||
self.isFirstTime = false
|
||||
} else {
|
||||
if transition.crossFade {
|
||||
options.insert(.AnimateCrossfade)
|
||||
}
|
||||
@ -1707,13 +1764,38 @@ public final class VoiceChatController: ViewController {
|
||||
options.insert(.LowLatency)
|
||||
options.insert(.PreferSynchronousResourceLoading)
|
||||
|
||||
var scrollToItem: ListViewScrollToItem?
|
||||
if self.isFirstTime {
|
||||
self.isFirstTime = false
|
||||
scrollToItem = ListViewScrollToItem(index: 0, position: .bottom(0), animated: false, curve: .Default(duration: nil), directionHint: .Up)
|
||||
}
|
||||
var itemsHeight: CGFloat = 46.0 + CGFloat(transition.count - 1) * 56.0
|
||||
|
||||
let bottomAreaHeight: CGFloat = 268.0
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -1722,9 +1804,12 @@ public final class VoiceChatController: ViewController {
|
||||
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.currentSpeakingPeers = speakingPeers
|
||||
self.currentInvitedPeers = invitedPeers
|
||||
@ -1789,7 +1874,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.currentEntries = entries
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -56,20 +56,24 @@ public final class VoiceChatOverlayController: ViewController {
|
||||
transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint(x: slideOffset, y: 0.0))
|
||||
} else {
|
||||
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?.isHidden = true
|
||||
actionButton.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { [weak actionButton] finished in
|
||||
if finished {
|
||||
actionButton?.isHidden = true
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
actionButton.isHidden = false
|
||||
if slide {
|
||||
transition.updateSublayerTransformOffset(layer: actionButton.layer, offset: CGPoint())
|
||||
} else {
|
||||
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)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
actionButton.isHidden = hidden
|
||||
actionButton.layer.removeAllAnimations()
|
||||
if hidden {
|
||||
if slide {
|
||||
actionButton.layer.sublayerTransform = CATransform3DMakeTranslation(slideOffset, 0.0, 0.0)
|
||||
|
@ -18,8 +18,8 @@ import AccountContext
|
||||
import LegacyComponents
|
||||
import AudioBlob
|
||||
|
||||
public final class VoiceChatParticipantItem: ListViewItem {
|
||||
public enum ParticipantText {
|
||||
final class VoiceChatParticipantItem: ListViewItem {
|
||||
enum ParticipantText {
|
||||
public enum TextColor {
|
||||
case generic
|
||||
case accent
|
||||
@ -31,25 +31,25 @@ public final class VoiceChatParticipantItem: ListViewItem {
|
||||
case none
|
||||
}
|
||||
|
||||
public enum Icon {
|
||||
enum Icon {
|
||||
case none
|
||||
case microphone(Bool, UIColor)
|
||||
case invite(Bool)
|
||||
}
|
||||
|
||||
public struct RevealOption {
|
||||
public enum RevealOptionType {
|
||||
struct RevealOption {
|
||||
enum RevealOptionType {
|
||||
case neutral
|
||||
case warning
|
||||
case destructive
|
||||
case accent
|
||||
}
|
||||
|
||||
public var type: RevealOptionType
|
||||
public var title: String
|
||||
public var action: () -> Void
|
||||
var type: RevealOptionType
|
||||
var title: String
|
||||
var action: () -> Void
|
||||
|
||||
public init(type: RevealOptionType, title: String, action: @escaping () -> Void) {
|
||||
init(type: RevealOptionType, title: String, action: @escaping () -> Void) {
|
||||
self.type = type
|
||||
self.title = title
|
||||
self.action = action
|
||||
@ -138,7 +138,7 @@ public final class VoiceChatParticipantItem: ListViewItem {
|
||||
|
||||
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 bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
@ -170,7 +170,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
||||
|
||||
public init() {
|
||||
init() {
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
@ -281,7 +281,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
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 makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||
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)
|
||||
|
||||
self.isHighlighted = highlighted
|
||||
@ -714,20 +714,19 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
override public func header() -> ListViewItemHeader? {
|
||||
override func header() -> ListViewItemHeader? {
|
||||
return nil
|
||||
}
|
||||
|
||||
override public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
var rect = rect
|
||||
rect.origin.y += self.insets.top
|
||||
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)
|
||||
|
||||
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 {
|
||||
item.setPeerIdWithRevealedOptions(item.peer.id, nil)
|
||||
}
|
||||
}
|
||||
|
||||
override public func revealOptionsInteractivelyClosed() {
|
||||
override func revealOptionsInteractivelyClosed() {
|
||||
if let item = self.layoutParams?.0 {
|
||||
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 {
|
||||
item.revealOptions[Int(option.key)].action()
|
||||
}
|
||||
@ -781,4 +780,8 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
self.setRevealOptionsOpened(false, animated: true)
|
||||
self.revealOptionsInteractivelyClosed()
|
||||
}
|
||||
|
||||
override var preferredAnimationCurve: (CGFloat) -> CGFloat {
|
||||
return listViewAnimationCurveEaseInOut
|
||||
}
|
||||
}
|
||||
|
@ -614,12 +614,18 @@ public final class GroupCallParticipantsContext {
|
||||
public enum Update {
|
||||
public struct StateUpdate {
|
||||
public struct ParticipantUpdate {
|
||||
public enum ParticipationStatusChange {
|
||||
case none
|
||||
case joined
|
||||
case left
|
||||
}
|
||||
|
||||
public var peerId: PeerId
|
||||
public var ssrc: UInt32
|
||||
public var joinTimestamp: Int32
|
||||
public var activityTimestamp: Double?
|
||||
public var muteState: Participant.MuteState?
|
||||
public var isRemoved: Bool
|
||||
public var participationStatusChange: ParticipationStatusChange
|
||||
}
|
||||
|
||||
public var participantUpdates: [ParticipantUpdate]
|
||||
@ -911,7 +917,7 @@ public final class GroupCallParticipantsContext {
|
||||
var updatedTotalCount = strongSelf.stateValue.state.totalCount
|
||||
|
||||
for participantUpdate in update.participantUpdates {
|
||||
if participantUpdate.isRemoved {
|
||||
if case .left = participantUpdate.participationStatusChange {
|
||||
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) {
|
||||
updatedParticipants.remove(at: index)
|
||||
updatedTotalCount = max(0, updatedTotalCount - 1)
|
||||
@ -927,7 +933,7 @@ public final class GroupCallParticipantsContext {
|
||||
if let index = updatedParticipants.firstIndex(where: { $0.peer.id == participantUpdate.peerId }) {
|
||||
previousActivityTimestamp = updatedParticipants[index].activityTimestamp
|
||||
updatedParticipants.remove(at: index)
|
||||
} else {
|
||||
} else if case .left = participantUpdate.participationStatusChange {
|
||||
updatedTotalCount += 1
|
||||
}
|
||||
|
||||
@ -1107,13 +1113,24 @@ extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate {
|
||||
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
|
||||
}
|
||||
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(
|
||||
peerId: peerId,
|
||||
ssrc: ssrc,
|
||||
joinTimestamp: date,
|
||||
activityTimestamp: activeDate.flatMap(Double.init),
|
||||
muteState: muteState,
|
||||
isRemoved: isRemoved
|
||||
participationStatusChange: participationStatusChange
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1133,13 +1150,24 @@ extension GroupCallParticipantsContext.Update.StateUpdate {
|
||||
muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: canUnmute)
|
||||
}
|
||||
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(
|
||||
peerId: peerId,
|
||||
ssrc: ssrc,
|
||||
joinTimestamp: date,
|
||||
activityTimestamp: activeDate.flatMap(Double.init),
|
||||
muteState: muteState,
|
||||
isRemoved: isRemoved
|
||||
participationStatusChange: participationStatusChange
|
||||
))
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -7071,7 +7071,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
if let controller = voiceChatOverlayController {
|
||||
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
|
||||
}
|
||||
controller.update(hidden: hidden, slide: false, animated: true)
|
||||
|
@ -12,6 +12,7 @@ import AccountContext
|
||||
import StickerResources
|
||||
import ContextUI
|
||||
import Markdown
|
||||
import ShimmerEffect
|
||||
|
||||
private let nameFont = Font.medium(14.0)
|
||||
private let inlineBotPrefixFont = Font.regular(14.0)
|
||||
@ -21,6 +22,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
private let contextSourceNode: ContextExtractedContentContainingNode
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
let imageNode: TransformImageNode
|
||||
private var placeholderNode: StickerShimmerEffectNode?
|
||||
var textNode: TextNode?
|
||||
|
||||
private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
|
||||
@ -50,6 +52,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.imageNode = TransformImageNode()
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
self.placeholderNode?.isUserInteractionEnabled = false
|
||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||
|
||||
super.init(layerBacked: false)
|
||||
@ -96,6 +100,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
self.containerNode.addSubnode(self.contextSourceNode)
|
||||
self.containerNode.targetNodeForActivationProgress = self.contextSourceNode.contentNode
|
||||
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.dateAndStatusNode)
|
||||
|
||||
@ -115,6 +122,21 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
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() {
|
||||
super.didLoad()
|
||||
|
||||
|
@ -293,6 +293,7 @@ private func channelRecentActionsFilterControllerEntries(presentationData: Prese
|
||||
([.editMessages], presentationData.strings.Channel_AdminLogFilter_EventsEditedMessages),
|
||||
([.pinnedMessages], presentationData.strings.Channel_AdminLogFilter_EventsPinned),
|
||||
([.leave], presentationData.strings.Channel_AdminLogFilter_EventsLeaving),
|
||||
([.calls], presentationData.strings.Channel_AdminLogFilter_EventsCalls)
|
||||
]
|
||||
} else {
|
||||
order = [
|
||||
|
@ -559,6 +559,11 @@ public final class OngoingCallContext {
|
||||
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 var networkTypeDisposable: Disposable?
|
||||
|
||||
@ -687,6 +692,9 @@ public final class OngoingCallContext {
|
||||
context.signalBarsChanged = { signalBars in
|
||||
self?.receptionPromise.set(.single(signalBars))
|
||||
}
|
||||
context.audioLevelUpdated = { level in
|
||||
self?.audioLevelPromise.set(.single(level))
|
||||
}
|
||||
|
||||
strongSelf.networkTypeDisposable = (updatedNetworkType
|
||||
|> deliverOn(queue)).start(next: { networkType in
|
||||
|
@ -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 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;
|
||||
|
||||
|
@ -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) {
|
||||
[queue dispatch:^{
|
||||
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 58f3f89352d164540067e197ad735c0717546172
|
||||
Subproject commit 6ae73f4c388854d86a0ce66bf243561a11d9e719
|
Loading…
x
Reference in New Issue
Block a user