Merge commit '8ec300703d18093504b95aee79c37d561c0fafbc'

This commit is contained in:
Ali 2021-04-09 18:18:33 +04:00
commit 6e5e365d40
10 changed files with 221 additions and 217 deletions

View File

@ -841,9 +841,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1) let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1)
let foundLocalPeers: Signal<(peers: [RenderedPeer], unread: [PeerId: (Int32, Bool)]), NoError> let foundLocalPeers: Signal<(peers: [RenderedPeer], unread: [PeerId: (Int32, Bool)]), NoError>
if let query = query { if let query = query {
foundLocalPeers = context.account.postbox.searchPeers(query: query.lowercased()) foundLocalPeers = context.account.postbox.searchPeers(query: query.lowercased())
|> mapToSignal { local -> Signal<([PeerView], [RenderedPeer]), NoError> in |> mapToSignal { local -> Signal<([PeerView], [RenderedPeer]), NoError> in
@ -1279,7 +1277,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openUrl: { url in }, openUrl: { url in
interaction.openUrl(url) interaction.openUrl(url)
}, openPeer: { peer, navigation in }, openPeer: { peer, navigation in
// interaction.openPeer(peer.id, navigation)
}, callPeer: { _, _ in }, callPeer: { _, _ in
}, enqueueMessage: { _ in }, enqueueMessage: { _ in
}, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .custom(messages: foundMessages, at: message.id, loadMore: { }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .custom(messages: foundMessages, at: message.id, loadMore: {

View File

@ -320,21 +320,6 @@ private final class VisualMediaItemNode: ASDisplayNode {
func updateIsVisible(_ isVisible: Bool) { func updateIsVisible(_ isVisible: Bool) {
self.hasVisibility = isVisible self.hasVisibility = isVisible
// if let _ = self.videoLayerFrameManager {
// let displayLink: ConstantDisplayLinkAnimator
// if let current = self.displayLink {
// displayLink = current
// } else {
// displayLink = ConstantDisplayLinkAnimator { [weak self] in
// guard let strongSelf = self else {
// return
// }
// strongSelf.displayLinkTimestamp += 1.0 / 30.0
// }
// displayLink.frameInterval = 2
// self.displayLink = displayLink
// }
// }
self.displayLink?.isPaused = !self.hasVisibility || self.isHidden self.displayLink?.isPaused = !self.hasVisibility || self.isHidden
} }
@ -422,8 +407,8 @@ private final class VisualMediaItem {
let dimensions: CGSize let dimensions: CGSize
let aspectRatio: CGFloat let aspectRatio: CGFloat
init(message: Message) { init(message: Message, index: UInt32?) {
self.index = nil self.index = index
self.message = message self.message = message
var aspectRatio: CGFloat = 1.0 var aspectRatio: CGFloat = 1.0
@ -441,10 +426,10 @@ private final class VisualMediaItem {
} }
var stableId: UInt32 { var stableId: UInt32 {
if let message = self.message { if let index = self.index {
return message.stableId
} else if let index = self.index {
return index return index
} else if let message = self.message {
return message.stableId
} else { } else {
return 0 return 0
} }
@ -708,7 +693,6 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
self.animationTimer?.invalidate() self.animationTimer?.invalidate()
} }
func updateHistory(entries: [ChatListSearchEntry]?, totalCount: Int32, updateType: ViewUpdateType) { func updateHistory(entries: [ChatListSearchEntry]?, totalCount: Int32, updateType: ViewUpdateType) {
switch updateType { switch updateType {
case .FillHole: case .FillHole:
@ -716,11 +700,13 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate {
default: default:
self.mediaItems.removeAll() self.mediaItems.removeAll()
var index: UInt32 = 0
if let entries = entries { if let entries = entries {
for entry in entries { for entry in entries {
if case let .message(message, _, _, _, _, _, _) = entry { if case let .message(message, _, _, _, _, _, _) = entry {
self.mediaItems.append(VisualMediaItem(message: message)) self.mediaItems.append(VisualMediaItem(message: message, index: nil))
} }
index += 1
} }
} }
self.itemsLayout = nil self.itemsLayout = nil

View File

@ -1028,6 +1028,185 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
} }
private func switchToTemporaryScheduledParticipantsContext() {
guard let callInfo = self.internalState.callInfo, callInfo.scheduleTimestamp != nil else {
return
}
let accountContext = self.accountContext
let peerId = self.peerId
let rawAdminIds: Signal<Set<PeerId>, NoError>
if peerId.namespace == Namespaces.Peer.CloudChannel {
rawAdminIds = Signal { subscriber in
let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in
var peerIds = Set<PeerId>()
for item in list.list {
if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) {
peerIds.insert(item.peer.id)
}
}
subscriber.putNext(peerIds)
})
return disposable
}
|> distinctUntilChanged
|> runOn(.mainQueue())
} else {
rawAdminIds = accountContext.account.postbox.combinedView(keys: [.cachedPeerData(peerId: peerId)])
|> map { views -> Set<PeerId> in
guard let view = views.views[.cachedPeerData(peerId: peerId)] as? CachedPeerDataView else {
return Set()
}
guard let cachedData = view.cachedPeerData as? CachedGroupData, let participants = cachedData.participants else {
return Set()
}
return Set(participants.participants.compactMap { item -> PeerId? in
switch item {
case .creator, .admin:
return item.peerId
default:
return nil
}
})
}
|> distinctUntilChanged
}
let adminIds = combineLatest(queue: .mainQueue(),
rawAdminIds,
accountContext.account.postbox.combinedView(keys: [.basicPeer(peerId)])
)
|> map { rawAdminIds, view -> Set<PeerId> in
var rawAdminIds = rawAdminIds
if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer as? TelegramChannel {
if peer.hasPermission(.manageCalls) {
rawAdminIds.insert(accountContext.account.peerId)
} else {
rawAdminIds.remove(accountContext.account.peerId)
}
}
return rawAdminIds
}
|> distinctUntilChanged
let participantsContext = GroupCallParticipantsContext(
account: self.accountContext.account,
peerId: self.peerId,
myPeerId: self.joinAsPeerId,
id: callInfo.id,
accessHash: callInfo.accessHash,
state: GroupCallParticipantsContext.State(
participants: [],
nextParticipantsFetchOffset: nil,
adminIds: Set(),
isCreator: false,
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: self.stateValue.defaultParticipantMuteState == .muted, canChange: false),
sortAscending: true,
recordingStartTimestamp: nil,
title: self.stateValue.title,
scheduleTimestamp: self.stateValue.scheduleTimestamp,
subscribedToScheduled: self.stateValue.subscribedToScheduled,
totalCount: 0,
version: 0
),
previousServiceState: nil
)
self.temporaryParticipantsContext = nil
self.participantsContext = participantsContext
let myPeerId = self.joinAsPeerId
let myPeer = self.accountContext.account.postbox.transaction { transaction -> (Peer, CachedPeerData?)? in
if let peer = transaction.getPeer(myPeerId) {
return (peer, transaction.getPeerCachedData(peerId: myPeerId))
} else {
return nil
}
}
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
participantsContext.state,
adminIds,
myPeer,
accountContext.account.postbox.peerView(id: peerId)
).start(next: { [weak self] state, adminIds, myPeerAndCachedData, view in
guard let strongSelf = self else {
return
}
var members = PresentationGroupCallMembers(
participants: [],
speakingParticipants: Set(),
totalCount: state.totalCount,
loadMoreToken: state.nextParticipantsFetchOffset
)
var participants: [GroupCallParticipantsContext.Participant] = []
var topParticipants: [GroupCallParticipantsContext.Participant] = []
if let (myPeer, cachedData) = myPeerAndCachedData {
let about: String?
if let cachedData = cachedData as? CachedUserData {
about = cachedData.about
} else if let cachedData = cachedData as? CachedUserData {
about = cachedData.about
} else {
about = nil
}
participants.append(GroupCallParticipantsContext.Participant(
peer: myPeer,
ssrc: nil,
jsonParams: nil,
joinTimestamp: strongSelf.temporaryJoinTimestamp,
raiseHandRating: strongSelf.temporaryRaiseHandRating,
hasRaiseHand: strongSelf.temporaryHasRaiseHand,
activityTimestamp: strongSelf.temporaryActivityTimestamp,
activityRank: strongSelf.temporaryActivityRank,
muteState: strongSelf.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
volume: nil,
about: about
))
}
for participant in participants {
members.participants.append(participant)
if topParticipants.count < 3 {
topParticipants.append(participant)
}
}
strongSelf.membersValue = members
strongSelf.stateValue.adminIds = adminIds
strongSelf.stateValue.canManageCall = state.isCreator || adminIds.contains(strongSelf.accountContext.account.peerId)
if (state.isCreator || strongSelf.stateValue.adminIds.contains(strongSelf.accountContext.account.peerId)) && state.defaultParticipantsAreMuted.canChange {
strongSelf.stateValue.defaultParticipantMuteState = state.defaultParticipantsAreMuted.isMuted ? .muted : .unmuted
}
strongSelf.stateValue.recordingStartTimestamp = state.recordingStartTimestamp
strongSelf.stateValue.title = state.title
strongSelf.stateValue.scheduleTimestamp = strongSelf.isScheduledStarted ? nil : state.scheduleTimestamp
if state.scheduleTimestamp == nil && !strongSelf.isScheduledStarted {
strongSelf.updateSessionState(internalState: .active(GroupCallInfo(id: callInfo.id, accessHash: callInfo.accessHash, participantCount: state.totalCount, clientParams: callInfo.clientParams, streamDcId: callInfo.streamDcId, title: state.title, scheduleTimestamp: nil, subscribedToScheduled: false, recordingStartTimestamp: nil, sortAscending: true)), audioSessionControl: strongSelf.audioSessionControl)
} else {
strongSelf.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
id: callInfo.id,
accessHash: callInfo.accessHash,
participantCount: state.totalCount,
clientParams: nil,
streamDcId: nil,
title: state.title,
scheduleTimestamp: state.scheduleTimestamp,
subscribedToScheduled: false,
recordingStartTimestamp: state.recordingStartTimestamp,
sortAscending: state.sortAscending
))))
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
participantCount: state.totalCount,
topParticipants: topParticipants,
activeSpeakers: Set()
)))
}
}))
}
private func updateSessionState(internalState: InternalState, audioSessionControl: ManagedAudioSessionControl?) { private func updateSessionState(internalState: InternalState, audioSessionControl: ManagedAudioSessionControl?) {
let previousControl = self.audioSessionControl let previousControl = self.audioSessionControl
self.audioSessionControl = audioSessionControl self.audioSessionControl = audioSessionControl
@ -1725,179 +1904,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.startCheckingCallIfNeeded() self.startCheckingCallIfNeeded()
} }
} else if case let .active(callInfo) = internalState, callInfo.scheduleTimestamp != nil { } else if case let .active(callInfo) = internalState, callInfo.scheduleTimestamp != nil {
let accountContext = self.accountContext self.switchToTemporaryScheduledParticipantsContext()
let peerId = self.peerId
let rawAdminIds: Signal<Set<PeerId>, NoError>
if peerId.namespace == Namespaces.Peer.CloudChannel {
rawAdminIds = Signal { subscriber in
let (disposable, _) = accountContext.peerChannelMemberCategoriesContextsManager.admins(postbox: accountContext.account.postbox, network: accountContext.account.network, accountPeerId: accountContext.account.peerId, peerId: peerId, updated: { list in
var peerIds = Set<PeerId>()
for item in list.list {
if let adminInfo = item.participant.adminInfo, adminInfo.rights.rights.contains(.canManageCalls) {
peerIds.insert(item.peer.id)
}
}
subscriber.putNext(peerIds)
})
return disposable
}
|> distinctUntilChanged
|> runOn(.mainQueue())
} else {
rawAdminIds = accountContext.account.postbox.combinedView(keys: [.cachedPeerData(peerId: peerId)])
|> map { views -> Set<PeerId> in
guard let view = views.views[.cachedPeerData(peerId: peerId)] as? CachedPeerDataView else {
return Set()
}
guard let cachedData = view.cachedPeerData as? CachedGroupData, let participants = cachedData.participants else {
return Set()
}
return Set(participants.participants.compactMap { item -> PeerId? in
switch item {
case .creator, .admin:
return item.peerId
default:
return nil
}
})
}
|> distinctUntilChanged
}
let adminIds = combineLatest(queue: .mainQueue(),
rawAdminIds,
accountContext.account.postbox.combinedView(keys: [.basicPeer(peerId)])
)
|> map { rawAdminIds, view -> Set<PeerId> in
var rawAdminIds = rawAdminIds
if let peerView = view.views[.basicPeer(peerId)] as? BasicPeerView, let peer = peerView.peer as? TelegramChannel {
if peer.hasPermission(.manageCalls) {
rawAdminIds.insert(accountContext.account.peerId)
} else {
rawAdminIds.remove(accountContext.account.peerId)
}
}
return rawAdminIds
}
|> distinctUntilChanged
let participantsContext = GroupCallParticipantsContext(
account: self.accountContext.account,
peerId: self.peerId,
myPeerId: self.joinAsPeerId,
id: callInfo.id,
accessHash: callInfo.accessHash,
state: GroupCallParticipantsContext.State(
participants: [],
nextParticipantsFetchOffset: nil,
adminIds: Set(),
isCreator: false,
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: self.stateValue.defaultParticipantMuteState == .muted, canChange: false),
sortAscending: true,
recordingStartTimestamp: nil,
title: self.stateValue.title,
scheduleTimestamp: self.stateValue.scheduleTimestamp,
subscribedToScheduled: self.stateValue.subscribedToScheduled,
totalCount: 0,
version: 0
),
previousServiceState: nil
)
self.temporaryParticipantsContext = nil
self.participantsContext = participantsContext
let myPeerId = self.joinAsPeerId
let myPeer = self.accountContext.account.postbox.transaction { transaction -> (Peer, CachedPeerData?)? in
if let peer = transaction.getPeer(myPeerId) {
return (peer, transaction.getPeerCachedData(peerId: myPeerId))
} else {
return nil
}
}
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
participantsContext.state,
adminIds,
myPeer,
accountContext.account.postbox.peerView(id: peerId)
).start(next: { [weak self] state, adminIds, myPeerAndCachedData, view in
guard let strongSelf = self else {
return
}
var members = PresentationGroupCallMembers(
participants: [],
speakingParticipants: Set(),
totalCount: state.totalCount,
loadMoreToken: state.nextParticipantsFetchOffset
)
var participants: [GroupCallParticipantsContext.Participant] = []
var topParticipants: [GroupCallParticipantsContext.Participant] = []
if let (myPeer, cachedData) = myPeerAndCachedData {
let about: String?
if let cachedData = cachedData as? CachedUserData {
about = cachedData.about
} else if let cachedData = cachedData as? CachedUserData {
about = cachedData.about
} else {
about = nil
}
participants.append(GroupCallParticipantsContext.Participant(
peer: myPeer,
ssrc: nil,
jsonParams: nil,
joinTimestamp: strongSelf.temporaryJoinTimestamp,
raiseHandRating: strongSelf.temporaryRaiseHandRating,
hasRaiseHand: strongSelf.temporaryHasRaiseHand,
activityTimestamp: strongSelf.temporaryActivityTimestamp,
activityRank: strongSelf.temporaryActivityRank,
muteState: strongSelf.temporaryMuteState ?? GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
volume: nil,
about: about
))
}
for participant in participants {
members.participants.append(participant)
if topParticipants.count < 3 {
topParticipants.append(participant)
}
}
strongSelf.membersValue = members
strongSelf.stateValue.adminIds = adminIds
strongSelf.stateValue.canManageCall = state.isCreator || adminIds.contains(strongSelf.accountContext.account.peerId)
if (state.isCreator || strongSelf.stateValue.adminIds.contains(strongSelf.accountContext.account.peerId)) && state.defaultParticipantsAreMuted.canChange {
strongSelf.stateValue.defaultParticipantMuteState = state.defaultParticipantsAreMuted.isMuted ? .muted : .unmuted
}
strongSelf.stateValue.recordingStartTimestamp = state.recordingStartTimestamp
strongSelf.stateValue.title = state.title
strongSelf.stateValue.scheduleTimestamp = strongSelf.isScheduledStarted ? nil : state.scheduleTimestamp
if state.scheduleTimestamp == nil && !strongSelf.isScheduledStarted {
strongSelf.updateSessionState(internalState: .active(GroupCallInfo(id: callInfo.id, accessHash: callInfo.accessHash, participantCount: state.totalCount, clientParams: callInfo.clientParams, streamDcId: callInfo.streamDcId, title: state.title, scheduleTimestamp: nil, subscribedToScheduled: false, recordingStartTimestamp: nil, sortAscending: true)), audioSessionControl: strongSelf.audioSessionControl)
} else {
strongSelf.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
id: callInfo.id,
accessHash: callInfo.accessHash,
participantCount: state.totalCount,
clientParams: nil,
streamDcId: nil,
title: state.title,
scheduleTimestamp: state.scheduleTimestamp,
subscribedToScheduled: false,
recordingStartTimestamp: state.recordingStartTimestamp,
sortAscending: state.sortAscending
))))
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
participantCount: state.totalCount,
topParticipants: topParticipants,
activeSpeakers: Set()
)))
}
}))
} }
} }
} }
@ -2086,6 +2093,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if strongSelf.stateValue.scheduleTimestamp != nil { if strongSelf.stateValue.scheduleTimestamp != nil {
strongSelf.stateValue.myPeerId = peerId strongSelf.stateValue.myPeerId = peerId
strongSelf.reconnectedAsEventsPipe.putNext(myPeer) strongSelf.reconnectedAsEventsPipe.putNext(myPeer)
strongSelf.switchToTemporaryScheduledParticipantsContext()
} else { } else {
strongSelf.reconnectingAsPeer = myPeer strongSelf.reconnectingAsPeer = myPeer

View File

@ -1723,7 +1723,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.currentContentOffset = offset
if !strongSelf.animatingExpansion && !strongSelf.animatingInsertion && strongSelf.panGestureArguments == nil { if !strongSelf.animatingExpansion && !strongSelf.animatingInsertion && strongSelf.panGestureArguments == nil && !strongSelf.animatingAppearance {
strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition) strongSelf.updateFloatingHeaderOffset(offset: offset, transition: transition)
} }
} }
@ -2876,9 +2876,24 @@ public final class VoiceChatController: ViewController {
topInset = listSize.height topInset = listSize.height
} }
let offset = offset + topInset var bottomEdge: CGFloat = 0.0
self.listNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? ListViewItemNode {
let convertedFrame = self.listNode.view.convert(itemNode.frame, to: self.contentContainer.view)
if convertedFrame.maxY > bottomEdge {
bottomEdge = convertedFrame.maxY
}
}
}
let offset = (bottomEdge.isZero ? 0.0 : offset) + topInset
self.floatingHeaderOffset = offset self.floatingHeaderOffset = offset
if bottomEdge.isZero {
bottomEdge = self.listNode.frame.minY + 46.0 + 56.0
}
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: size.width, height: topPanelHeight)) let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: size.width, height: topPanelHeight))
@ -2920,16 +2935,6 @@ public final class VoiceChatController: ViewController {
} }
self.topPanelBackgroundNode.frame = CGRect(x: 0.0, y: topPanelHeight - 24.0, width: size.width, height: 24.0) self.topPanelBackgroundNode.frame = CGRect(x: 0.0, y: topPanelHeight - 24.0, width: 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.frame, to: self.contentContainer.view)
if convertedFrame.maxY > bottomEdge {
bottomEdge = convertedFrame.maxY
}
}
}
let listMaxY = listTopInset + listSize.height let listMaxY = listTopInset + listSize.height
let bottomOffset: CGFloat = min(0.0, bottomEdge - listMaxY) let bottomOffset: CGFloat = min(0.0, bottomEdge - listMaxY)
@ -3388,16 +3393,20 @@ public final class VoiceChatController: ViewController {
guard let (layout, navigationHeight) = self.validLayout else { guard let (layout, navigationHeight) = self.validLayout else {
return return
} }
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) self.updateFloatingHeaderOffset(offset: 0.0, transition: .immediate)
self.animatingAppearance = true
let topPanelFrame = self.topPanelNode.view.convert(self.topPanelNode.bounds, to: self.view)
let initialBounds = self.contentContainer.bounds let initialBounds = self.contentContainer.bounds
let topPanelFrame = self.topPanelNode.view.convert(self.topPanelNode.bounds, to: self.view)
self.contentContainer.bounds = initialBounds.offsetBy(dx: 0.0, dy: -(layout.size.height - topPanelFrame.minY)) self.contentContainer.bounds = initialBounds.offsetBy(dx: 0.0, dy: -(layout.size.height - topPanelFrame.minY))
self.contentContainer.isHidden = false self.contentContainer.isHidden = false
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
transition.animateView({ transition.animateView({
self.contentContainer.view.bounds = initialBounds self.contentContainer.view.bounds = initialBounds
}, completion: { _ in }, completion: { _ in
self.animatingAppearance = false
if self.actionButton.supernode !== self.bottomPanelNode { if self.actionButton.supernode !== self.bottomPanelNode {
self.actionButton.ignoreHierarchyChanges = true self.actionButton.ignoreHierarchyChanges = true
self.audioButton.isHidden = false self.audioButton.isHidden = false
@ -3731,6 +3740,7 @@ public final class VoiceChatController: ViewController {
private var animatingInsertion = false private var animatingInsertion = false
private var animatingExpansion = false private var animatingExpansion = false
private var animatingAppearance = false
private var panGestureArguments: (topInset: CGFloat, offset: CGFloat)? private var panGestureArguments: (topInset: CGFloat, offset: CGFloat)?
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) { @objc func panGesture(_ recognizer: UIPanGestureRecognizer) {

View File

@ -132,7 +132,7 @@ final class VoiceChatTimerNode: ASDisplayNode {
let elapsedTime = scheduleTime - currentTime let elapsedTime = scheduleTime - currentTime
let timerText: String let timerText: String
if elapsedTime >= 86400 { if elapsedTime >= 86400 {
timerText = timeIntervalString(strings: self.strings, value: elapsedTime) timerText = timeIntervalString(strings: self.strings, value: elapsedTime).uppercased()
} else { } else {
timerText = textForTimeout(value: abs(elapsedTime)) timerText = textForTimeout(value: abs(elapsedTime))
} }

View File

@ -188,8 +188,11 @@ func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer]) -> Mes
var hasher = Hasher() var hasher = Hasher()
hasher.combine(id.id) hasher.combine(id.id)
hasher.combine(id.peerId) hasher.combine(id.peerId)
let stableId = UInt32(clamping: hasher.finalize()) let hashValue = Int64(hasher.finalize())
let first = UInt32((hashValue >> 32) & 0xffffffff)
let second = UInt32(hashValue & 0xffffffff)
let stableId = first &+ second
return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: []) return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])
} }