diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index ac0620eca7..22dc881132 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -314,6 +314,8 @@ public protocol PresentationGroupCall: class { var incomingVideoSources: Signal<[PeerId: UInt32], NoError> { get } func makeIncomingVideoView(source: UInt32, completion: @escaping (PresentationCallVideoView?) -> Void) + + func loadMoreMembers(token: String) } public protocol PresentationCallManager: class { diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index a5e961741c..0efdfa61e6 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1982,7 +1982,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let signal: Signal = strongSelf.context.account.postbox.transaction { transaction -> Void in for peerId in peerIds { - removePeerChat(account: context.account, transaction: transaction, mediaBox: context.account.postbox.mediaBox, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: false) + removePeerChat(account: context.account, transaction: transaction, mediaBox: context.account.postbox.mediaBox, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: peerId.namespace == Namespaces.Peer.SecretChat) } } |> afterDisposed { diff --git a/submodules/Reachability/BUILD b/submodules/Reachability/BUILD index 35b3371126..5e08b22c98 100644 --- a/submodules/Reachability/BUILD +++ b/submodules/Reachability/BUILD @@ -1,20 +1,14 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") -objc_library( +swift_library( name = "Reachability", - enable_modules = True, module_name = "Reachability", srcs = glob([ - "Sources/*.m", + "Sources/**/*.swift", ]), - hdrs = glob([ - "PublicHeaders/**/*.h", - ]), - includes = [ - "PublicHeaders", - ], - sdk_frameworks = [ - "Foundation", - "SystemConfiguration", + deps = [ + "//submodules/Reachability/LegacyReachability:LegacyReachability", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", ], visibility = [ "//visibility:public", diff --git a/submodules/Reachability/LegacyReachability/BUILD b/submodules/Reachability/LegacyReachability/BUILD new file mode 100644 index 0000000000..f9f9023871 --- /dev/null +++ b/submodules/Reachability/LegacyReachability/BUILD @@ -0,0 +1,22 @@ + +objc_library( + name = "LegacyReachability", + enable_modules = True, + module_name = "LegacyReachability", + srcs = glob([ + "Sources/*.m", + ]), + hdrs = glob([ + "PublicHeaders/**/*.h", + ]), + includes = [ + "PublicHeaders", + ], + sdk_frameworks = [ + "Foundation", + "SystemConfiguration", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/Reachability/PublicHeaders/Reachability/Reachability.h b/submodules/Reachability/LegacyReachability/PublicHeaders/LegacyReachability/LegacyReachability.h similarity index 97% rename from submodules/Reachability/PublicHeaders/Reachability/Reachability.h rename to submodules/Reachability/LegacyReachability/PublicHeaders/LegacyReachability/LegacyReachability.h index 2ece48e5d4..f03b45f207 100644 --- a/submodules/Reachability/PublicHeaders/Reachability/Reachability.h +++ b/submodules/Reachability/LegacyReachability/PublicHeaders/LegacyReachability/LegacyReachability.h @@ -24,7 +24,7 @@ typedef enum : NSInteger { extern NSString *kReachabilityChangedNotification; -@interface Reachability : NSObject +@interface LegacyReachability : NSObject @property (nonatomic, copy) void (^reachabilityChanged)(NetworkStatus status); diff --git a/submodules/Reachability/Sources/Reachability.m b/submodules/Reachability/LegacyReachability/Sources/LegacyReachability.m similarity index 92% rename from submodules/Reachability/Sources/Reachability.m rename to submodules/Reachability/LegacyReachability/Sources/LegacyReachability.m index 4b3abb5088..ecedbeccbc 100644 --- a/submodules/Reachability/Sources/Reachability.m +++ b/submodules/Reachability/LegacyReachability/Sources/LegacyReachability.m @@ -14,7 +14,7 @@ #import -#import +#import #import #import @@ -126,14 +126,14 @@ static ReachabilityAtomic *contexts() { return instance; } -static void withContext(int32_t key, void (^f)(Reachability *)) { - Reachability *reachability = [contexts() with:^id(NSDictionary *dict) { +static void withContext(int32_t key, void (^f)(LegacyReachability *)) { + LegacyReachability *reachability = [contexts() with:^id(NSDictionary *dict) { return dict[@(key)]; }]; f(reachability); } -static int32_t addContext(Reachability *context) { +static int32_t addContext(LegacyReachability *context) { int32_t key = OSAtomicIncrement32(&nextKey); [contexts() modify:^id(NSMutableDictionary *dict) { NSMutableDictionary *updatedDict = [[NSMutableDictionary alloc] initWithDictionary:dict]; @@ -155,19 +155,19 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach { #pragma unused (target, flags) //NSCAssert(info != NULL, @"info was NULL in ReachabilityCallback"); - //NSCAssert([(__bridge NSObject*) info isKindOfClass: [Reachability class]], @"info was wrong class in ReachabilityCallback"); + //NSCAssert([(__bridge NSObject*) info isKindOfClass: [LegacyReachability class]], @"info was wrong class in ReachabilityCallback"); int32_t key = (int32_t)((intptr_t)info); - withContext(key, ^(Reachability *context) { - if ([context isKindOfClass:[Reachability class]] && context.reachabilityChanged != nil) + withContext(key, ^(LegacyReachability *context) { + if ([context isKindOfClass:[LegacyReachability class]] && context.reachabilityChanged != nil) context.reachabilityChanged(context.currentReachabilityStatus); }); } -#pragma mark - Reachability implementation +#pragma mark - LegacyReachability implementation -@implementation Reachability +@implementation LegacyReachability { int32_t _key; SCNetworkReachabilityRef _reachabilityRef; @@ -175,7 +175,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach + (instancetype)reachabilityWithHostName:(NSString *)hostName { - Reachability* returnValue = NULL; + LegacyReachability* returnValue = NULL; SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]); if (reachability != NULL) { @@ -199,7 +199,7 @@ static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReach { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, hostAddress); - Reachability* returnValue = NULL; + LegacyReachability* returnValue = NULL; if (reachability != NULL) { diff --git a/submodules/Reachability/Sources/Reachability.swift b/submodules/Reachability/Sources/Reachability.swift new file mode 100644 index 0000000000..02c934d527 --- /dev/null +++ b/submodules/Reachability/Sources/Reachability.swift @@ -0,0 +1,180 @@ +import Foundation +import SwiftSignalKit + +import LegacyReachability +import Network + +private final class WrappedLegacyReachability: NSObject { + @objc private static func threadImpl() { + while true { + RunLoop.current.run(until: .distantFuture) + } + } + + private static let thread: Thread = { + let thread = Thread(target: Reachability.self, selector: #selector(WrappedLegacyReachability.threadImpl), object: nil) + thread.start() + return thread + }() + + @objc private static func dispatchOnThreadImpl(_ f: @escaping () -> Void) { + f() + } + + private static func dispatchOnThread(_ f: @escaping @convention(block) () -> Void) { + WrappedLegacyReachability.perform(#selector(WrappedLegacyReachability.dispatchOnThreadImpl(_:)), on: WrappedLegacyReachability.thread, with: f, waitUntilDone: false) + } + + private let reachability: LegacyReachability + + let value: ValuePromise + + override init() { + assert(Thread.current === WrappedLegacyReachability.thread) + self.reachability = LegacyReachability.forInternetConnection() + let type: Reachability.NetworkType + switch self.reachability.currentReachabilityStatus() { + case NotReachable: + type = .none + case ReachableViaWiFi: + type = .wifi + case ReachableViaWWAN: + type = .cellular + default: + type = .none + } + self.value = ValuePromise(type) + + super.init() + + self.reachability.reachabilityChanged = { [weak self] status in + WrappedLegacyReachability.dispatchOnThread { + guard let strongSelf = self else { + return + } + let internalNetworkType: Reachability.NetworkType + switch status { + case NotReachable: + internalNetworkType = .none + case ReachableViaWiFi: + internalNetworkType = .wifi + case ReachableViaWWAN: + internalNetworkType = .cellular + default: + internalNetworkType = .none + } + strongSelf.value.set(internalNetworkType) + } + } + self.reachability.startNotifier() + } + + private static var valueRef: Unmanaged? + + static func withInstance(_ f: @escaping (WrappedLegacyReachability) -> Void) { + WrappedLegacyReachability.dispatchOnThread { + if self.valueRef == nil { + self.valueRef = Unmanaged.passRetained(WrappedLegacyReachability()) + } + if let valueRef = self.valueRef { + let value = valueRef.takeUnretainedValue() + f(value) + } + } + } +} + +@available(iOSApplicationExtension 12.0, iOS 12.0, *) +private final class PathMonitor { + private let queue: Queue + private let monitor: NWPathMonitor + + let networkType = Promise() + + init(queue: Queue) { + self.queue = queue + self.monitor = NWPathMonitor() + + self.monitor.pathUpdateHandler = { [weak self] path in + queue.async { + guard let strongSelf = self else { + return + } + let networkType: Reachability.NetworkType + if path.status == .satisfied { + if path.usesInterfaceType(.cellular) { + networkType = .cellular + } else { + networkType = .wifi + } + } else { + networkType = .none + } + + strongSelf.networkType.set(.single(networkType)) + } + } + + self.monitor.start(queue: self.queue.queue) + + let networkType: Reachability.NetworkType + let path = self.monitor.currentPath + if path.status == .satisfied { + if path.usesInterfaceType(.cellular) { + networkType = .cellular + } else { + networkType = .wifi + } + } else { + networkType = .none + } + + self.networkType.set(.single(networkType)) + } +} + +@available(iOSApplicationExtension 12.0, iOS 12.0, *) +private final class SharedPathMonitor { + static let queue = Queue() + static let impl = QueueLocalObject(queue: queue, generate: { + return PathMonitor(queue: queue) + }) +} + +public enum Reachability { + public enum NetworkType: Equatable { + case none + case wifi + case cellular + } + + public static var networkType: Signal { + if #available(iOSApplicationExtension 12.0, iOS 12.0, *) { + return Signal { subscriber in + let disposable = MetaDisposable() + + SharedPathMonitor.impl.with { impl in + disposable.set(impl.networkType.get().start(next: { value in + subscriber.putNext(value) + })) + } + + return disposable + } + |> distinctUntilChanged + } else { + return Signal { subscriber in + let disposable = MetaDisposable() + + WrappedLegacyReachability.withInstance({ impl in + disposable.set(impl.value.get().start(next: { next in + subscriber.putNext(next) + })) + }) + + return disposable + } + |> distinctUntilChanged + } + } +} diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 5f803e17d6..2f08e4f761 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -78,7 +78,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext { groupCall: nil ))) - self.disposable = (getGroupCallParticipants(account: account, callId: call.id, accessHash: call.accessHash, offset: "", limit: 100) + self.disposable = (getGroupCallParticipants(account: account, callId: call.id, accessHash: call.accessHash, offset: "", ssrcs: [], limit: 100) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -94,6 +94,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext { accessHash: call.accessHash, state: state ) + strongSelf.participantsContext = context strongSelf.panelDataPromise.set(combineLatest(queue: .mainQueue(), context.state, @@ -248,13 +249,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private let silentTimeout: Int32 = 2 struct Participant { + let ssrc: UInt32 let timestamp: Int32 let level: Float } private var participants: [PeerId: Participant] = [:] - private let speakingParticipantsPromise = ValuePromise>() - private var speakingParticipants = Set() { + private let speakingParticipantsPromise = ValuePromise<[PeerId: UInt32]>() + private var speakingParticipants = [PeerId: UInt32]() { didSet { self.speakingParticipantsPromise.set(self.speakingParticipants) } @@ -271,11 +273,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { var validSpeakers: [PeerId: Participant] = [:] var silentParticipants = Set() - var speakingParticipants = Set() - for (peerId, _, level, hasVoice) in levels { + var speakingParticipants = [PeerId: UInt32]() + for (peerId, ssrc, level, hasVoice) in levels { if level > speakingLevelThreshold && hasVoice { - validSpeakers[peerId] = Participant(timestamp: timestamp, level: level) - speakingParticipants.insert(peerId) + validSpeakers[peerId] = Participant(ssrc: ssrc, timestamp: timestamp, level: level) + speakingParticipants[peerId] = ssrc } else { silentParticipants.insert(peerId) } @@ -288,11 +290,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { if silentParticipants.contains(peerId) { if delta < silentTimeout { validSpeakers[peerId] = participant - speakingParticipants.insert(peerId) + speakingParticipants[peerId] = participant.ssrc } } else if delta < cutoffTimeout { validSpeakers[peerId] = participant - speakingParticipants.insert(peerId) + speakingParticipants[peerId] = participant.ssrc } } } @@ -309,8 +311,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.audioLevelsPromise.set(.single(audioLevels)) } - func get() -> Signal, NoError> { - return self.speakingParticipantsPromise.get() |> distinctUntilChanged + func get() -> Signal<[PeerId: UInt32], NoError> { + return self.speakingParticipantsPromise.get() } func getAudioLevels() -> Signal<[(PeerId, UInt32, Float, Bool)], NoError> { @@ -976,15 +978,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { var myLevel: Float = 0.0 var myLevelHasVoice: Bool = false for (ssrcKey, level, hasVoice) in levels { - let source: UInt32 - let peerId: PeerId? + var peerId: PeerId? + let ssrcValue: UInt32 switch ssrcKey { case .local: peerId = strongSelf.accountContext.account.peerId - source = 0 + ssrcValue = 0 case let .source(ssrc): peerId = strongSelf.ssrcMapping[ssrc] - source = ssrc + ssrcValue = ssrc } if let peerId = peerId { if case .local = ssrcKey { @@ -993,7 +995,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { myLevelHasVoice = hasVoice } } - result.append((peerId, source, level, hasVoice)) + result.append((peerId, ssrcValue, level, hasVoice)) } } @@ -1065,9 +1067,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { var topParticipants: [GroupCallParticipantsContext.Participant] = [] - var reportSpeakingParticipants: [PeerId] = [] + var reportSpeakingParticipants: [PeerId: UInt32] = [:] let timestamp = CACurrentMediaTime() - for peerId in speakingParticipants { + for (peerId, ssrc) in speakingParticipants { let shouldReport: Bool if let previousTimestamp = strongSelf.speakingParticipantsReportTimestamp[peerId] { shouldReport = previousTimestamp + 1.0 < timestamp @@ -1076,7 +1078,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } if shouldReport { strongSelf.speakingParticipantsReportTimestamp[peerId] = timestamp - reportSpeakingParticipants.append(peerId) + reportSpeakingParticipants[peerId] = ssrc } } @@ -1088,7 +1090,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { var members = PresentationGroupCallMembers( participants: [], - speakingParticipants: speakingParticipants, + speakingParticipants: Set(speakingParticipants.keys), totalCount: 0, loadMoreToken: nil ) @@ -1127,6 +1129,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { strongSelf.stateValue.muteState = GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false) strongSelf.callContext?.setIsMuted(true) } + } else { + if let volume = participant.volume { + strongSelf.callContext?.setVolume(ssrc: participant.ssrc, volume: Double(volume) / 10000.0) + } else if participant.muteState?.mutedByYou == true { + strongSelf.callContext?.setVolume(ssrc: participant.ssrc, volume: 0.0) + } } if let index = updatedInvitedPeers.firstIndex(of: participant.peer.id) { @@ -1369,11 +1377,11 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { case let .muted(isPushToTalkActive): isEffectivelyMuted = !isPushToTalkActive isVisuallyMuted = true - self.updateMuteState(peerId: self.accountContext.account.peerId, isMuted: true) + let _ = self.updateMuteState(peerId: self.accountContext.account.peerId, isMuted: true) case .unmuted: isEffectivelyMuted = false isVisuallyMuted = false - self.updateMuteState(peerId: self.accountContext.account.peerId, isMuted: false) + let _ = self.updateMuteState(peerId: self.accountContext.account.peerId, isMuted: false) } self.callContext?.setIsMuted(isEffectivelyMuted) @@ -1511,6 +1519,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } else if self.stateValue.adminIds.contains(self.accountContext.account.peerId) { canThenUnmute = true } else { + self.setVolume(peerId: peerId, volume: 0, sync: false) mutedByYou = true canThenUnmute = true } @@ -1526,6 +1535,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.participantsContext?.updateMuteState(peerId: peerId, muteState: muteState, volume: nil) return muteState } else { + self.setVolume(peerId: peerId, volume: 10000, sync: true) self.participantsContext?.updateMuteState(peerId: peerId, muteState: nil, volume: nil) return nil } @@ -1726,4 +1736,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } }) } + + public func loadMoreMembers(token: String) { + self.participantsContext?.loadMore(token: token) + } } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index b9df6d61ef..1ad740c281 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -547,14 +547,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, animated: Bool, context: AccountContext, presentationData: PresentationData, interaction: Interaction) -> 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, count: toEntries.count, animated: true) + return ListTransition(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, crossFade: crossFade, count: toEntries.count, animated: animated) } private weak var controller: VoiceChatController? @@ -598,7 +598,7 @@ public final class VoiceChatController: ViewController { private var currentTitle: String = "" private var currentSubtitle: String = "" - private var currentCallMembers: [GroupCallParticipantsContext.Participant]? + private var currentCallMembers: ([GroupCallParticipantsContext.Participant], String?)? private var currentInvitedPeers: [Peer]? private var currentSpeakingPeers: Set? private var currentContentOffset: CGFloat? @@ -619,6 +619,8 @@ public final class VoiceChatController: ViewController { private var callState: PresentationGroupCallState? + private var currentLoadToken: String? + private var effectiveMuteState: GroupCallParticipantsContext.Participant.MuteState? { if self.pushingToTalk { return nil @@ -788,7 +790,7 @@ public final class VoiceChatController: ViewController { } var filters: [ChannelMembersSearchFilter] = [] - if let currentCallMembers = strongSelf.currentCallMembers { + if let (currentCallMembers, _) = strongSelf.currentCallMembers { filters.append(.disable(Array(currentCallMembers.map { $0.peer.id }))) } if let groupPeer = groupPeer as? TelegramChannel { @@ -1253,7 +1255,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 ?? [], callMembers?.loadMoreToken), invitedPeers: invitedPeers, speakingPeers: callMembers?.speakingParticipants ?? []) let subtitle = strongSelf.presentationData.strings.VoiceChat_Panel_Members(Int32(max(1, callMembers?.totalCount ?? 0))) strongSelf.currentSubtitle = subtitle @@ -1282,7 +1284,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 ?? ([], nil), invitedPeers: strongSelf.currentInvitedPeers ?? [], speakingPeers: strongSelf.currentSpeakingPeers ?? Set()) strongSelf.didSetDataReady = true strongSelf.controller?.dataReady.set(true) @@ -1468,6 +1470,18 @@ public final class VoiceChatController: ViewController { } } + self.listNode.visibleBottomContentOffsetChanged = { [weak self] offset in + guard let strongSelf = self else { + return + } + if case let .known(value) = offset, value < 100.0 { + if let loadMoreToken = strongSelf.currentCallMembers?.1 { + strongSelf.currentLoadToken = loadMoreToken + strongSelf.call.loadMoreMembers(token: loadMoreToken) + } + } + } + self.memberEventsDisposable.set((self.call.memberEvents |> deliverOnMainQueue).start(next: { [weak self] event in guard let strongSelf = self else { @@ -1667,7 +1681,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 ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set()) } @objc private func actionButtonPressGesture(_ gestureRecognizer: UILongPressGestureRecognizer) { @@ -1712,7 +1726,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 ?? ([], nil), invitedPeers: self.currentInvitedPeers ?? [], speakingPeers: self.currentSpeakingPeers ?? Set()) default: break } @@ -2404,7 +2418,12 @@ public final class VoiceChatController: ViewController { } } - private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: [GroupCallParticipantsContext.Participant], invitedPeers: [Peer], speakingPeers: Set) { + private func updateMembers(muteState: GroupCallParticipantsContext.Participant.MuteState?, callMembers: ([GroupCallParticipantsContext.Participant], String?), invitedPeers: [Peer], speakingPeers: Set) { + var disableAnimation = false + if self.currentCallMembers?.1 != callMembers.1 { + disableAnimation = true + } + self.currentCallMembers = callMembers self.currentSpeakingPeers = speakingPeers self.currentInvitedPeers = invitedPeers @@ -2418,7 +2437,7 @@ public final class VoiceChatController: ViewController { entries.append(.invite(self.presentationData.theme, self.presentationData.strings, self.presentationData.strings.VoiceChat_InviteMember)) - for member in callMembers { + for member in callMembers.0 { if processedPeerIds.contains(member.peer.id) { continue } @@ -2486,7 +2505,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, animated: !disableAnimation, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!) self.enqueueTransition(transition) } diff --git a/submodules/TelegramCore/Sources/ChatHistoryImport.swift b/submodules/TelegramCore/Sources/ChatHistoryImport.swift index 48f455b609..6664fa6e20 100644 --- a/submodules/TelegramCore/Sources/ChatHistoryImport.swift +++ b/submodules/TelegramCore/Sources/ChatHistoryImport.swift @@ -49,7 +49,7 @@ public enum ChatHistoryImport { } public static func initSession(account: Account, peerId: PeerId, file: TempBoxFile, mediaCount: Int32) -> Signal { - return multipartUpload(network: account.network, postbox: account.postbox, source: .tempFile(file), encrypt: false, tag: nil, hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: false, useLargerParts: true, increaseParallelParts: true, useMultiplexedRequests: false, useCompression: true) + return multipartUpload(network: account.network, postbox: account.postbox, source: .tempFile(file), encrypt: false, tag: nil, hintFileSize: nil, hintFileIsLarge: false, forceNoBigParts: true, useLargerParts: true, increaseParallelParts: true, useMultiplexedRequests: false, useCompression: true) |> mapError { _ -> InitImportError in return .generic } diff --git a/submodules/TelegramCore/Sources/GroupCalls.swift b/submodules/TelegramCore/Sources/GroupCalls.swift index 7f7756527f..242e6c3e94 100644 --- a/submodules/TelegramCore/Sources/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/GroupCalls.swift @@ -195,10 +195,8 @@ public enum GetGroupCallParticipantsError { case generic } -public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, ssrcs: [UInt32] = [], offset: String, limit: Int32) -> Signal { - return account.network.request(Api.functions.phone.getGroupParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), ids: [], sources: ssrcs.map { - Int32(bitPattern: $0) - }, offset: offset, limit: limit)) +public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32) -> Signal { + return account.network.request(Api.functions.phone.getGroupParticipants(call: .inputGroupCall(id: callId, accessHash: accessHash), ids: [], sources: ssrcs.map { Int32(bitPattern: $0) }, offset: offset, limit: limit)) |> mapError { _ -> GetGroupCallParticipantsError in return .generic } @@ -386,7 +384,7 @@ public func joinGroupCall(account: Account, peerId: PeerId, callId: Int64, acces |> mapError { _ -> JoinGroupCallError in return .generic }, - getGroupCallParticipants(account: account, callId: callId, accessHash: accessHash, offset: "", limit: 100) + getGroupCallParticipants(account: account, callId: callId, accessHash: accessHash, offset: "", ssrcs: [], limit: 100) |> mapError { _ -> JoinGroupCallError in return .generic }, @@ -760,7 +758,7 @@ public final class GroupCallParticipantsContext { private let id: Int64 private let accessHash: Int64 - private var hasReceivedSpeackingParticipantsReport: Bool = false + private var hasReceivedSpeakingParticipantsReport: Bool = false private var stateValue: InternalState { didSet { @@ -812,6 +810,10 @@ public final class GroupCallParticipantsContext { private let updatesDisposable = MetaDisposable() private var activitiesDisposable: Disposable? + private var isLoadingMore: Bool = false + private var shouldResetStateFromServer: Bool = false + private var missingSsrcs = Set() + private let updateDefaultMuteDisposable = MetaDisposable() public init(account: Account, peerId: PeerId, id: Int64, accessHash: Int64, state: State) { @@ -844,11 +846,12 @@ public final class GroupCallParticipantsContext { return } - strongSelf.activeSpeakersValue = Set(activities.map { item -> PeerId in + let peerIds = Set(activities.map { item -> PeerId in item.0 }) + strongSelf.activeSpeakersValue = peerIds - if !strongSelf.hasReceivedSpeackingParticipantsReport { + if !strongSelf.hasReceivedSpeakingParticipantsReport { var updatedParticipants = strongSelf.stateValue.state.participants var indexMap: [PeerId: Int] = [:] for i in 0 ..< updatedParticipants.count { @@ -925,9 +928,9 @@ public final class GroupCallParticipantsContext { } } - public func reportSpeakingParticipants(ids: [PeerId]) { + public func reportSpeakingParticipants(ids: [PeerId: UInt32]) { if !ids.isEmpty { - self.hasReceivedSpeackingParticipantsReport = true + self.hasReceivedSpeakingParticipantsReport = true } let strongSelf = self @@ -941,7 +944,7 @@ public final class GroupCallParticipantsContext { let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - for activityPeerId in ids { + for (activityPeerId, _) in ids { if let index = indexMap[activityPeerId] { var updateTimestamp = false if let activityTimestamp = updatedParticipants[index].activityTimestamp { @@ -982,6 +985,77 @@ public final class GroupCallParticipantsContext { overlayState: strongSelf.stateValue.overlayState ) } + + self.ensureHaveParticipants(ssrcs: Set(ids.map { $0.1 })) + } + + private func ensureHaveParticipants(ssrcs: Set) { + var missingSsrcs = Set() + + var existingSsrcs = Set() + for participant in self.stateValue.state.participants { + existingSsrcs.insert(participant.ssrc) + } + + for ssrc in ssrcs { + if !existingSsrcs.contains(ssrc) { + missingSsrcs.insert(ssrc) + } + } + + if !missingSsrcs.isEmpty { + self.missingSsrcs.formUnion(missingSsrcs) + self.loadMissingSsrcs() + } + } + + private func loadMissingSsrcs() { + if self.missingSsrcs.isEmpty { + return + } + if self.isLoadingMore { + return + } + self.isLoadingMore = true + + let ssrcs = self.missingSsrcs + + self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", ssrcs: Array(ssrcs), limit: 100) + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let strongSelf = self else { + return + } + strongSelf.isLoadingMore = false + + strongSelf.missingSsrcs.subtract(ssrcs) + + var updatedState = strongSelf.stateValue.state + + var existingParticipantIds = Set() + for participant in updatedState.participants { + existingParticipantIds.insert(participant.peer.id) + } + for participant in state.participants { + if existingParticipantIds.contains(participant.peer.id) { + continue + } + existingParticipantIds.insert(participant.peer.id) + updatedState.participants.append(participant) + } + + updatedState.participants.sort() + + updatedState.totalCount = max(updatedState.totalCount, state.totalCount) + updatedState.version = max(updatedState.version, updatedState.version) + + strongSelf.stateValue.state = updatedState + + if strongSelf.shouldResetStateFromServer { + strongSelf.resetStateFromServer() + } else { + strongSelf.loadMissingSsrcs() + } + })) } private func beginProcessingUpdatesIfNeeded() { @@ -1123,13 +1197,22 @@ public final class GroupCallParticipantsContext { } private func resetStateFromServer() { + if self.isLoadingMore { + self.shouldResetStateFromServer = true + return + } + + self.isLoadingMore = true + self.updateQueue.removeAll() - self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", limit: 100) + self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: "", ssrcs: [], limit: 100) |> deliverOnMainQueue).start(next: { [weak self] state in guard let strongSelf = self else { return } + strongSelf.isLoadingMore = false + strongSelf.shouldResetStateFromServer = false strongSelf.stateValue.state = state strongSelf.endedProcessingUpdate() })) @@ -1232,6 +1315,49 @@ public final class GroupCallParticipantsContext { strongSelf.account.stateManager.addUpdates(updates) })) } + + public func loadMore(token: String) { + if token != self.stateValue.state.nextParticipantsFetchOffset { + Logger.shared.log("GroupCallParticipantsContext", "loadMore called with an invalid token \(token) (the valid one is \(String(describing: self.stateValue.state.nextParticipantsFetchOffset)))") + return + } + if self.isLoadingMore { + return + } + self.isLoadingMore = true + + self.disposable.set((getGroupCallParticipants(account: self.account, callId: self.id, accessHash: self.accessHash, offset: token, ssrcs: [], limit: 100) + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let strongSelf = self else { + return + } + strongSelf.isLoadingMore = false + + var updatedState = strongSelf.stateValue.state + + var existingParticipantIds = Set() + for participant in updatedState.participants { + existingParticipantIds.insert(participant.peer.id) + } + for participant in state.participants { + if existingParticipantIds.contains(participant.peer.id) { + continue + } + existingParticipantIds.insert(participant.peer.id) + updatedState.participants.append(participant) + } + + updatedState.nextParticipantsFetchOffset = state.nextParticipantsFetchOffset + updatedState.totalCount = max(updatedState.totalCount, state.totalCount) + updatedState.version = max(updatedState.version, updatedState.version) + + strongSelf.stateValue.state = updatedState + + if strongSelf.shouldResetStateFromServer { + strongSelf.resetStateFromServer() + } + })) + } } extension GroupCallParticipantsContext.Update.StateUpdate.ParticipantUpdate { diff --git a/submodules/TelegramCore/Sources/NetworkType.swift b/submodules/TelegramCore/Sources/NetworkType.swift index 52e111a845..ab672ea004 100644 --- a/submodules/TelegramCore/Sources/NetworkType.swift +++ b/submodules/TelegramCore/Sources/NetworkType.swift @@ -34,12 +34,6 @@ extension CellularNetworkType { #endif -enum InternalNetworkType: Equatable { - case none - case wifi - case cellular -} - public enum NetworkType: Equatable { case none case wifi @@ -50,7 +44,7 @@ public enum NetworkType: Equatable { extension NetworkType { #if os(iOS) - init(internalType: InternalNetworkType, cellularType: CellularNetworkType) { + init(internalType: Reachability.NetworkType, cellularType: CellularNetworkType) { switch internalType { case .none: self = .none @@ -61,7 +55,7 @@ extension NetworkType { } } #else - init(internalType: InternalNetworkType) { + init(internalType: Reachability.NetworkType) { switch internalType { case .none: self = .none @@ -72,91 +66,11 @@ extension NetworkType { #endif } -private final class WrappedReachability: NSObject { - @objc private static func threadImpl() { - while true { - RunLoop.current.run(until: .distantFuture) - } - } - - static let thread: Thread = { - let thread = Thread(target: WrappedReachability.self, selector: #selector(WrappedReachability.threadImpl), object: nil) - thread.start() - return thread - }() - - @objc private static func dispatchOnThreadImpl(_ f: @escaping () -> Void) { - f() - } - - private static func dispatchOnThread(_ f: @escaping @convention(block) () -> Void) { - WrappedReachability.perform(#selector(WrappedReachability.dispatchOnThreadImpl(_:)), on: WrappedReachability.thread, with: f, waitUntilDone: false) - } - - private let reachability: Reachability - - let value: ValuePromise - - override init() { - assert(Thread.current === WrappedReachability.thread) - self.reachability = Reachability.forInternetConnection() - let type: InternalNetworkType - switch self.reachability.currentReachabilityStatus() { - case NotReachable: - type = .none - case ReachableViaWiFi: - type = .wifi - case ReachableViaWWAN: - type = .cellular - default: - type = .none - } - self.value = ValuePromise(type) - - super.init() - - self.reachability.reachabilityChanged = { [weak self] status in - WrappedReachability.dispatchOnThread { - guard let strongSelf = self else { - return - } - let internalNetworkType: InternalNetworkType - switch status { - case NotReachable: - internalNetworkType = .none - case ReachableViaWiFi: - internalNetworkType = .wifi - case ReachableViaWWAN: - internalNetworkType = .cellular - default: - internalNetworkType = .none - } - strongSelf.value.set(internalNetworkType) - } - } - self.reachability.startNotifier() - } - - static var valueRef: Unmanaged? - - static func withInstance(_ f: @escaping (WrappedReachability) -> Void) { - WrappedReachability.dispatchOnThread { - if self.valueRef == nil { - self.valueRef = Unmanaged.passRetained(WrappedReachability()) - } - if let valueRef = self.valueRef { - let value = valueRef.takeUnretainedValue() - f(value) - } - } - } -} - private final class NetworkTypeManagerImpl { let queue: Queue let updated: (NetworkType) -> Void var networkTypeDisposable: Disposable? - var currentNetworkType: InternalNetworkType? + var currentNetworkType: Reachability.NetworkType? var networkType: NetworkType? #if os(iOS) var currentCellularType: CellularNetworkType @@ -196,29 +110,27 @@ private final class NetworkTypeManagerImpl { let networkTypeDisposable = MetaDisposable() self.networkTypeDisposable = networkTypeDisposable - - WrappedReachability.withInstance({ [weak self] impl in - networkTypeDisposable.set((impl.value.get() - |> deliverOn(queue)).start(next: { networkStatus in - guard let strongSelf = self else { - return + + networkTypeDisposable.set((Reachability.networkType + |> deliverOn(queue)).start(next: { [weak self] networkStatus in + guard let strongSelf = self else { + return + } + if strongSelf.currentNetworkType != networkStatus { + strongSelf.currentNetworkType = networkStatus + + let networkType: NetworkType + #if os(iOS) + networkType = NetworkType(internalType: networkStatus, cellularType: strongSelf.currentCellularType) + #else + networkType = NetworkType(internalType: networkStatus) + #endif + if strongSelf.networkType != networkType { + strongSelf.networkType = networkType + updated(networkType) } - if strongSelf.currentNetworkType != networkStatus { - strongSelf.currentNetworkType = networkStatus - - let networkType: NetworkType - #if os(iOS) - networkType = NetworkType(internalType: networkStatus, cellularType: strongSelf.currentCellularType) - #else - networkType = NetworkType(internalType: networkStatus) - #endif - if strongSelf.networkType != networkType { - strongSelf.networkType = networkType - updated(networkType) - } - } - })) - }) + } + })) } func stop() { diff --git a/versions.json b/versions.json index 759565ff78..b826ca7fc4 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "7.4.1", + "app": "7.4.2", "bazel": "3.7.0", "xcode": "12.3" }