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

This commit is contained in:
Ilya Laktyushin 2022-04-15 03:31:49 +04:00
commit 92d53e6ca6
11 changed files with 386 additions and 132 deletions

View File

@ -351,6 +351,7 @@ public final class NavigateToChatControllerParams {
public let activateInput: Bool
public let keepStack: NavigateToChatKeepStack
public let useExisting: Bool
public let useBackAnimation: Bool
public let purposefulAction: (() -> Void)?
public let scrollToEndIfExists: Bool
public let activateMessageSearch: (ChatSearchDomain, String)?
@ -366,7 +367,7 @@ public final class NavigateToChatControllerParams {
public let setupController: (ChatController) -> Void
public let completion: (ChatController) -> Void
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, completion: @escaping (ChatController) -> Void = { _ in }) {
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, attachBotStart: ChatControllerInitialAttachBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, useBackAnimation: Bool = false, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, chatListFilter: Int32? = nil, chatNavigationStack: [PeerId] = [], changeColors: Bool = false, setupController: @escaping (ChatController) -> Void = { _ in }, completion: @escaping (ChatController) -> Void = { _ in }) {
self.navigationController = navigationController
self.chatController = chatController
self.chatLocationContextHolder = chatLocationContextHolder
@ -379,6 +380,7 @@ public final class NavigateToChatControllerParams {
self.activateInput = activateInput
self.keepStack = keepStack
self.useExisting = useExisting
self.useBackAnimation = useBackAnimation
self.purposefulAction = purposefulAction
self.scrollToEndIfExists = scrollToEndIfExists
self.activateMessageSearch = activateMessageSearch

View File

@ -160,7 +160,7 @@ class BotCheckoutHeaderItemNode: ListViewItemNode {
var imageApply: (() -> Void)?
var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
if let photo = item.invoice.photo, let dimensions = photo.dimensions {
let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: dimensions.cgSize.aspectFilled(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor)
let arguments = TransformImageArguments(corners: ImageCorners(radius: 4.0), imageSize: dimensions.cgSize.aspectFilled(imageSize), boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor)
imageApply = makeImageLayout(arguments)
maxTextWidth = max(1.0, maxTextWidth - imageSize.width - imageTextSpacing)
if imageUpdated {

View File

@ -352,7 +352,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
let accountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|> take(1)
self.suggestedFiltersDisposable.set((combineLatest(suggestedPeers, self.suggestedDates.get(), self.selectedFilterPromise.get(), self.searchQuery.get(), accountPeer)
|> mapToSignal { peers, dates, selectedFilter, searchQuery, accountPeer -> Signal<([EnginePeer], [(Date?, Date, String?)], ChatListSearchFilterEntryId?, String?, EnginePeer?), NoError> in
if searchQuery?.isEmpty ?? true {

View File

@ -275,6 +275,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
case recentlyDownloaded
}
case recentlySearchedPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)
case localPeer(EnginePeer, EnginePeer?, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case globalPeer(FoundPeer, (Int32, Bool)?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder, ChatListSearchSectionExpandType)
case message(EngineMessage, EngineRenderedPeer, EnginePeerReadCounters?, ChatListPresentationData, Int32, Bool?, Bool, MessageOrderingKey, (id: String, size: Int, isFirstInList: Bool)?, MessageSection, Bool)
@ -282,126 +283,219 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
public var stableId: ChatListSearchEntryStableId {
switch self {
case let .localPeer(peer, _, _, _, _, _, _, _, _):
return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id)
case let .message(message, _, _, _, _, _, _, _, _, section, _):
return .messageId(message.id, section)
case .addContact:
return .addContact
case let .recentlySearchedPeer(peer, _, _, _, _, _, _, _):
return .localPeerId(peer.id)
case let .localPeer(peer, _, _, _, _, _, _, _, _):
return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id)
case let .message(message, _, _, _, _, _, _, _, _, section, _):
return .messageId(message.id, section)
case .addContact:
return .addContact
}
}
public static func ==(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool {
switch lhs {
case let .localPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType):
if case let .localPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
return true
} else {
case let .recentlySearchedPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder):
if case let .recentlySearchedPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 {
return true
} else {
return false
}
case let .localPeer(lhsPeer, lhsAssociatedPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType):
if case let .localPeer(rhsPeer, rhsAssociatedPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
return true
} else {
return false
}
case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType):
if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
return true
} else {
return false
}
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader, lhsKey, lhsResourceId, lhsSection, lhsAllPaused):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader, rhsKey, rhsResourceId, rhsSection, rhsAllPaused) = rhs {
if lhsMessage.id != rhsMessage.id {
return false
}
case let .globalPeer(lhsPeer, lhsUnreadBadge, lhsIndex, lhsTheme, lhsStrings, lhsSortOrder, lhsDisplayOrder, lhsExpandType):
if case let .globalPeer(rhsPeer, rhsUnreadBadge, rhsIndex, rhsTheme, rhsStrings, rhsSortOrder, rhsDisplayOrder, rhsExpandType) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex && lhsTheme === rhsTheme && lhsStrings === rhsStrings && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsUnreadBadge?.0 == rhsUnreadBadge?.0 && lhsUnreadBadge?.1 == rhsUnreadBadge?.1 && lhsExpandType == rhsExpandType {
return true
} else {
if lhsMessage.stableVersion != rhsMessage.stableVersion {
return false
}
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData, lhsTotalCount, lhsSelected, lhsDisplayCustomHeader, lhsKey, lhsResourceId, lhsSection, lhsAllPaused):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData, rhsTotalCount, rhsSelected, rhsDisplayCustomHeader, rhsKey, rhsResourceId, rhsSection, rhsAllPaused) = rhs {
if lhsMessage.id != rhsMessage.id {
return false
}
if lhsMessage.stableVersion != rhsMessage.stableVersion {
return false
}
if lhsPeer != rhsPeer {
return false
}
if lhsPresentationData !== rhsPresentationData {
return false
}
if lhsCombinedPeerReadState != rhsCombinedPeerReadState {
return false
}
if lhsTotalCount != rhsTotalCount {
return false
}
if lhsSelected != rhsSelected {
return false
}
if lhsDisplayCustomHeader != rhsDisplayCustomHeader {
return false
}
if lhsKey != rhsKey {
return false
}
if lhsResourceId?.0 != rhsResourceId?.0 {
return false
}
if lhsResourceId?.1 != rhsResourceId?.1 {
return false
}
if lhsSection != rhsSection {
return false
}
if lhsAllPaused != rhsAllPaused {
return false
}
return true
} else {
if lhsPeer != rhsPeer {
return false
}
case let .addContact(lhsPhoneNumber, lhsTheme, lhsStrings):
if case let .addContact(rhsPhoneNumber, rhsTheme, rhsStrings) = rhs {
if lhsPhoneNumber != rhsPhoneNumber {
return false
}
if lhsTheme !== rhsTheme {
return false
}
if lhsStrings !== rhsStrings {
return false
}
return true
} else {
if lhsPresentationData !== rhsPresentationData {
return false
}
if lhsCombinedPeerReadState != rhsCombinedPeerReadState {
return false
}
if lhsTotalCount != rhsTotalCount {
return false
}
if lhsSelected != rhsSelected {
return false
}
if lhsDisplayCustomHeader != rhsDisplayCustomHeader {
return false
}
if lhsKey != rhsKey {
return false
}
if lhsResourceId?.0 != rhsResourceId?.0 {
return false
}
if lhsResourceId?.1 != rhsResourceId?.1 {
return false
}
if lhsSection != rhsSection {
return false
}
if lhsAllPaused != rhsAllPaused {
return false
}
return true
} else {
return false
}
case let .addContact(lhsPhoneNumber, lhsTheme, lhsStrings):
if case let .addContact(rhsPhoneNumber, rhsTheme, rhsStrings) = rhs {
if lhsPhoneNumber != rhsPhoneNumber {
return false
}
if lhsTheme !== rhsTheme {
return false
}
if lhsStrings !== rhsStrings {
return false
}
return true
} else {
return false
}
}
}
public static func <(lhs: ChatListSearchEntry, rhs: ChatListSearchEntry) -> Bool {
switch lhs {
case let .localPeer(_, _, _, lhsIndex, _, _, _, _, _):
if case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex
} else {
return true
}
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _):
switch rhs {
case .localPeer:
return false
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _):
return lhsIndex <= rhsIndex
case .message, .addContact:
return true
}
case let .message(_, _, _, _, _, _, _, lhsKey, _, _, _):
if case let .message(_, _, _, _, _, _, _, rhsKey, _, _, _) = rhs {
return lhsKey < rhsKey
} else if case .addContact = rhs {
return true
} else {
return false
}
case .addContact:
case let .recentlySearchedPeer(_, _, _, lhsIndex, _, _, _, _):
if case let .recentlySearchedPeer(_, _, _, rhsIndex, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex
} else {
return true
}
case let .localPeer(_, _, _, lhsIndex, _, _, _, _, _):
switch rhs {
case .recentlySearchedPeer:
return false
case let .localPeer(_, _, _, rhsIndex, _, _, _, _, _):
return lhsIndex <= rhsIndex
case .globalPeer, .message, .addContact:
return true
}
case let .globalPeer(_, _, lhsIndex, _, _, _, _, _):
switch rhs {
case .recentlySearchedPeer, .localPeer:
return false
case let .globalPeer(_, _, rhsIndex, _, _, _, _, _):
return lhsIndex <= rhsIndex
case .message, .addContact:
return true
}
case let .message(_, _, _, _, _, _, _, lhsKey, _, _, _):
if case let .message(_, _, _, _, _, _, _, rhsKey, _, _, _) = rhs {
return lhsKey < rhsKey
} else if case .addContact = rhs {
return true
} else {
return false
}
case .addContact:
return false
}
}
public func item(context: AccountContext, presentationData: PresentationData, enableHeaders: Bool, filter: ChatListNodePeersFilter, key: ChatListSearchPaneKey, tagMask: EngineMessage.Tags?, interaction: ChatListNodeInteraction, listInteraction: ListMessageItemInteraction, peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?) -> Void)?, toggleExpandLocalResults: @escaping () -> Void, toggleExpandGlobalResults: @escaping () -> Void, searchPeer: @escaping (EnginePeer) -> Void, searchQuery: String?, searchOptions: ChatListSearchOptions?, messageContextAction: ((EngineMessage, ASDisplayNode?, CGRect?, UIGestureRecognizer?, ChatListSearchPaneKey, (id: String, size: Int, isFirstInList: Bool)?) -> Void)?, openClearRecentlyDownloaded: @escaping () -> Void, toggleAllPaused: @escaping () -> Void) -> ListViewItem {
switch self {
case let .recentlySearchedPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder):
let primaryPeer: EnginePeer
var chatPeer: EnginePeer?
if let associatedPeer = associatedPeer {
primaryPeer = associatedPeer
chatPeer = peer
} else {
primaryPeer = peer
chatPeer = peer
}
var enabled = true
if filter.contains(.onlyWriteable) {
if let peer = chatPeer {
enabled = canSendMessagesToPeer(peer._asPeer())
} else {
enabled = false
}
}
if filter.contains(.onlyPrivateChats) {
if let peer = chatPeer {
switch peer {
case .user, .secretChat:
break
default:
enabled = false
}
} else {
enabled = false
}
}
if filter.contains(.onlyGroups) {
if let peer = chatPeer {
if case .legacyGroup = peer {
} else if case let .channel(peer) = peer, case .group = peer.info {
} else {
enabled = false
}
} else {
enabled = false
}
}
var badge: ContactsPeerItemBadge?
if let unreadBadge = unreadBadge {
badge = ContactsPeerItemBadge(count: unreadBadge.0, type: unreadBadge.1 ? .inactive : .active)
}
let header: ChatListSearchItemHeader?
if filter.contains(.removeSearchHeader) {
header = nil
} else {
let headerType: ChatListSearchItemHeaderType
if filter.contains(.onlyGroups) {
headerType = .chats
} else {
headerType = .recentPeers
}
header = ChatListSearchItemHeader(type: headerType, theme: theme, strings: strings, actionTitle: nil, action: nil)
}
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .generalSearch, peer: .peer(peer: primaryPeer, chatPeer: chatPeer), status: .none, badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { contactPeer in
if case let .peer(maybePeer, maybeChatPeer) = contactPeer, let peer = maybePeer, let chatPeer = maybeChatPeer {
interaction.peerSelected(chatPeer, peer, nil)
} else {
interaction.peerSelected(peer, nil, nil)
}
}, contextAction: peerContextAction.flatMap { peerContextAction in
return { node, gesture in
if let chatPeer = chatPeer, chatPeer.id.namespace != Namespaces.Peer.SecretChat {
peerContextAction(chatPeer, .search(nil), node, gesture)
} else {
gesture?.cancel()
}
}
}, arrowAction: nil)
case let .localPeer(peer, associatedPeer, unreadBadge, _, theme, strings, nameSortOrder, nameDisplayOrder, expandType):
let primaryPeer: EnginePeer
var chatPeer: EnginePeer?
@ -1089,27 +1183,53 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
let accountPeer = context.account.postbox.loadedPeerWithId(context.account.peerId) |> take(1)
let foundLocalPeers: Signal<(peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)]), NoError>
let foundLocalPeers: Signal<(peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set<EnginePeer.Id>), NoError>
if let query = query {
foundLocalPeers = context.engine.contacts.searchLocalPeers(query: query.lowercased())
|> mapToSignal { local -> Signal<([EnginePeer.Id: Optional<EnginePeer.NotificationSettings>], [EnginePeer.Id: Int], [EngineRenderedPeer]), NoError> in
foundLocalPeers = combineLatest(
context.engine.contacts.searchLocalPeers(query: query.lowercased()),
context.engine.peers.recentlySearchedPeers()
)
|> mapToSignal { local, allRecentlySearched -> Signal<([EnginePeer.Id: Optional<EnginePeer.NotificationSettings>], [EnginePeer.Id: Int], [EngineRenderedPeer], Set<EnginePeer.Id>), NoError> in
let recentlySearched = allRecentlySearched.filter { peer in
guard let peer = peer.peer.peer else {
return false
}
return peer.indexName.matchesByTokens(query)
}
var peerIds = Set<EnginePeer.Id>()
var peers: [EngineRenderedPeer] = []
for peer in recentlySearched {
if !peerIds.contains(peer.peer.peerId) {
peerIds.insert(peer.peer.peerId)
peers.append(EngineRenderedPeer(peer.peer))
}
}
for peer in local {
if !peerIds.contains(peer.peerId) {
peerIds.insert(peer.peerId)
peers.append(peer)
}
}
return context.engine.data.subscribe(
EngineDataMap(
local.map { peer -> TelegramEngine.EngineData.Item.Peer.NotificationSettings in
return TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peer.peerId)
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Peer.NotificationSettings in
return TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId)
}
),
EngineDataMap(
local.map { peer -> TelegramEngine.EngineData.Item.Messages.PeerUnreadCount in
return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peer.peerId)
peerIds.map { peerId -> TelegramEngine.EngineData.Item.Messages.PeerUnreadCount in
return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId)
}
)
)
|> map { notificationSettings, unreadCounts in
return (notificationSettings, unreadCounts, local)
return (notificationSettings, unreadCounts, peers, Set(recentlySearched.map(\.peer.peerId)))
}
}
|> map { notificationSettings, unreadCounts, peers -> (peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)]) in
|> map { notificationSettings, unreadCounts, peers, recentlySearchedPeerIds -> (peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set<EnginePeer.Id>) in
var unread: [EnginePeer.Id: (Int32, Bool)] = [:]
for peer in peers {
var isMuted: Bool = false
@ -1127,10 +1247,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
unread[peer.peerId] = (Int32(unreadCount), isMuted)
}
}
return (peers: peers, unread: unread)
return (peers: peers, unread: unread, recentlySearchedPeerIds: recentlySearchedPeerIds)
}
} else {
foundLocalPeers = .single((peers: [], unread: [:]))
foundLocalPeers = .single((peers: [], unread: [:], recentlySearchedPeerIds: Set()))
}
let foundRemotePeers: Signal<([FoundPeer], [FoundPeer], Bool), NoError>
@ -1319,6 +1439,26 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
}
for renderedPeer in foundLocalPeers.peers {
if !foundLocalPeers.recentlySearchedPeerIds.contains(renderedPeer.peerId) {
continue
}
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, EnginePeer(accountPeer)) {
if !existingPeerIds.contains(peer.id) {
existingPeerIds.insert(peer.id)
var associatedPeer: EnginePeer?
if case let .secretChat(secretChat) = peer, let associatedPeerId = secretChat.associatedPeerId {
associatedPeer = renderedPeer.peers[associatedPeerId]
}
entries.append(.recentlySearchedPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder))
index += 1
}
}
}
if lowercasedQuery.count > 1 {
for peer in recentPeers {
if let peer = peer.peer.chatMainPeer, !existingPeerIds.contains(peer.id) {
@ -1348,6 +1488,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if case .expand = localExpandType, numberOfLocalPeers >= 3 {
break
}
if foundLocalPeers.recentlySearchedPeerIds.contains(renderedPeer.peerId) {
continue
}
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != context.account.peerId, filteredPeer(peer, EnginePeer(accountPeer)) {
if !existingPeerIds.contains(peer.id) {
@ -1356,6 +1499,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
if case let .secretChat(secretChat) = peer, let associatedPeerId = secretChat.associatedPeerId {
associatedPeer = renderedPeer.peers[associatedPeerId]
}
entries.append(.localPeer(peer, associatedPeer, foundLocalPeers.unread[peer.id], index, presentationData.theme, presentationData.strings, presentationData.nameSortOrder, presentationData.nameDisplayOrder, localExpandType))
index += 1
numberOfLocalPeers += 1

View File

@ -1274,6 +1274,12 @@ open class NavigationController: UINavigationController, ContainableController,
completion()
}
public func replaceControllers(controllers: [UIViewController], animated: Bool, options: NavigationAnimationOptions = [], ready: ValuePromise<Bool>? = nil, completion: @escaping () -> Void = {}) {
ready?.set(true)
self.setViewControllers(controllers, animated: animated)
completion()
}
public func replaceAllButRootController(_ controller: ViewController, animated: Bool, animationOptions: NavigationAnimationOptions = [], ready: ValuePromise<Bool>? = nil, completion: @escaping () -> Void = {}) {
ready?.set(true)
var controllers = self.viewControllers

View File

@ -33,6 +33,8 @@
#import <MtProtoKit/MTSerialization.h>
#import <MtProtoKit/MTEncryption.h>
#import <MtProtoKit/MTTimer.h>
#import "MTBuffer.h"
#import "MTInternalMessageParser.h"
#import "MTMsgContainerMessage.h"
@ -125,6 +127,9 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
bool _isProbing;
MTMetaDisposable *_probingDisposable;
NSNumber *_probingStatus;
bool _isConnectionThrottled;
MTTimer *_unthrottleConnectionTimer;
}
@end
@ -625,8 +630,9 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64;
{
_willRequestTransactionOnNextQueuePass = false;
if ([self isStopped] || [self isPaused])
if ([self isStopped] || [self isPaused] || _isConnectionThrottled) {
return;
}
if (_transport == nil)
[self resetTransport];
@ -1940,8 +1946,6 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
id currentTransport = _transport;
[self transportTransactionsMayHaveFailed:transport transactionIds:@[transactionId]];
for (NSInteger i = (NSInteger)_messageServices.count - 1; i >= 0; i--)
{
id<MTMessageService> messageService = _messageServices[(NSUInteger)i];
@ -1954,8 +1958,28 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
[self handleMissingKey:scheme];
}
if (currentTransport == _transport)
[self requestSecureTransportReset];
if (protocolErrorCode == -429) {
_isConnectionThrottled = true;
if (_unthrottleConnectionTimer == nil) {
__weak MTProto *weakSelf = self;
_unthrottleConnectionTimer = [[MTTimer alloc] initWithTimeout:5.0 repeat:false completion:^{
__strong MTProto *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
strongSelf->_isConnectionThrottled = false;
strongSelf->_unthrottleConnectionTimer = nil;
[strongSelf requestTransportTransaction];
} queue:[MTProto managerQueue].nativeQueue];
}
} else {
if (currentTransport == _transport) {
[self requestSecureTransportReset];
}
[self transportTransactionsMayHaveFailed:transport transactionIds:@[transactionId]];
}
return;
}

View File

@ -110,9 +110,11 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
self.contentContainer.addSubnode(self.scrollNode)
self.contentContainerMask = UIImageView()
let maskGradientWidth: CGFloat = 10.0
self.contentContainerMask.image = generateImage(CGSize(width: maskGradientWidth * 2.0 + 1.0, height: 8.0), rotatedContext: { size, context in
self.contentContainerMask.image = generateImage(CGSize(width: 52.0, height: 52.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: 1.1)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
let shadowColor = UIColor.black
@ -122,17 +124,21 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
for i in 0 ... stepCount {
let t = CGFloat(i) / CGFloat(stepCount)
colors.append(shadowColor.withAlphaComponent(t * t).cgColor)
colors.append(shadowColor.withAlphaComponent(t).cgColor)
locations.append(t)
}
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colors as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: maskGradientWidth, y: 0.0), options: CGGradientDrawingOptions())
context.drawLinearGradient(gradient, start: CGPoint(x: size.width, y: 0.0), end: CGPoint(x: maskGradientWidth + 1.0, y: 0.0), options: CGGradientDrawingOptions())
let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
let gradientWidth = 6.0
context.drawRadialGradient(gradient, startCenter: center, startRadius: size.width / 2.0, endCenter: center, endRadius: size.width / 2.0 - gradientWidth, options: [])
context.setFillColor(shadowColor.cgColor)
context.fill(CGRect(origin: CGPoint(x: maskGradientWidth, y: 0.0), size: CGSize(width: 1.0, height: size.height)))
})?.stretchableImage(withLeftCapWidth: Int(maskGradientWidth), topCapHeight: 0)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: gradientWidth - 1.0, dy: gradientWidth - 1.0))
})?.stretchableImage(withLeftCapWidth: Int(52.0 / 2.0), topCapHeight: Int(52.0 / 2.0))
self.contentContainer.view.mask = self.contentContainerMask
//self.contentContainer.view.addSubview(self.contentContainerMask)
super.init()

View File

@ -567,6 +567,8 @@ public final class MediaStreamComponent: CombinedComponent {
private var scheduledDismissUITimer: SwiftSignalKit.Timer?
let deactivatePictureInPictureIfVisible = StoredActionSlot(Void.self)
init(call: PresentationGroupCallImpl) {
self.call = call
@ -639,8 +641,6 @@ public final class MediaStreamComponent: CombinedComponent {
}
})
//let _ = call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: call.peerId, revokePreviousCredentials: false).start()
self.isVisibleInHierarchyDisposable = (call.accountContext.sharedContext.applicationBindings.applicationInForeground
|> deliverOnMainQueue).start(next: { [weak self] inForeground in
guard let strongSelf = self else {
@ -649,6 +649,16 @@ public final class MediaStreamComponent: CombinedComponent {
if strongSelf.isVisibleInHierarchy != inForeground {
strongSelf.isVisibleInHierarchy = inForeground
strongSelf.updated(transition: .immediate)
if inForeground {
Queue.mainQueue().after(0.5, {
guard let strongSelf = self, strongSelf.isVisibleInHierarchy else {
return
}
strongSelf.deactivatePictureInPictureIfVisible.invoke(Void())
})
}
}
})
}
@ -705,6 +715,7 @@ public final class MediaStreamComponent: CombinedComponent {
let toolbar = Child(ToolbarComponent.self)
let activatePictureInPicture = StoredActionSlot(Action<Void>.self)
let deactivatePictureInPicture = StoredActionSlot(Void.self)
let moreButtonTag = GenericComponentViewTag()
let moreAnimationTag = GenericComponentViewTag()
@ -725,6 +736,16 @@ public final class MediaStreamComponent: CombinedComponent {
let state = context.state
let controller = environment.controller
context.state.deactivatePictureInPictureIfVisible.connect {
guard let controller = controller() else {
return
}
if controller.view.window == nil {
return
}
deactivatePictureInPicture.invoke(Void())
}
let video = video.update(
component: MediaStreamVideoComponent(
call: context.component.call,
@ -733,6 +754,7 @@ public final class MediaStreamComponent: CombinedComponent {
isAdmin: context.state.canManageCall,
peerTitle: context.state.peerTitle,
activatePictureInPicture: activatePictureInPicture,
deactivatePictureInPicture: deactivatePictureInPicture,
bringBackControllerForPictureInPictureDeactivation: { [weak call] completed in
guard let call = call else {
completed()
@ -742,6 +764,9 @@ public final class MediaStreamComponent: CombinedComponent {
call.accountContext.sharedContext.mainWindow?.inCallNavigate?()
completed()
},
pictureInPictureClosed: { [weak call] in
let _ = call?.leave(terminateIfPossible: false)
}
),
availableSize: context.availableSize,
@ -1095,8 +1120,13 @@ public final class MediaStreamComponent: CombinedComponent {
state.updateDismissOffset(value: offset.y, interactive: true)
case let .ended(velocity):
if abs(velocity.y) > 200.0 {
state.updateDismissOffset(value: velocity.y < 0 ? -height : height, interactive: false)
(controller() as? MediaStreamComponentController)?.dismiss(closing: false, manual: true)
activatePictureInPicture.invoke(Action { [weak state] in
guard let state = state, let controller = controller() as? MediaStreamComponentController else {
return
}
state.updateDismissOffset(value: velocity.y < 0 ? -height : height, interactive: false)
controller.dismiss(closing: false, manual: true)
})
} else {
state.updateDismissOffset(value: 0.0, interactive: false)
}

View File

@ -14,16 +14,30 @@ final class MediaStreamVideoComponent: Component {
let isAdmin: Bool
let peerTitle: String
let activatePictureInPicture: ActionSlot<Action<Void>>
let deactivatePictureInPicture: ActionSlot<Void>
let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void
let pictureInPictureClosed: () -> Void
init(call: PresentationGroupCallImpl, hasVideo: Bool, isVisible: Bool, isAdmin: Bool, peerTitle: String, activatePictureInPicture: ActionSlot<Action<Void>>, bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void) {
init(
call: PresentationGroupCallImpl,
hasVideo: Bool,
isVisible: Bool,
isAdmin: Bool,
peerTitle: String,
activatePictureInPicture: ActionSlot<Action<Void>>,
deactivatePictureInPicture: ActionSlot<Void>,
bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void,
pictureInPictureClosed: @escaping () -> Void
) {
self.call = call
self.hasVideo = hasVideo
self.isVisible = isVisible
self.isAdmin = isAdmin
self.peerTitle = peerTitle
self.activatePictureInPicture = activatePictureInPicture
self.deactivatePictureInPicture = deactivatePictureInPicture
self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation
self.pictureInPictureClosed = pictureInPictureClosed
}
public static func ==(lhs: MediaStreamVideoComponent, rhs: MediaStreamVideoComponent) -> Bool {
@ -72,6 +86,8 @@ final class MediaStreamVideoComponent: Component {
private var component: MediaStreamVideoComponent?
private var hadVideo: Bool = false
private var requestedExpansion: Bool = false
private var noSignalTimer: Timer?
private var noSignalTimeout: Bool = false
@ -101,7 +117,10 @@ final class MediaStreamVideoComponent: Component {
}
func expandFromPictureInPicture() {
self.pictureInPictureController?.stopPictureInPicture()
if let pictureInPictureController = self.pictureInPictureController, pictureInPictureController.isPictureInPictureActive {
self.requestedExpansion = true
self.pictureInPictureController?.stopPictureInPicture()
}
}
func update(component: MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
@ -290,6 +309,14 @@ final class MediaStreamVideoComponent: Component {
completion(Void())
}
component.deactivatePictureInPicture.connect { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.expandFromPictureInPicture()
}
return availableSize
}
@ -308,6 +335,14 @@ final class MediaStreamVideoComponent: Component {
self.state?.updated(transition: .immediate)
}
func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
if self.requestedExpansion {
self.requestedExpansion = false
} else {
self.component?.pictureInPictureClosed()
}
}
func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
self.state?.updated(transition: .immediate)
}

View File

@ -8861,7 +8861,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
updatedChatNavigationStack.removeSubrange(0 ..< (index + 1))
}
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), animated: true, chatListFilter: nextFolderId, chatNavigationStack: updatedChatNavigationStack, completion: { nextController in
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(id: peer.id), useBackAnimation: true, animated: true, chatListFilter: nextFolderId, chatNavigationStack: updatedChatNavigationStack, completion: { nextController in
let _ = nextController
}))
})))

View File

@ -122,9 +122,16 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
params.completion(controller)
})
} else {
params.navigationController.replaceControllersAndPush(controllers: viewControllers, controller: controller, animated: params.animated, options: params.options, completion: {
params.completion(controller)
})
if params.useBackAnimation {
params.navigationController.viewControllers = [controller] + params.navigationController.viewControllers
params.navigationController.replaceControllers(controllers: viewControllers + [controller], animated: params.animated, options: params.options, completion: {
params.completion(controller)
})
} else {
params.navigationController.replaceControllersAndPush(controllers: viewControllers, controller: controller, animated: params.animated, options: params.options, completion: {
params.completion(controller)
})
}
}
}
if params.activateInput {