Various fixes

This commit is contained in:
Ilya Laktyushin 2024-04-15 00:54:55 +04:00
parent 0d1d0c265d
commit ead45405b7
25 changed files with 287 additions and 80 deletions

View File

@ -12050,3 +12050,5 @@ Sorry for the inconvenience.";
"MediaEditor.NewStickerPack.Title" = "New Sticker Set"; "MediaEditor.NewStickerPack.Title" = "New Sticker Set";
"MediaEditor.NewStickerPack.Text" = "Choose a name for your sticker set."; "MediaEditor.NewStickerPack.Text" = "Choose a name for your sticker set.";
"Premium.Gift.ContactSelection.SendMessage" = "Send Message";
"Premium.Gift.ContactSelection.OpenProfile" = "Open Profile";

View File

@ -637,6 +637,7 @@ public enum ContactListAction: Equatable {
case generic case generic
case voiceCall case voiceCall
case videoCall case videoCall
case more
} }
public enum ContactListPeer: Equatable { public enum ContactListPeer: Equatable {

View File

@ -102,8 +102,10 @@ public final class ContactMultiselectionControllerParams {
public let alwaysEnabled: Bool public let alwaysEnabled: Bool
public let limit: Int32? public let limit: Int32?
public let reachedLimit: ((Int32) -> Void)? public let reachedLimit: ((Int32) -> Void)?
public let openProfile: ((EnginePeer) -> Void)?
public let sendMessage: ((EnginePeer) -> Void)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool = false, isGroupInvitation: Bool = false, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], onlyWriteable: Bool = false, isGroupInvitation: Bool = false, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, attemptDisabledItemSelection: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil, openProfile: ((EnginePeer) -> Void)? = nil, sendMessage: ((EnginePeer) -> Void)? = nil) {
self.context = context self.context = context
self.updatedPresentationData = updatedPresentationData self.updatedPresentationData = updatedPresentationData
self.mode = mode self.mode = mode
@ -116,6 +118,8 @@ public final class ContactMultiselectionControllerParams {
self.alwaysEnabled = alwaysEnabled self.alwaysEnabled = alwaysEnabled
self.limit = limit self.limit = limit
self.reachedLimit = reachedLimit self.reachedLimit = reachedLimit
self.openProfile = openProfile
self.sendMessage = sendMessage
} }
} }

View File

@ -45,6 +45,7 @@ swift_library(
"//submodules/TooltipUI", "//submodules/TooltipUI",
"//submodules/UndoUI", "//submodules/UndoUI",
"//submodules/TelegramIntents", "//submodules/TelegramIntents",
"//submodules/ContextUI",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -23,6 +23,7 @@ import AppBundle
import ContextUI import ContextUI
import PhoneNumberFormat import PhoneNumberFormat
import LocalizedPeerData import LocalizedPeerData
import ContextUI
private let dropDownIcon = { () -> UIImage in private let dropDownIcon = { () -> UIImage in
UIGraphicsBeginImageContextWithOptions(CGSize(width: 12.0, height: 12.0), false, 0.0) UIGraphicsBeginImageContextWithOptions(CGSize(width: 12.0, height: 12.0), false, 0.0)
@ -56,7 +57,7 @@ private final class ContactListNodeInteraction {
fileprivate let activateSearch: () -> Void fileprivate let activateSearch: () -> Void
fileprivate let authorize: () -> Void fileprivate let authorize: () -> Void
fileprivate let suppressWarning: () -> Void fileprivate let suppressWarning: () -> Void
fileprivate let openPeer: (ContactListPeer, ContactListAction) -> Void fileprivate let openPeer: (ContactListPeer, ContactListAction, ASDisplayNode?, ContextGesture?) -> Void
fileprivate let openDisabledPeer: (EnginePeer, ChatListDisabledPeerReason) -> Void fileprivate let openDisabledPeer: (EnginePeer, ChatListDisabledPeerReason) -> Void
fileprivate let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)? fileprivate let contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)?
fileprivate let openStories: (EnginePeer, ASDisplayNode) -> Void fileprivate let openStories: (EnginePeer, ASDisplayNode) -> Void
@ -65,7 +66,7 @@ private final class ContactListNodeInteraction {
let itemHighlighting = ContactItemHighlighting() let itemHighlighting = ContactItemHighlighting()
init(activateSearch: @escaping () -> Void, authorize: @escaping () -> Void, suppressWarning: @escaping () -> Void, openPeer: @escaping (ContactListPeer, ContactListAction) -> Void, openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)?, openStories: @escaping (EnginePeer, ASDisplayNode) -> Void, deselectAll: @escaping () -> Void, toggleSelection: @escaping ([EnginePeer], Bool) -> Void) { init(activateSearch: @escaping () -> Void, authorize: @escaping () -> Void, suppressWarning: @escaping () -> Void, openPeer: @escaping (ContactListPeer, ContactListAction, ASDisplayNode?, ContextGesture?) -> Void, openDisabledPeer: @escaping (EnginePeer, ChatListDisabledPeerReason) -> Void, contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)?, openStories: @escaping (EnginePeer, ASDisplayNode) -> Void, deselectAll: @escaping () -> Void, toggleSelection: @escaping ([EnginePeer], Bool) -> Void) {
self.activateSearch = activateSearch self.activateSearch = activateSearch
self.authorize = authorize self.authorize = authorize
self.suppressWarning = suppressWarning self.suppressWarning = suppressWarning
@ -96,7 +97,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
case permissionInfo(PresentationTheme, String, String, Bool) case permissionInfo(PresentationTheme, String, String, Bool)
case permissionEnable(PresentationTheme, String) case permissionEnable(PresentationTheme, String)
case option(Int, ContactListAdditionalOption, ListViewItemHeader?, PresentationTheme, PresentationStrings) case option(Int, ContactListAdditionalOption, ListViewItemHeader?, PresentationTheme, PresentationStrings)
case peer(Int, ContactListPeer, EnginePeer.Presence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool, Bool, StoryData?, Bool) case peer(Int, ContactListPeer, EnginePeer.Presence?, ListViewItemHeader?, ContactsPeerItemSelection, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, Bool, Bool, Bool, StoryData?, Bool)
var stableId: ContactListNodeEntryId { var stableId: ContactListNodeEntryId {
switch self { switch self {
@ -110,7 +111,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
return .permission(action: true) return .permission(action: true)
case let .option(index, _, _, _, _): case let .option(index, _, _, _, _):
return .option(index: index) return .option(index: index)
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, storyData, _): case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, storyData, _):
switch peer { switch peer {
case let .peer(peer, _, _): case let .peer(peer, _, _):
return .peerId(peerId: peer.id.toInt64(), section: storyData != nil ? .stories : .contacts) return .peerId(peerId: peer.id.toInt64(), section: storyData != nil ? .stories : .contacts)
@ -143,7 +144,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
}) })
case let .option(_, option, header, _, _): case let .option(_, option, header, _, _):
return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, icon: option.icon, clearHighlightAutomatically: option.clearHighlightAutomatically, header: header, action: option.action) return ContactListActionItem(presentationData: ItemListPresentationData(presentationData), title: option.title, icon: option.icon, clearHighlightAutomatically: option.clearHighlightAutomatically, header: header, action: option.action)
case let .peer(_, peer, presence, header, selection, _, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, enabled, storyData, requiresPremiumForMessaging): case let .peer(_, peer, presence, header, selection, _, strings, dateTimeFormat, nameSortOrder, nameDisplayOrder, displayCallIcons, hasMoreButton, enabled, storyData, requiresPremiumForMessaging):
var status: ContactsPeerItemStatus var status: ContactsPeerItemStatus
let itemPeer: ContactsPeerItemPeer let itemPeer: ContactsPeerItemPeer
var isContextActionEnabled = false var isContextActionEnabled = false
@ -200,11 +201,15 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
} }
var additionalActions: [ContactsPeerItemAction] = [] var additionalActions: [ContactsPeerItemAction] = []
if displayCallIcons { if hasMoreButton {
additionalActions = [ContactsPeerItemAction(icon: .voiceCall, action: { _ in additionalActions = [ContactsPeerItemAction(icon: .more, action: { _, sourceNode, gesture in
interaction.openPeer(peer, .voiceCall) interaction.openPeer(peer, .more, sourceNode, gesture)
}), ContactsPeerItemAction(icon: .videoCall, action: { _ in })]
interaction.openPeer(peer, .videoCall) } else if displayCallIcons {
additionalActions = [ContactsPeerItemAction(icon: .voiceCall, action: { _, sourceNode, gesture in
interaction.openPeer(peer, .voiceCall, sourceNode, gesture)
}), ContactsPeerItemAction(icon: .videoCall, action: { _, sourceNode, gesture in
interaction.openPeer(peer, .videoCall, sourceNode, gesture)
})] })]
} }
@ -218,7 +223,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
} }
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: isSearch ? .generalSearch(isSavedMessages: false) : .peer, peer: itemPeer, status: status, requiresPremiumForMessaging: requiresPremiumForMessaging, enabled: enabled, selection: selection, selectionPosition: .left, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), additionalActions: additionalActions, index: nil, header: header, action: { _ in
interaction.openPeer(peer, .generic) interaction.openPeer(peer, .generic, nil, nil)
}, disabledAction: { _ in }, disabledAction: { _ in
if case let .peer(peer, _, _) = peer { if case let .peer(peer, _, _) = peer {
interaction.openDisabledPeer(EnginePeer(peer), requiresPremiumForMessaging ? .premiumRequired : .generic) interaction.openDisabledPeer(EnginePeer(peer), requiresPremiumForMessaging ? .premiumRequired : .generic)
@ -263,9 +268,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
} else { } else {
return false return false
} }
case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder, lhsDisplayCallIcons, lhsEnabled, lhsStoryData, lhsRequiresPremiumForMessaging): case let .peer(lhsIndex, lhsPeer, lhsPresence, lhsHeader, lhsSelection, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder, lhsDisplayCallIcons, lhsHasMoreButton, lhsEnabled, lhsStoryData, lhsRequiresPremiumForMessaging):
switch rhs { switch rhs {
case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder, rhsDisplayCallIcons, rhsEnabled, rhsStoryData, rhsRequiresPremiumForMessaging): case let .peer(rhsIndex, rhsPeer, rhsPresence, rhsHeader, rhsSelection, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder, rhsDisplayCallIcons, rhsHasMoreButton, rhsEnabled, rhsStoryData, rhsRequiresPremiumForMessaging):
if lhsIndex != rhsIndex { if lhsIndex != rhsIndex {
return false return false
} }
@ -303,6 +308,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
if lhsDisplayCallIcons != rhsDisplayCallIcons { if lhsDisplayCallIcons != rhsDisplayCallIcons {
return false return false
} }
if lhsHasMoreButton != rhsHasMoreButton {
return false
}
if lhsEnabled != rhsEnabled { if lhsEnabled != rhsEnabled {
return false return false
} }
@ -353,11 +361,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
case .peer: case .peer:
return true return true
} }
case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _, _, lhsStoryData, _): case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, lhsStoryData, _):
switch rhs { switch rhs {
case .search, .sort, .permissionInfo, .permissionEnable, .option: case .search, .sort, .permissionInfo, .permissionEnable, .option:
return false return false
case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, rhsStoryData, _): case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, rhsStoryData, _):
if (lhsStoryData == nil) != (rhsStoryData == nil) { if (lhsStoryData == nil) != (rhsStoryData == nil) {
if lhsStoryData != nil { if lhsStoryData != nil {
return true return true
@ -551,7 +559,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
} }
let presence = presences[peer.id] let presence = presences[peer.id]
entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, nil, false)) entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false))
index += 1 index += 1
} }
@ -608,7 +616,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
} }
let presence = presences[peer.id] let presence = presences[peer.id]
entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, nil, false)) entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, true, nil, false))
index += 1 index += 1
} }
@ -657,7 +665,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
} }
let presence = presences[peer.id] let presence = presences[peer.id]
entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, true, nil, false)) entries.append(.peer(index, .peer(peer: peer._asPeer(), isGlobal: false, participantCount: nil), presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, false, false, true, nil, false))
index += 1 index += 1
} }
@ -701,7 +709,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
enabled = true enabled = true
} }
entries.append(.peer(index, peer, presence, nil, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, displayCallIcons, enabled, nil, false)) entries.append(.peer(index, peer, presence, nil, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, displayCallIcons, false, enabled, nil, false))
index += 1 index += 1
} }
} }
@ -748,7 +756,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
} }
entries.append(.peer(index, peer, presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, displayCallIcons, enabled, nil, requiresPremiumForMessaging)) entries.append(.peer(index, peer, presence, header, selection, theme, strings, dateTimeFormat, sortOrder, displayOrder, displayCallIcons, false, enabled, nil, requiresPremiumForMessaging))
index += 1 index += 1
} }
return entries return entries
@ -772,7 +780,7 @@ private func preparedContactListNodeTransition(context: AccountContext, presenta
case .search: case .search:
//indexSections.apend(CollectionIndexNode.searchIndex) //indexSections.apend(CollectionIndexNode.searchIndex)
break break
case let .peer(_, _, _, header, _, _, _, _, _, _, _, _, _, _): case let .peer(_, _, _, header, _, _, _, _, _, _, _, _, _, _, _):
if let header = header as? ContactListNameIndexHeader { if let header = header as? ContactListNameIndexHeader {
if !existingSections.contains(header.letter) { if !existingSections.contains(header.letter) {
existingSections.insert(header.letter) existingSections.insert(header.letter)
@ -1036,7 +1044,7 @@ public final class ContactListNode: ASDisplayNode {
public var contentScrollingEnded: ((ListView) -> Bool)? public var contentScrollingEnded: ((ListView) -> Bool)?
public var activateSearch: (() -> Void)? public var activateSearch: (() -> Void)?
public var openPeer: ((ContactListPeer, ContactListAction) -> Void)? public var openPeer: ((ContactListPeer, ContactListAction, ASDisplayNode?, ContextGesture?) -> Void)?
public var openDisabledPeer: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? public var openDisabledPeer: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
public var deselectedAll: (() -> Void)? public var deselectedAll: (() -> Void)?
public var updatedSelection: (([EnginePeer], Bool) -> Void)? public var updatedSelection: (([EnginePeer], Bool) -> Void)?
@ -1136,7 +1144,7 @@ public final class ContactListNode: ASDisplayNode {
authorizeImpl?() authorizeImpl?()
}, suppressWarning: { [weak self] in }, suppressWarning: { [weak self] in
self?.suppressPermissionWarning?() self?.suppressPermissionWarning?()
}, openPeer: { [weak self] peer, action in }, openPeer: { [weak self] peer, action, sourceNode, gesture in
if let strongSelf = self { if let strongSelf = self {
if strongSelf.multipleSelection { if strongSelf.multipleSelection {
var updated = false var updated = false
@ -1151,10 +1159,10 @@ public final class ContactListNode: ASDisplayNode {
} }
}) })
if !updated { if !updated {
strongSelf.openPeer?(peer, action) strongSelf.openPeer?(peer, action, sourceNode, gesture)
} }
} else { } else {
strongSelf.openPeer?(peer, action) strongSelf.openPeer?(peer, action, sourceNode, gesture)
} }
} }
}, openDisabledPeer: { [weak self] peer, reason in }, openDisabledPeer: { [weak self] peer, reason in
@ -1224,7 +1232,7 @@ public final class ContactListNode: ASDisplayNode {
strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.PreferSynchronousDrawing, .PreferSynchronousResourceLoading], scrollToItem: ListViewScrollToItem(index: index, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: nil), directionHint: .Down), additionalScrollDistance: 0.0, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) strongSelf.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.PreferSynchronousDrawing, .PreferSynchronousResourceLoading], scrollToItem: ListViewScrollToItem(index: index, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: nil), directionHint: .Down), additionalScrollDistance: 0.0, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
break loop break loop
} }
case let .peer(_, _, _, header, _, _, _, _, _, _, _, _, _, _): case let .peer(_, _, _, header, _, _, _, _, _, _, _, _, _, _, _):
if let header = header as? ContactListNameIndexHeader { if let header = header as? ContactListNameIndexHeader {
if let scalar = UnicodeScalar(header.letter) { if let scalar = UnicodeScalar(header.letter) {
let title = "\(Character(scalar))" let title = "\(Character(scalar))"

View File

@ -345,7 +345,7 @@ public class ContactsController: ViewController {
self?.activateSearch() self?.activateSearch()
} }
self.contactsNode.contactListNode.openPeer = { [weak self] peer, _ in self.contactsNode.contactListNode.openPeer = { [weak self] peer, _, _, _ in
guard let self else { guard let self else {
return return
} }

View File

@ -31,6 +31,7 @@ swift_library(
"//submodules/TelegramUI/Components/MultiAnimationRenderer", "//submodules/TelegramUI/Components/MultiAnimationRenderer",
"//submodules/TelegramUI/Components/EmojiStatusComponent", "//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent", "//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
"//submodules/MoreButtonNode",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -20,6 +20,7 @@ import ComponentFlow
import AnimationCache import AnimationCache
import MultiAnimationRenderer import MultiAnimationRenderer
import EmojiStatusComponent import EmojiStatusComponent
import MoreButtonNode
public final class ContactItemHighlighting { public final class ContactItemHighlighting {
public var chatLocation: ChatLocation? public var chatLocation: ChatLocation?
@ -88,13 +89,14 @@ public enum ContactsPeerItemActionIcon {
case add case add
case voiceCall case voiceCall
case videoCall case videoCall
case more
} }
public struct ContactsPeerItemAction { public struct ContactsPeerItemAction {
public let icon: ContactsPeerItemActionIcon public let icon: ContactsPeerItemActionIcon
public let action: ((ContactsPeerItemPeer) -> Void)? public let action: ((ContactsPeerItemPeer, ASDisplayNode, ContextGesture?) -> Void)?
public init(icon: ContactsPeerItemActionIcon, action: @escaping (ContactsPeerItemPeer) -> Void) { public init(icon: ContactsPeerItemActionIcon, action: @escaping (ContactsPeerItemPeer, ASDisplayNode, ContextGesture?) -> Void) {
self.icon = icon self.icon = icon
self.action = action self.action = action
} }
@ -417,6 +419,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
private var badgeTextNode: TextNode? private var badgeTextNode: TextNode?
private var selectionNode: CheckNode? private var selectionNode: CheckNode?
private var actionButtonNodes: [HighlightableButtonNode]? private var actionButtonNodes: [HighlightableButtonNode]?
private var moreButtonNode: MoreButtonNode?
private var arrowButtonNode: HighlightableButtonNode? private var arrowButtonNode: HighlightableButtonNode?
private var avatarTapRecognizer: UITapGestureRecognizer? private var avatarTapRecognizer: UITapGestureRecognizer?
@ -744,10 +747,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
var actionButtons: [ActionButton]? var actionButtons: [ActionButton]?
struct ActionButton { struct ActionButton {
let type: ContactsPeerItemActionIcon
let image: UIImage? let image: UIImage?
let action: ((ContactsPeerItemPeer) -> Void)? let action: ((ContactsPeerItemPeer, ASDisplayNode, ContextGesture?) -> Void)?
init(theme: PresentationTheme, icon: ContactsPeerItemActionIcon, action: ((ContactsPeerItemPeer) -> Void)?) { init(theme: PresentationTheme, icon: ContactsPeerItemActionIcon, action: ((ContactsPeerItemPeer, ASDisplayNode, ContextGesture?) -> Void)?) {
let image: UIImage? let image: UIImage?
switch icon { switch icon {
case .none: case .none:
@ -758,7 +762,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
image = PresentationResourcesItemList.voiceCallIcon(theme) image = PresentationResourcesItemList.voiceCallIcon(theme)
case .videoCall: case .videoCall:
image = PresentationResourcesItemList.videoCallIcon(theme) image = PresentationResourcesItemList.videoCallIcon(theme)
case .more:
image = PresentationResourcesItemList.videoCallIcon(theme)
} }
self.type = icon
self.image = image self.image = image
self.action = action self.action = action
} }
@ -1357,7 +1364,23 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
verifiedIconView.removeFromSuperview() verifiedIconView.removeFromSuperview()
} }
if let actionButtons = actionButtons { if let actionButtons, actionButtons.count == 1, let actionButton = actionButtons.first, case .more = actionButton.type {
let moreButtonNode: MoreButtonNode
if let current = strongSelf.moreButtonNode {
moreButtonNode = current
} else {
moreButtonNode = MoreButtonNode(theme: item.presentationData.theme)
moreButtonNode.iconNode.enqueueState(.more, animated: false)
moreButtonNode.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
strongSelf.offsetContainerNode.addSubnode(moreButtonNode)
strongSelf.moreButtonNode = moreButtonNode
}
moreButtonNode.action = { sourceNode, gesture in
actionButton.action?(item.peer, sourceNode, gesture)
}
let moreButtonSize = moreButtonNode.measure(CGSize(width: 100.0, height: nodeLayout.contentSize.height))
moreButtonNode.frame = CGRect(origin: CGPoint(x: revealOffset + params.width - params.rightInset - 18.0 - moreButtonSize.width, y:floor((nodeLayout.contentSize.height - moreButtonSize.height) / 2.0)), size: moreButtonSize)
} else if let actionButtons = actionButtons {
if strongSelf.actionButtonNodes == nil { if strongSelf.actionButtonNodes == nil {
var actionButtonNodes: [HighlightableButtonNode] = [] var actionButtonNodes: [HighlightableButtonNode] = []
for action in actionButtons { for action in actionButtons {
@ -1524,7 +1547,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
guard let actionButtonNodes = self.actionButtonNodes, let index = actionButtonNodes.firstIndex(of: sender), let item = self.item, index < item.additionalActions.count else { guard let actionButtonNodes = self.actionButtonNodes, let index = actionButtonNodes.firstIndex(of: sender), let item = self.item, index < item.additionalActions.count else {
return return
} }
item.additionalActions[index].action?(item.peer) item.additionalActions[index].action?(item.peer, sender, nil)
} }
override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {

View File

@ -1599,7 +1599,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
videoNode.setBaseRate(self.playbackRate ?? 1.0) videoNode.setBaseRate(self.playbackRate ?? 1.0)
} }
} else { } else {
if self.shouldAutoplayOnCentrality() { if isAnimated {
self.playOnContentOwnership = true
} else if self.shouldAutoplayOnCentrality() {
self.playOnContentOwnership = true self.playOnContentOwnership = true
} }
} }

View File

@ -1592,11 +1592,14 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
if case let .noAccess(cameraAccess) = self.state { if case let .noAccess(cameraAccess) = self.state {
var hasCamera = cameraAccess == .authorized var hasCamera = cameraAccess == .authorized
var story = false var story = false
if let subject = self.controller?.subject, case .assets(_, .story) = subject { if let subject = self.controller?.subject {
if case .assets(_, .story) = subject {
hasCamera = false hasCamera = false
story = true story = true
self.controller?.navigationItem.rightBarButtonItem = nil self.controller?.navigationItem.rightBarButtonItem = nil
} else if case .assets(_, .createSticker) = subject {
hasCamera = false
}
} }
var placeholderTransition = transition var placeholderTransition = transition

View File

@ -112,6 +112,10 @@ private final class MediaCutoutScreenComponent: Component {
x: location.x / controller.drawingView.bounds.width, x: location.x / controller.drawingView.bounds.width,
y: location.y / controller.drawingView.bounds.height y: location.y / controller.drawingView.bounds.height
) )
let validRange: Range<CGFloat> = 0.0 ..< 1.0
guard validRange.contains(point.x) && validRange.contains(point.y) else {
return
}
component.mediaEditor.processImage { [weak self] originalImage, _ in component.mediaEditor.processImage { [weak self] originalImage, _ in
cutoutImage(from: originalImage, values: nil, target: .point(point), includeExtracted: false, completion: { [weak self] results in cutoutImage(from: originalImage, values: nil, target: .point(point), includeExtracted: false, completion: { [weak self] results in

View File

@ -1177,8 +1177,10 @@ final class MediaEditorScreenComponent: Component {
let authorName = forwardAuthor.displayTitle(strings: environment.strings, displayOrder: .firstLast) let authorName = forwardAuthor.displayTitle(strings: environment.strings, displayOrder: .firstLast)
header = AnyComponent( header = AnyComponent(
ForwardInfoPanelComponent( ForwardInfoPanelComponent(
context: component.context,
authorName: authorName, authorName: authorName,
text: forwardStory.text, text: forwardStory.text,
entities: forwardStory.entities,
isChannel: forwardAuthor.id.isGroupOrChannel, isChannel: forwardAuthor.id.isGroupOrChannel,
isVibrant: true, isVibrant: true,
fillsWidth: true fillsWidth: true

View File

@ -228,20 +228,48 @@ func findEdgePoints(in pixelBuffer: CVPixelBuffer) -> [CGPoint] {
return pixel >= 235 return pixel >= 235
} }
var startPoint: Point? = nil
var visited = Set<Point>()
var componentSize = 0
func floodFill(from point: Point) -> Int {
var stack = [point]
var size = 0
while let current = stack.popLast() {
let x = Int(current.x)
let y = Int(current.y)
if x < 0 || x >= width || y < 0 || y >= height || visited.contains(current) || !isPixelWhiteAt(x: x, y: y) {
continue
}
visited.insert(current)
size += 1
stack.append(contentsOf: [Point(x: x+1, y: y), Point(x: x-1, y: y), Point(x: x, y: y+1), Point(x: x, y: y-1)])
}
return size
}
for y in 0..<height {
for x in 0..<width {
let point = Point(x: x, y: y)
if isPixelWhiteAt(x: x, y: y) && !visited.contains(point) {
let size = floodFill(from: point)
if size > componentSize {
componentSize = size
startPoint = point
}
}
}
}
let directions = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)] let directions = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
var lastDirectionIndex = 0 var lastDirectionIndex = 0
var startPoint: Point? = nil guard let startingPoint = startPoint, componentSize > 60 else { return [] }
outerLoop: for y in 0..<height {
for x in 0..<width {
if isPixelWhiteAt(x: x, y: y) {
startPoint = Point(x: x, y: y)
break outerLoop
}
}
}
guard let startingPoint = startPoint else { return [] }
edgePoints.insert(startingPoint) edgePoints.insert(startingPoint)
edgePath.append(startingPoint) edgePath.append(startingPoint)
var currentPoint = startingPoint var currentPoint = startingPoint

View File

@ -1365,7 +1365,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
contactListNode.activateSearch = { [weak self] in contactListNode.activateSearch = { [weak self] in
self?.requestActivateSearch?() self?.requestActivateSearch?()
} }
contactListNode.openPeer = { [weak self] peer, _ in contactListNode.openPeer = { [weak self] peer, _, _, _ in
if case let .peer(peer, _, _) = peer { if case let .peer(peer, _, _) = peer {
self?.contactListNode?.listNode.clearHighlightAnimated(true) self?.contactListNode?.listNode.clearHighlightAnimated(true)
self?.requestOpenPeer?(EnginePeer(peer), nil) self?.requestOpenPeer?(EnginePeer(peer), nil)

View File

@ -12,9 +12,13 @@ swift_library(
deps = [ deps = [
"//submodules/Display", "//submodules/Display",
"//submodules/ComponentFlow", "//submodules/ComponentFlow",
"//submodules/TelegramCore",
"//submodules/TelegramPresentationData", "//submodules/TelegramPresentationData",
"//submodules/Components/MultilineTextComponent", "//submodules/Components/MultilineTextComponent",
"//submodules/Components/MultilineTextWithEntitiesComponent",
"//submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView", "//submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView",
"//submodules/AccountContext",
"//submodules/TextFormat",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -2,25 +2,35 @@ import Foundation
import UIKit import UIKit
import Display import Display
import ComponentFlow import ComponentFlow
import TelegramCore
import MultilineTextComponent import MultilineTextComponent
import MultilineTextWithEntitiesComponent
import MessageInlineBlockBackgroundView import MessageInlineBlockBackgroundView
import AccountContext
import TextFormat
public final class ForwardInfoPanelComponent: Component { public final class ForwardInfoPanelComponent: Component {
public let context: AccountContext
public let authorName: String public let authorName: String
public let text: String public let text: String
public let entities: [MessageTextEntity]
public let isChannel: Bool public let isChannel: Bool
public let isVibrant: Bool public let isVibrant: Bool
public let fillsWidth: Bool public let fillsWidth: Bool
public init( public init(
context: AccountContext,
authorName: String, authorName: String,
text: String, text: String,
entities: [MessageTextEntity],
isChannel: Bool, isChannel: Bool,
isVibrant: Bool, isVibrant: Bool,
fillsWidth: Bool fillsWidth: Bool
) { ) {
self.context = context
self.authorName = authorName self.authorName = authorName
self.text = text self.text = text
self.entities = entities
self.isChannel = isChannel self.isChannel = isChannel
self.isVibrant = isVibrant self.isVibrant = isVibrant
self.fillsWidth = fillsWidth self.fillsWidth = fillsWidth
@ -33,6 +43,9 @@ public final class ForwardInfoPanelComponent: Component {
if lhs.text != rhs.text { if lhs.text != rhs.text {
return false return false
} }
if lhs.entities != rhs.entities {
return false
}
if lhs.isChannel != rhs.isChannel { if lhs.isChannel != rhs.isChannel {
return false return false
} }
@ -47,7 +60,6 @@ public final class ForwardInfoPanelComponent: Component {
public final class View: UIView { public final class View: UIView {
public let backgroundView: UIImageView public let backgroundView: UIImageView
// private let blurBackgroundView: BlurredBackgroundView
private let blurBackgroundView: UIVisualEffectView private let blurBackgroundView: UIVisualEffectView
private let blockView: MessageInlineBlockBackgroundView private let blockView: MessageInlineBlockBackgroundView
private var iconView: UIImageView? private var iconView: UIImageView?
@ -58,8 +70,6 @@ public final class ForwardInfoPanelComponent: Component {
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
override init(frame: CGRect) { override init(frame: CGRect) {
// self.blurBackgroundView = BlurredBackgroundView(color: UIColor(rgb: 0x000000, alpha: 0.4))
if #available(iOS 13.0, *) { if #available(iOS 13.0, *) {
self.blurBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterialDark)) self.blurBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterialDark))
} else { } else {
@ -124,14 +134,20 @@ public final class ForwardInfoPanelComponent: Component {
view.frame = titleFrame view.frame = titleFrame
} }
let textFont = Font.regular(14.0)
let textColor = UIColor.white
let attributedText = stringWithAppliedEntities(component.text, entities: component.entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: nil)
let textSize = self.text.update( let textSize = self.text.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(MultilineTextComponent( component: AnyComponent(MultilineTextWithEntitiesComponent(
text: .plain(NSAttributedString( context: component.context,
string: component.text, animationCache: component.context.animationCache,
font: Font.regular(14.0), animationRenderer: component.context.animationRenderer,
textColor: .white placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.4),
)), text: .plain(attributedText),
horizontalAlignment: .natural,
truncationType: .end,
maximumNumberOfLines: 1 maximumNumberOfLines: 1
)), )),
environment: {}, environment: {},

View File

@ -698,6 +698,7 @@ final class StoryContentCaptionComponent: Component {
let authorName: String let authorName: String
let isChannel: Bool let isChannel: Bool
let text: String? let text: String?
let entities: [MessageTextEntity]
switch forwardInfo { switch forwardInfo {
case let .known(peer, _, _): case let .known(peer, _, _):
@ -706,6 +707,7 @@ final class StoryContentCaptionComponent: Component {
if let story = self.forwardInfoStory { if let story = self.forwardInfoStory {
text = story.text text = story.text
entities = story.entities
} else if self.forwardInfoDisposable == nil, let forwardInfoStory = component.forwardInfoStory { } else if self.forwardInfoDisposable == nil, let forwardInfoStory = component.forwardInfoStory {
self.forwardInfoDisposable = (forwardInfoStory self.forwardInfoDisposable = (forwardInfoStory
|> deliverOnMainQueue).start(next: { story in |> deliverOnMainQueue).start(next: { story in
@ -717,13 +719,16 @@ final class StoryContentCaptionComponent: Component {
} }
}) })
text = "" text = ""
entities = []
} else { } else {
text = "" text = ""
entities = []
} }
case let .unknown(name, _): case let .unknown(name, _):
authorName = name authorName = name
isChannel = false isChannel = false
text = "" text = ""
entities = []
} }
if let text { if let text {
@ -741,8 +746,10 @@ final class StoryContentCaptionComponent: Component {
PlainButtonComponent( PlainButtonComponent(
content: AnyComponent( content: AnyComponent(
ForwardInfoPanelComponent( ForwardInfoPanelComponent(
context: component.context,
authorName: authorName, authorName: authorName,
text: text, text: text,
entities: entities,
isChannel: isChannel, isChannel: isChannel,
isVibrant: false, isVibrant: false,
fillsWidth: false fillsWidth: false

View File

@ -1164,6 +1164,8 @@ extension ChatControllerImpl {
controller.openCamera = { [weak self] cameraView in controller.openCamera = { [weak self] cameraView in
if let cameraView = cameraView as? TGAttachmentCameraView { if let cameraView = cameraView as? TGAttachmentCameraView {
self?.openCamera(cameraView: cameraView) self?.openCamera(cameraView: cameraView)
} else {
self?.openCamera(cameraView: nil)
} }
} }
controller.presentWebSearch = { [weak self, weak controller] mediaGroups, activateOnDisplay in controller.presentWebSearch = { [weak self, weak controller] mediaGroups, activateOnDisplay in

View File

@ -670,11 +670,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
if messages.count == 1 { if messages.count == 1 {
for media in messages[0].media { for media in messages[0].media {
if let file = media as? TelegramMediaFile { if let file = media as? TelegramMediaFile {
for attribute in file.attributes { if file.isSticker {
if case let .Sticker(_, packInfo, _) = attribute, packInfo != nil {
loadStickerSaveStatus = file.fileId loadStickerSaveStatus = file.fileId
} }
}
if loadStickerSaveStatus == nil { if loadStickerSaveStatus == nil {
loadCopyMediaResource = file.resource loadCopyMediaResource = file.resource
} }

View File

@ -116,7 +116,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
self?.activateSearch() self?.activateSearch()
} }
self.contactsNode.contactListNode.openPeer = { [weak self] peer, _ in self.contactsNode.contactListNode.openPeer = { [weak self] peer, _, _, _ in
if case let .peer(peer, _, _) = peer { if case let .peer(peer, _, _) = peer {
self?.openPeer(peerId: peer.id) self?.openPeer(peerId: peer.id)
} }

View File

@ -16,6 +16,7 @@ import CounterContollerTitleView
import EditableTokenListNode import EditableTokenListNode
import PremiumUI import PremiumUI
import UndoUI import UndoUI
import ContextUI
private func peerTokenTitle(accountPeerId: PeerId, peer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String { private func peerTokenTitle(accountPeerId: PeerId, peer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String {
if peer.id == accountPeerId { if peer.id == accountPeerId {
@ -415,6 +416,38 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
} }
} }
self.contactsNode.openPeerMore = { [weak self] peer, node, gesture in
guard let self, case let .peer(peer, _, _) = peer, let node = node as? ContextReferenceContentNode else {
return
}
let presentationData = self.presentationData
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.stings.Premium_Gift_ContactSelection_SendMessage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor)
}, iconPosition: .left, action: { [weak self] _, a in
a(.default)
if let self {
self.params.sendMessage?(EnginePeer(peer))
}
})))
items.append(.action(ContextMenuActionItem(text: presentationData.stings.Premium_Gift_ContactSelection_OpenProfile, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
}, iconPosition: .left, action: { [weak self] _, a in
a(.default)
if let self {
self.params.openProfile?(EnginePeer(peer))
}
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(ContactContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.present(contextController, in: .window(.root))
}
self.contactsNode.openDisabledPeer = { [weak self] peer, reason in self.contactsNode.openDisabledPeer = { [weak self] peer, reason in
guard let self else { guard let self else {
return return
@ -765,3 +798,17 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
self._result.set(.single(.result(peerIds: peerIds, additionalOptionIds: additionalOptionIds))) self._result.set(.single(.result(peerIds: peerIds, additionalOptionIds: additionalOptionIds)))
} }
} }
private final class ContactContextReferenceContentSource: ContextReferenceContentSource {
private let controller: ViewController
private let sourceNode: ContextReferenceContentNode
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
self.controller = controller
self.sourceNode = sourceNode
}
func transitionInfo() -> ContextControllerReferenceViewInfo? {
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View File

@ -13,6 +13,7 @@ import AnimationCache
import MultiAnimationRenderer import MultiAnimationRenderer
import EditableTokenListNode import EditableTokenListNode
import SolidRoundedButtonNode import SolidRoundedButtonNode
import ContextUI
private struct SearchResultEntry: Identifiable { private struct SearchResultEntry: Identifiable {
let index: Int let index: Int
@ -58,6 +59,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
var requestDeactivateSearch: (() -> Void)? var requestDeactivateSearch: (() -> Void)?
var requestOpenPeerFromSearch: ((ContactListPeerId) -> Void)? var requestOpenPeerFromSearch: ((ContactListPeerId) -> Void)?
var openPeer: ((ContactListPeer) -> Void)? var openPeer: ((ContactListPeer) -> Void)?
var openPeerMore: ((ContactListPeer, ASDisplayNode?, ContextGesture?) -> Void)?
var openDisabledPeer: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? var openDisabledPeer: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
var removeSelectedPeer: ((ContactListPeerId) -> Void)? var removeSelectedPeer: ((ContactListPeerId) -> Void)?
var removeSelectedCategory: ((Int) -> Void)? var removeSelectedCategory: ((Int) -> Void)?
@ -262,9 +264,13 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
switch self.contentNode { switch self.contentNode {
case let .contacts(contactsNode): case let .contacts(contactsNode):
contactsNode.openPeer = { [weak self] peer, _ in contactsNode.openPeer = { [weak self] peer, action, sourceNode, gesture in
if case .more = action {
self?.openPeerMore?(peer, sourceNode, gesture)
} else {
self?.openPeer?(peer) self?.openPeer?(peer)
} }
}
contactsNode.openDisabledPeer = { [weak self] peer, reason in contactsNode.openDisabledPeer = { [weak self] peer, reason in
guard let self else { guard let self else {
return return
@ -362,7 +368,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
globalSearch: globalSearch, globalSearch: globalSearch,
displaySavedMessages: displaySavedMessages displaySavedMessages: displaySavedMessages
))), filters: filters, onlyWriteable: strongSelf.onlyWriteable, isGroupInvitation: strongSelf.isGroupInvitation, isPeerEnabled: strongSelf.isPeerEnabled, selectionState: selectionState, isSearch: true) ))), filters: filters, onlyWriteable: strongSelf.onlyWriteable, isGroupInvitation: strongSelf.isGroupInvitation, isPeerEnabled: strongSelf.isPeerEnabled, selectionState: selectionState, isSearch: true)
searchResultsNode.openPeer = { peer, _ in searchResultsNode.openPeer = { peer, _, _, _ in
self?.tokenListNode.setText("") self?.tokenListNode.setText("")
self?.openPeer?(peer) self?.openPeer?(peer)
} }

View File

@ -197,7 +197,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
self?.activateSearch() self?.activateSearch()
} }
self.contactsNode.contactListNode.openPeer = { [weak self] peer, action in self.contactsNode.contactListNode.openPeer = { [weak self] peer, action, _, _ in
self?.openPeer(peer: peer, action: action) self?.openPeer(peer: peer, action: action)
} }

View File

@ -2164,15 +2164,33 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mode = .premiumGifting(birthdays: nil, selectToday: false) mode = .premiumGifting(birthdays: nil, selectToday: false)
} }
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: mode, options: [], isPeerEnabled: { peer in var openProfileImpl: ((EnginePeer) -> Void)?
var sendMessageImpl: ((EnginePeer) -> Void)?
let controller = context.sharedContext.makeContactMultiselectionController(
ContactMultiselectionControllerParams(
context: context,
mode: mode,
options: [],
isPeerEnabled: { peer in
if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) { if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) {
return true return true
} else { } else {
return false return false
} }
}, limit: limit, reachedLimit: { limit in },
limit: limit,
reachedLimit: { limit in
reachedLimitImpl?(limit) reachedLimitImpl?(limit)
})) },
openProfile: { peer in
openProfileImpl?(peer)
},
sendMessage: { peer in
sendMessageImpl?(peer)
}
)
)
reachedLimitImpl = { [weak controller] limit in reachedLimitImpl = { [weak controller] limit in
guard let controller else { guard let controller else {
@ -2227,6 +2245,36 @@ public final class SharedAccountContextImpl: SharedAccountContext {
controller.push(giftController) controller.push(giftController)
}) })
sendMessageImpl = { [weak self, weak controller] peer in
guard let self, let controller, let navigationController = controller.navigationController as? NavigationController else {
return
}
self.navigateToChatController(
NavigateToChatControllerParams(
navigationController: navigationController,
context: context,
chatLocation: .peer(peer)
)
)
}
openProfileImpl = { [weak self, weak controller] peer in
guard let self, let controller else {
return
}
if let infoController = self.makePeerInfoController(
context: context,
updatedPresentationData: nil,
peer: peer._asPeer(),
mode: .generic,
avatarInitiallyExpanded: true,
fromChat: false,
requestsContext: nil
) {
controller.replace(with: infoController)
}
}
return controller return controller
} }