diff --git a/Telegram/Telegram-iOS/Resources/VoiceCancelReminder.tgs b/Telegram/Telegram-iOS/Resources/VoiceCancelReminder.tgs index e70706ac8f..133d47e1e1 100644 Binary files a/Telegram/Telegram-iOS/Resources/VoiceCancelReminder.tgs and b/Telegram/Telegram-iOS/Resources/VoiceCancelReminder.tgs differ diff --git a/Telegram/Telegram-iOS/Resources/VoiceMute.tgs b/Telegram/Telegram-iOS/Resources/VoiceMute.tgs index b2e8b4a79d..533a7a6a2e 100644 Binary files a/Telegram/Telegram-iOS/Resources/VoiceMute.tgs and b/Telegram/Telegram-iOS/Resources/VoiceMute.tgs differ diff --git a/Telegram/Telegram-iOS/Resources/VoiceSetReminder.tgs b/Telegram/Telegram-iOS/Resources/VoiceSetReminder.tgs index 1cea642acb..fe22c2c4ea 100644 Binary files a/Telegram/Telegram-iOS/Resources/VoiceSetReminder.tgs and b/Telegram/Telegram-iOS/Resources/VoiceSetReminder.tgs differ diff --git a/Telegram/Telegram-iOS/Resources/VoiceUnmute.tgs b/Telegram/Telegram-iOS/Resources/VoiceUnmute.tgs index 16ba09aa4d..57d546d4d2 100644 Binary files a/Telegram/Telegram-iOS/Resources/VoiceUnmute.tgs and b/Telegram/Telegram-iOS/Resources/VoiceUnmute.tgs differ diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index a8c34852be..93af51f808 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -841,9 +841,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1) - let foundLocalPeers: Signal<(peers: [RenderedPeer], unread: [PeerId: (Int32, Bool)]), NoError> - if let query = query { foundLocalPeers = context.account.postbox.searchPeers(query: query.lowercased()) |> mapToSignal { local -> Signal<([PeerView], [RenderedPeer]), NoError> in @@ -1279,7 +1277,6 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, openUrl: { url in interaction.openUrl(url) }, openPeer: { peer, navigation in -// interaction.openPeer(peer.id, navigation) }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: .custom(messages: foundMessages, at: message.id, loadMore: { diff --git a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift index d74c1208e9..7979476472 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchMediaNode.swift @@ -320,21 +320,6 @@ private final class VisualMediaItemNode: ASDisplayNode { func updateIsVisible(_ isVisible: Bool) { 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 } @@ -422,8 +407,8 @@ private final class VisualMediaItem { let dimensions: CGSize let aspectRatio: CGFloat - init(message: Message) { - self.index = nil + init(message: Message, index: UInt32?) { + self.index = index self.message = message var aspectRatio: CGFloat = 1.0 @@ -441,10 +426,10 @@ private final class VisualMediaItem { } var stableId: UInt32 { - if let message = self.message { - return message.stableId - } else if let index = self.index { + if let index = self.index { return index + } else if let message = self.message { + return message.stableId } else { return 0 } @@ -708,7 +693,6 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { self.animationTimer?.invalidate() } - func updateHistory(entries: [ChatListSearchEntry]?, totalCount: Int32, updateType: ViewUpdateType) { switch updateType { case .FillHole: @@ -716,11 +700,13 @@ final class ChatListSearchMediaNode: ASDisplayNode, UIScrollViewDelegate { default: self.mediaItems.removeAll() + var index: UInt32 = 0 if let entries = entries { for entry in entries { 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 diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 35e91c9cec..6679d24bf1 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -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, 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() + 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 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 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?) { let previousControl = self.audioSessionControl self.audioSessionControl = audioSessionControl @@ -1725,179 +1904,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.startCheckingCallIfNeeded() } } else if case let .active(callInfo) = internalState, callInfo.scheduleTimestamp != nil { - let accountContext = self.accountContext - let peerId = self.peerId - let rawAdminIds: Signal, 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() - 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 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 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() - ))) - } - })) + self.switchToTemporaryScheduledParticipantsContext() } } } @@ -2086,6 +2093,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { if strongSelf.stateValue.scheduleTimestamp != nil { strongSelf.stateValue.myPeerId = peerId strongSelf.reconnectedAsEventsPipe.putNext(myPeer) + strongSelf.switchToTemporaryScheduledParticipantsContext() } else { strongSelf.reconnectingAsPeer = myPeer diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 4e23ad8ae5..27c7472a94 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1723,7 +1723,7 @@ public final class VoiceChatController: ViewController { self.listNode.updateFloatingHeaderOffset = { [weak self] offset, transition in if let strongSelf = self { 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) } } @@ -2876,9 +2876,24 @@ public final class VoiceChatController: ViewController { 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 - + + if bottomEdge.isZero { + bottomEdge = self.listNode.frame.minY + 46.0 + 56.0 + } + let rawPanelOffset = offset + listTopInset - topPanelHeight let panelOffset = max(layoutTopInset, rawPanelOffset) 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) - 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 bottomOffset: CGFloat = min(0.0, bottomEdge - listMaxY) @@ -3388,16 +3393,20 @@ public final class VoiceChatController: ViewController { guard let (layout, navigationHeight) = self.validLayout else { 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 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.isHidden = false + + let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) transition.animateView({ self.contentContainer.view.bounds = initialBounds }, completion: { _ in + self.animatingAppearance = false if self.actionButton.supernode !== self.bottomPanelNode { self.actionButton.ignoreHierarchyChanges = true self.audioButton.isHidden = false @@ -3731,6 +3740,7 @@ public final class VoiceChatController: ViewController { private var animatingInsertion = false private var animatingExpansion = false + private var animatingAppearance = false private var panGestureArguments: (topInset: CGFloat, offset: CGFloat)? @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift index 6d586d3946..0994a2880a 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift @@ -132,7 +132,7 @@ final class VoiceChatTimerNode: ASDisplayNode { let elapsedTime = scheduleTime - currentTime let timerText: String if elapsedTime >= 86400 { - timerText = timeIntervalString(strings: self.strings, value: elapsedTime) + timerText = timeIntervalString(strings: self.strings, value: elapsedTime).uppercased() } else { timerText = textForTimeout(value: abs(elapsedTime)) } diff --git a/submodules/TelegramCore/Sources/MessageUtils.swift b/submodules/TelegramCore/Sources/MessageUtils.swift index ee98a74694..80f922b68b 100644 --- a/submodules/TelegramCore/Sources/MessageUtils.swift +++ b/submodules/TelegramCore/Sources/MessageUtils.swift @@ -188,8 +188,11 @@ func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer]) -> Mes var hasher = Hasher() hasher.combine(id.id) 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: []) }