mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Cherry-pick various fixes
This commit is contained in:
parent
5dc97b4e3d
commit
669a8719bf
@ -12050,3 +12050,5 @@ Sorry for the inconvenience.";
|
||||
"MediaEditor.NewStickerPack.Title" = "New Sticker Set";
|
||||
"MediaEditor.NewStickerPack.Text" = "Choose a name for your sticker set.";
|
||||
|
||||
"Premium.Gift.ContactSelection.SendMessage" = "Send Message";
|
||||
"Premium.Gift.ContactSelection.OpenProfile" = "Open Profile";
|
||||
|
@ -637,6 +637,7 @@ public enum ContactListAction: Equatable {
|
||||
case generic
|
||||
case voiceCall
|
||||
case videoCall
|
||||
case more
|
||||
}
|
||||
|
||||
public enum ContactListPeer: Equatable {
|
||||
|
@ -102,8 +102,10 @@ public final class ContactMultiselectionControllerParams {
|
||||
public let alwaysEnabled: Bool
|
||||
public let limit: Int32?
|
||||
public let reachedLimit: ((Int32) -> 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 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, openProfile: ((EnginePeer) -> Void)? = nil, sendMessage: ((EnginePeer) -> Void)? = nil) {
|
||||
self.context = context
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.mode = mode
|
||||
@ -116,6 +118,8 @@ public final class ContactMultiselectionControllerParams {
|
||||
self.alwaysEnabled = alwaysEnabled
|
||||
self.limit = limit
|
||||
self.reachedLimit = reachedLimit
|
||||
self.openProfile = openProfile
|
||||
self.sendMessage = sendMessage
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,7 @@ swift_library(
|
||||
"//submodules/TooltipUI",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/TelegramIntents",
|
||||
"//submodules/ContextUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -23,6 +23,7 @@ import AppBundle
|
||||
import ContextUI
|
||||
import PhoneNumberFormat
|
||||
import LocalizedPeerData
|
||||
import ContextUI
|
||||
|
||||
private let dropDownIcon = { () -> UIImage in
|
||||
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 authorize: () -> 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 contextAction: ((EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?, Bool) -> Void)?
|
||||
fileprivate let openStories: (EnginePeer, ASDisplayNode) -> Void
|
||||
@ -65,7 +66,7 @@ private final class ContactListNodeInteraction {
|
||||
|
||||
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.authorize = authorize
|
||||
self.suppressWarning = suppressWarning
|
||||
@ -96,7 +97,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
case permissionInfo(PresentationTheme, String, String, Bool)
|
||||
case permissionEnable(PresentationTheme, String)
|
||||
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 {
|
||||
switch self {
|
||||
@ -110,7 +111,7 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
return .permission(action: true)
|
||||
case let .option(index, _, _, _, _):
|
||||
return .option(index: index)
|
||||
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, storyData, _):
|
||||
case let .peer(_, peer, _, _, _, _, _, _, _, _, _, _, _, storyData, _):
|
||||
switch peer {
|
||||
case let .peer(peer, _, _):
|
||||
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, _, _):
|
||||
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
|
||||
let itemPeer: ContactsPeerItemPeer
|
||||
var isContextActionEnabled = false
|
||||
@ -200,11 +201,15 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
}
|
||||
|
||||
var additionalActions: [ContactsPeerItemAction] = []
|
||||
if displayCallIcons {
|
||||
additionalActions = [ContactsPeerItemAction(icon: .voiceCall, action: { _ in
|
||||
interaction.openPeer(peer, .voiceCall)
|
||||
}), ContactsPeerItemAction(icon: .videoCall, action: { _ in
|
||||
interaction.openPeer(peer, .videoCall)
|
||||
if hasMoreButton {
|
||||
additionalActions = [ContactsPeerItemAction(icon: .more, action: { _, sourceNode, gesture in
|
||||
interaction.openPeer(peer, .more, sourceNode, gesture)
|
||||
})]
|
||||
} 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
|
||||
interaction.openPeer(peer, .generic)
|
||||
interaction.openPeer(peer, .generic, nil, nil)
|
||||
}, disabledAction: { _ in
|
||||
if case let .peer(peer, _, _) = peer {
|
||||
interaction.openDisabledPeer(EnginePeer(peer), requiresPremiumForMessaging ? .premiumRequired : .generic)
|
||||
@ -263,9 +268,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
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 {
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
@ -303,6 +308,9 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
if lhsDisplayCallIcons != rhsDisplayCallIcons {
|
||||
return false
|
||||
}
|
||||
if lhsHasMoreButton != rhsHasMoreButton {
|
||||
return false
|
||||
}
|
||||
if lhsEnabled != rhsEnabled {
|
||||
return false
|
||||
}
|
||||
@ -353,11 +361,11 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
case .peer:
|
||||
return true
|
||||
}
|
||||
case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _, _, lhsStoryData, _):
|
||||
case let .peer(lhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, lhsStoryData, _):
|
||||
switch rhs {
|
||||
case .search, .sort, .permissionInfo, .permissionEnable, .option:
|
||||
return false
|
||||
case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, rhsStoryData, _):
|
||||
case let .peer(rhsIndex, _, _, _, _, _, _, _, _, _, _, _, _, rhsStoryData, _):
|
||||
if (lhsStoryData == nil) != (rhsStoryData == nil) {
|
||||
if lhsStoryData != nil {
|
||||
return true
|
||||
@ -551,7 +559,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -608,7 +616,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -657,7 +665,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -701,7 +709,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
return entries
|
||||
@ -772,7 +780,7 @@ private func preparedContactListNodeTransition(context: AccountContext, presenta
|
||||
case .search:
|
||||
//indexSections.apend(CollectionIndexNode.searchIndex)
|
||||
break
|
||||
case let .peer(_, _, _, header, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .peer(_, _, _, header, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if let header = header as? ContactListNameIndexHeader {
|
||||
if !existingSections.contains(header.letter) {
|
||||
existingSections.insert(header.letter)
|
||||
@ -1036,7 +1044,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
public var contentScrollingEnded: ((ListView) -> Bool)?
|
||||
|
||||
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 deselectedAll: (() -> Void)?
|
||||
public var updatedSelection: (([EnginePeer], Bool) -> Void)?
|
||||
@ -1136,7 +1144,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
authorizeImpl?()
|
||||
}, suppressWarning: { [weak self] in
|
||||
self?.suppressPermissionWarning?()
|
||||
}, openPeer: { [weak self] peer, action in
|
||||
}, openPeer: { [weak self] peer, action, sourceNode, gesture in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.multipleSelection {
|
||||
var updated = false
|
||||
@ -1151,10 +1159,10 @@ public final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
})
|
||||
if !updated {
|
||||
strongSelf.openPeer?(peer, action)
|
||||
strongSelf.openPeer?(peer, action, sourceNode, gesture)
|
||||
}
|
||||
} else {
|
||||
strongSelf.openPeer?(peer, action)
|
||||
strongSelf.openPeer?(peer, action, sourceNode, gesture)
|
||||
}
|
||||
}
|
||||
}, 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 })
|
||||
break loop
|
||||
}
|
||||
case let .peer(_, _, _, header, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .peer(_, _, _, header, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if let header = header as? ContactListNameIndexHeader {
|
||||
if let scalar = UnicodeScalar(header.letter) {
|
||||
let title = "\(Character(scalar))"
|
||||
|
@ -345,7 +345,7 @@ public class ContactsController: ViewController {
|
||||
self?.activateSearch()
|
||||
}
|
||||
|
||||
self.contactsNode.contactListNode.openPeer = { [weak self] peer, _ in
|
||||
self.contactsNode.contactListNode.openPeer = { [weak self] peer, _, _, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
||||
"//submodules/MoreButtonNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -20,6 +20,7 @@ import ComponentFlow
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import EmojiStatusComponent
|
||||
import MoreButtonNode
|
||||
|
||||
public final class ContactItemHighlighting {
|
||||
public var chatLocation: ChatLocation?
|
||||
@ -88,13 +89,14 @@ public enum ContactsPeerItemActionIcon {
|
||||
case add
|
||||
case voiceCall
|
||||
case videoCall
|
||||
case more
|
||||
}
|
||||
|
||||
public struct ContactsPeerItemAction {
|
||||
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.action = action
|
||||
}
|
||||
@ -417,6 +419,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
private var badgeTextNode: TextNode?
|
||||
private var selectionNode: CheckNode?
|
||||
private var actionButtonNodes: [HighlightableButtonNode]?
|
||||
private var moreButtonNode: MoreButtonNode?
|
||||
private var arrowButtonNode: HighlightableButtonNode?
|
||||
|
||||
private var avatarTapRecognizer: UITapGestureRecognizer?
|
||||
@ -744,10 +747,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
var actionButtons: [ActionButton]?
|
||||
struct ActionButton {
|
||||
let type: ContactsPeerItemActionIcon
|
||||
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?
|
||||
switch icon {
|
||||
case .none:
|
||||
@ -758,7 +762,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
image = PresentationResourcesItemList.voiceCallIcon(theme)
|
||||
case .videoCall:
|
||||
image = PresentationResourcesItemList.videoCallIcon(theme)
|
||||
case .more:
|
||||
image = PresentationResourcesItemList.videoCallIcon(theme)
|
||||
}
|
||||
self.type = icon
|
||||
self.image = image
|
||||
self.action = action
|
||||
}
|
||||
@ -1357,7 +1364,23 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
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 {
|
||||
var actionButtonNodes: [HighlightableButtonNode] = []
|
||||
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 {
|
||||
return
|
||||
}
|
||||
item.additionalActions[index].action?(item.peer)
|
||||
item.additionalActions[index].action?(item.peer, sender, nil)
|
||||
}
|
||||
|
||||
override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
@ -1599,7 +1599,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
videoNode.setBaseRate(self.playbackRate ?? 1.0)
|
||||
}
|
||||
} else {
|
||||
if self.shouldAutoplayOnCentrality() {
|
||||
if isAnimated {
|
||||
self.playOnContentOwnership = true
|
||||
} else if self.shouldAutoplayOnCentrality() {
|
||||
self.playOnContentOwnership = true
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import Display
|
||||
private func generateHistogram(cgImage: CGImage) -> ([[vImagePixelCount]], Int)? {
|
||||
var sourceBuffer = vImage_Buffer()
|
||||
defer {
|
||||
free(sourceBuffer.data)
|
||||
sourceBuffer.data?.deallocate()
|
||||
}
|
||||
|
||||
var cgImageFormat = vImage_CGImageFormat(
|
||||
|
@ -5,6 +5,7 @@ import Postbox
|
||||
import SwiftSignalKit
|
||||
import ImageCompression
|
||||
import Accelerate.vImage
|
||||
import CoreImage
|
||||
|
||||
private final class RequestId {
|
||||
var id: PHImageRequestID?
|
||||
@ -15,24 +16,43 @@ private func resizedImage(_ image: UIImage, for size: CGSize) -> UIImage? {
|
||||
guard let cgImage = image.cgImage else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
if #available(iOS 14.1, *) {
|
||||
if cgImage.bitsPerComponent == 10, let ciImage = CIImage(image: image, options: [.applyOrientationProperty: true, .toneMapHDRtoSDR: true]) {
|
||||
let scaleX = size.width / ciImage.extent.width
|
||||
|
||||
let filter = CIFilter(name: "CILanczosScaleTransform")!
|
||||
filter.setValue(ciImage, forKey: kCIInputImageKey)
|
||||
filter.setValue(scaleX, forKey: kCIInputScaleKey)
|
||||
filter.setValue(1.0, forKey: kCIInputAspectRatioKey)
|
||||
|
||||
guard let outputImage = filter.outputImage else { return nil }
|
||||
|
||||
let ciContext = CIContext()
|
||||
guard let cgImage = ciContext.createCGImage(outputImage, from: outputImage.extent) else { return nil }
|
||||
|
||||
return UIImage(cgImage: cgImage)
|
||||
}
|
||||
}
|
||||
var format = vImage_CGImageFormat(bitsPerComponent: 8,
|
||||
bitsPerPixel: 32,
|
||||
colorSpace: nil,
|
||||
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
|
||||
version: 0,
|
||||
decode: nil,
|
||||
renderingIntent: .defaultIntent)
|
||||
renderingIntent: cgImage.renderingIntent)
|
||||
|
||||
var error: vImage_Error
|
||||
var sourceBuffer = vImage_Buffer()
|
||||
defer { sourceBuffer.data.deallocate() }
|
||||
defer { sourceBuffer.data?.deallocate() }
|
||||
error = vImageBuffer_InitWithCGImage(&sourceBuffer,
|
||||
&format,
|
||||
nil,
|
||||
cgImage,
|
||||
vImage_Flags(kvImageNoFlags))
|
||||
guard error == kvImageNoError else { return nil }
|
||||
guard error == kvImageNoError else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var destinationBuffer = vImage_Buffer()
|
||||
error = vImageBuffer_Init(&destinationBuffer,
|
||||
@ -84,7 +104,7 @@ extension UIImage.Orientation {
|
||||
|
||||
private let fetchPhotoWorkers = ThreadPool(threadCount: 3, threadPriority: 0.2)
|
||||
|
||||
public func fetchPhotoLibraryResource(localIdentifier: String, width: Int32?, height: Int32?, format: MediaImageFormat?, quality: Int32?) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
||||
public func fetchPhotoLibraryResource(localIdentifier: String, width: Int32?, height: Int32?, format: MediaImageFormat?, quality: Int32?, useExif: Bool) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
||||
return Signal { subscriber in
|
||||
let queue = ThreadPoolQueue(threadPool: fetchPhotoWorkers)
|
||||
|
||||
@ -96,7 +116,7 @@ public func fetchPhotoLibraryResource(localIdentifier: String, width: Int32?, he
|
||||
option.deliveryMode = .highQualityFormat
|
||||
option.isNetworkAccessAllowed = true
|
||||
option.isSynchronous = false
|
||||
|
||||
|
||||
let size: CGSize
|
||||
if let width, let height {
|
||||
size = CGSize(width: CGFloat(width), height: CGFloat(height))
|
||||
@ -104,11 +124,31 @@ public func fetchPhotoLibraryResource(localIdentifier: String, width: Int32?, he
|
||||
size = CGSize(width: 1280.0, height: 1280.0)
|
||||
}
|
||||
|
||||
var targetSize = PHImageManagerMaximumSize
|
||||
//TODO: figure out how to manually read and resize some weird 10-bit heif photos from third-party cameras
|
||||
if useExif, min(asset.pixelWidth, asset.pixelHeight) > 3800 {
|
||||
func encodeText(string: String, key: Int16) -> String {
|
||||
let nsString = string as NSString
|
||||
let result = NSMutableString()
|
||||
for i in 0 ..< nsString.length {
|
||||
var c: unichar = nsString.character(at: i)
|
||||
c = unichar(Int16(c) + key)
|
||||
result.append(NSString(characters: &c, length: 1) as String)
|
||||
}
|
||||
return result as String
|
||||
}
|
||||
if let values = asset.value(forKeyPath: encodeText(string: "jnbhfQspqfsujft", key: -1)) as? [String: Any] {
|
||||
if let depth = values["Depth"] as? Int, depth == 10 {
|
||||
targetSize = size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queue.addTask(ThreadPoolTask({ _ in
|
||||
let startTime = CACurrentMediaTime()
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
let requestIdValue = PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: option, resultHandler: { (image, info) -> Void in
|
||||
let requestIdValue = PHImageManager.default().requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: option, resultHandler: { (image, info) -> Void in
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
requestId.with { current -> Void in
|
||||
if !current.invalidated {
|
||||
|
@ -1592,11 +1592,14 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
if case let .noAccess(cameraAccess) = self.state {
|
||||
var hasCamera = cameraAccess == .authorized
|
||||
var story = false
|
||||
if let subject = self.controller?.subject, case .assets(_, .story) = subject {
|
||||
hasCamera = false
|
||||
story = true
|
||||
|
||||
self.controller?.navigationItem.rightBarButtonItem = nil
|
||||
if let subject = self.controller?.subject {
|
||||
if case .assets(_, .story) = subject {
|
||||
hasCamera = false
|
||||
story = true
|
||||
self.controller?.navigationItem.rightBarButtonItem = nil
|
||||
} else if case .assets(_, .createSticker) = subject {
|
||||
hasCamera = false
|
||||
}
|
||||
}
|
||||
|
||||
var placeholderTransition = transition
|
||||
|
@ -35,15 +35,11 @@ public enum PreparedShareItems {
|
||||
}
|
||||
|
||||
private func scalePhotoImage(_ image: UIImage, dimensions: CGSize) -> UIImage? {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = 1.0
|
||||
let renderer = UIGraphicsImageRenderer(size: dimensions, format: format)
|
||||
return renderer.image { _ in
|
||||
image.draw(in: CGRect(origin: .zero, size: dimensions))
|
||||
}
|
||||
} else {
|
||||
return TGScaleImageToPixelSize(image, dimensions)
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = 1.0
|
||||
let renderer = UIGraphicsImageRenderer(size: dimensions, format: format)
|
||||
return renderer.image { _ in
|
||||
image.draw(in: CGRect(origin: .zero, size: dimensions))
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,7 +230,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
|
||||
}
|
||||
)
|
||||
} else {
|
||||
let scaledImage = TGScaleImageToPixelSize(image, CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale).fitted(CGSize(width: 1280.0, height: 1280.0)))!
|
||||
let scaledImage = scalePhotoImage(image, dimensions: CGSize(width: image.size.width * image.scale, height: image.size.height * image.scale).fitted(CGSize(width: 1280.0, height: 1280.0)))!
|
||||
let imageData = scaledImage.jpegData(compressionQuality: 0.54)!
|
||||
return .single(.preparing(false))
|
||||
|> then(
|
||||
|
@ -166,7 +166,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
||||
}
|
||||
let textColor = strongSelf.theme.chat.inputPanel.primaryTextColor
|
||||
if entities.count > 0 {
|
||||
messageText = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message)
|
||||
messageText = stringWithAppliedEntities(trimToLineCount(message.text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message)
|
||||
} else {
|
||||
messageText = NSAttributedString(string: text, font: textFont, textColor: isMedia ? strongSelf.theme.chat.inputPanel.secondaryTextColor : strongSelf.theme.chat.inputPanel.primaryTextColor)
|
||||
}
|
||||
|
@ -112,6 +112,10 @@ private final class MediaCutoutScreenComponent: Component {
|
||||
x: location.x / controller.drawingView.bounds.width,
|
||||
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
|
||||
cutoutImage(from: originalImage, values: nil, target: .point(point), includeExtracted: false, completion: { [weak self] results in
|
||||
|
@ -1177,8 +1177,10 @@ final class MediaEditorScreenComponent: Component {
|
||||
let authorName = forwardAuthor.displayTitle(strings: environment.strings, displayOrder: .firstLast)
|
||||
header = AnyComponent(
|
||||
ForwardInfoPanelComponent(
|
||||
context: component.context,
|
||||
authorName: authorName,
|
||||
text: forwardStory.text,
|
||||
entities: forwardStory.entities,
|
||||
isChannel: forwardAuthor.id.isGroupOrChannel,
|
||||
isVibrant: true,
|
||||
fillsWidth: true
|
||||
|
@ -228,20 +228,48 @@ func findEdgePoints(in pixelBuffer: CVPixelBuffer) -> [CGPoint] {
|
||||
return pixel >= 235
|
||||
}
|
||||
|
||||
let directions = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
|
||||
var lastDirectionIndex = 0
|
||||
|
||||
var startPoint: Point? = nil
|
||||
outerLoop: for y in 0..<height {
|
||||
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 {
|
||||
if isPixelWhiteAt(x: x, y: y) {
|
||||
startPoint = Point(x: x, y: y)
|
||||
break outerLoop
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard let startingPoint = startPoint else { return [] }
|
||||
let directions = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
|
||||
var lastDirectionIndex = 0
|
||||
|
||||
guard let startingPoint = startPoint, componentSize > 60 else { return [] }
|
||||
|
||||
edgePoints.insert(startingPoint)
|
||||
edgePath.append(startingPoint)
|
||||
var currentPoint = startingPoint
|
||||
@ -326,7 +354,7 @@ private func getEdgesBitmap(_ ciImage: CIImage) -> CVPixelBuffer? {
|
||||
context.fill(CGRect(origin: .zero, size: size))
|
||||
image.draw(in: CGRect(origin: .zero, size: size))
|
||||
UIGraphicsPopContext()
|
||||
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
|
@ -1365,7 +1365,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
contactListNode.activateSearch = { [weak self] in
|
||||
self?.requestActivateSearch?()
|
||||
}
|
||||
contactListNode.openPeer = { [weak self] peer, _ in
|
||||
contactListNode.openPeer = { [weak self] peer, _, _, _ in
|
||||
if case let .peer(peer, _, _) = peer {
|
||||
self?.contactListNode?.listNode.clearHighlightAnimated(true)
|
||||
self?.requestOpenPeer?(EnginePeer(peer), nil)
|
||||
|
@ -12,9 +12,13 @@ swift_library(
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/MultilineTextWithEntitiesComponent",
|
||||
"//submodules/TelegramUI/Components/Chat/MessageInlineBlockBackgroundView",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/TextFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -2,25 +2,35 @@ import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import TelegramCore
|
||||
import MultilineTextComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import MessageInlineBlockBackgroundView
|
||||
import AccountContext
|
||||
import TextFormat
|
||||
|
||||
public final class ForwardInfoPanelComponent: Component {
|
||||
public let context: AccountContext
|
||||
public let authorName: String
|
||||
public let text: String
|
||||
public let entities: [MessageTextEntity]
|
||||
public let isChannel: Bool
|
||||
public let isVibrant: Bool
|
||||
public let fillsWidth: Bool
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
authorName: String,
|
||||
text: String,
|
||||
entities: [MessageTextEntity],
|
||||
isChannel: Bool,
|
||||
isVibrant: Bool,
|
||||
fillsWidth: Bool
|
||||
) {
|
||||
self.context = context
|
||||
self.authorName = authorName
|
||||
self.text = text
|
||||
self.entities = entities
|
||||
self.isChannel = isChannel
|
||||
self.isVibrant = isVibrant
|
||||
self.fillsWidth = fillsWidth
|
||||
@ -33,6 +43,9 @@ public final class ForwardInfoPanelComponent: Component {
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.entities != rhs.entities {
|
||||
return false
|
||||
}
|
||||
if lhs.isChannel != rhs.isChannel {
|
||||
return false
|
||||
}
|
||||
@ -47,7 +60,6 @@ public final class ForwardInfoPanelComponent: Component {
|
||||
|
||||
public final class View: UIView {
|
||||
public let backgroundView: UIImageView
|
||||
// private let blurBackgroundView: BlurredBackgroundView
|
||||
private let blurBackgroundView: UIVisualEffectView
|
||||
private let blockView: MessageInlineBlockBackgroundView
|
||||
private var iconView: UIImageView?
|
||||
@ -58,8 +70,6 @@ public final class ForwardInfoPanelComponent: Component {
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
// self.blurBackgroundView = BlurredBackgroundView(color: UIColor(rgb: 0x000000, alpha: 0.4))
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
self.blurBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterialDark))
|
||||
} else {
|
||||
@ -102,7 +112,7 @@ public final class ForwardInfoPanelComponent: Component {
|
||||
iconView.frame = CGRect(origin: CGPoint(x: sideInset + UIScreenPixel, y: 5.0), size: image.size)
|
||||
}
|
||||
titleOffset += 13.0
|
||||
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
@ -124,14 +134,20 @@ public final class ForwardInfoPanelComponent: Component {
|
||||
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(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: component.text,
|
||||
font: Font.regular(14.0),
|
||||
textColor: .white
|
||||
)),
|
||||
component: AnyComponent(MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: UIColor(rgb: 0xffffff, alpha: 0.4),
|
||||
text: .plain(attributedText),
|
||||
horizontalAlignment: .natural,
|
||||
truncationType: .end,
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
environment: {},
|
||||
|
@ -698,6 +698,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
let authorName: String
|
||||
let isChannel: Bool
|
||||
let text: String?
|
||||
let entities: [MessageTextEntity]
|
||||
|
||||
switch forwardInfo {
|
||||
case let .known(peer, _, _):
|
||||
@ -706,6 +707,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
|
||||
if let story = self.forwardInfoStory {
|
||||
text = story.text
|
||||
entities = story.entities
|
||||
} else if self.forwardInfoDisposable == nil, let forwardInfoStory = component.forwardInfoStory {
|
||||
self.forwardInfoDisposable = (forwardInfoStory
|
||||
|> deliverOnMainQueue).start(next: { story in
|
||||
@ -717,13 +719,16 @@ final class StoryContentCaptionComponent: Component {
|
||||
}
|
||||
})
|
||||
text = ""
|
||||
entities = []
|
||||
} else {
|
||||
text = ""
|
||||
entities = []
|
||||
}
|
||||
case let .unknown(name, _):
|
||||
authorName = name
|
||||
isChannel = false
|
||||
text = ""
|
||||
entities = []
|
||||
}
|
||||
|
||||
if let text {
|
||||
@ -741,8 +746,10 @@ final class StoryContentCaptionComponent: Component {
|
||||
PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
ForwardInfoPanelComponent(
|
||||
context: component.context,
|
||||
authorName: authorName,
|
||||
text: text,
|
||||
entities: entities,
|
||||
isChannel: isChannel,
|
||||
isVibrant: false,
|
||||
fillsWidth: false
|
||||
|
@ -757,15 +757,11 @@ final class StoryItemSetContainerSendMessage {
|
||||
let size = image.size.aspectFitted(CGSize(width: 512.0, height: 512.0))
|
||||
|
||||
func scaleImage(_ image: UIImage, size: CGSize, boundiingSize: CGSize) -> UIImage? {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = 1.0
|
||||
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
||||
return renderer.image { _ in
|
||||
image.draw(in: CGRect(origin: .zero, size: size))
|
||||
}
|
||||
} else {
|
||||
return TGScaleImageToPixelSize(image, size)
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = 1.0
|
||||
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
||||
return renderer.image { _ in
|
||||
image.draw(in: CGRect(origin: .zero, size: size))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,12 +41,23 @@ public func makeTelegramAccountAuxiliaryMethods(uploadInBackground: ((Postbox, M
|
||||
}
|
||||
|> castError(MediaResourceDataFetchError.self)
|
||||
|> mapToSignal { useModernPipeline -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> in
|
||||
fetchLocalFileVideoMediaResource(postbox: postbox, resource: resource, alwaysUseModernPipeline: useModernPipeline)
|
||||
return fetchLocalFileVideoMediaResource(postbox: postbox, resource: resource, alwaysUseModernPipeline: useModernPipeline)
|
||||
}
|
||||
} else if let resource = resource as? LocalFileGifMediaResource {
|
||||
return fetchLocalFileGifMediaResource(resource: resource)
|
||||
} else if let photoLibraryResource = resource as? PhotoLibraryMediaResource {
|
||||
return fetchPhotoLibraryResource(localIdentifier: photoLibraryResource.localIdentifier, width: photoLibraryResource.width, height: photoLibraryResource.height, format: photoLibraryResource.format, quality: photoLibraryResource.quality)
|
||||
return postbox.transaction { transaction -> Bool in
|
||||
var useExif = true
|
||||
let appConfig = currentAppConfiguration(transaction: transaction)
|
||||
if let data = appConfig.data, let _ = data["ios_killswitch_disable_use_photo_exif"] {
|
||||
useExif = false
|
||||
}
|
||||
return useExif
|
||||
}
|
||||
|> castError(MediaResourceDataFetchError.self)
|
||||
|> mapToSignal { useExif -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> in
|
||||
return fetchPhotoLibraryResource(localIdentifier: photoLibraryResource.localIdentifier, width: photoLibraryResource.width, height: photoLibraryResource.height, format: photoLibraryResource.format, quality: photoLibraryResource.quality, useExif: useExif)
|
||||
}
|
||||
} else if let resource = resource as? ICloudFileResource {
|
||||
return fetchICloudFileResource(resource: resource)
|
||||
} else if let resource = resource as? SecureIdLocalImageResource {
|
||||
|
@ -1163,6 +1163,8 @@ extension ChatControllerImpl {
|
||||
controller.openCamera = { [weak self] cameraView in
|
||||
if let cameraView = cameraView as? TGAttachmentCameraView {
|
||||
self?.openCamera(cameraView: cameraView)
|
||||
} else {
|
||||
self?.openCamera(cameraView: nil)
|
||||
}
|
||||
}
|
||||
controller.presentWebSearch = { [weak self, weak controller] mediaGroups, activateOnDisplay in
|
||||
|
@ -670,10 +670,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
if messages.count == 1 {
|
||||
for media in messages[0].media {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
for attribute in file.attributes {
|
||||
if case let .Sticker(_, packInfo, _) = attribute, packInfo != nil {
|
||||
loadStickerSaveStatus = file.fileId
|
||||
}
|
||||
if file.isSticker {
|
||||
loadStickerSaveStatus = file.fileId
|
||||
}
|
||||
if loadStickerSaveStatus == nil {
|
||||
loadCopyMediaResource = file.resource
|
||||
@ -1160,16 +1158,6 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
}
|
||||
}
|
||||
|
||||
for attribute in message.attributes {
|
||||
if hasExpandedAudioTranscription, let attribute = attribute as? AudioTranscriptionMessageAttribute {
|
||||
if !messageText.isEmpty {
|
||||
messageText.append("\n")
|
||||
}
|
||||
messageText.append(attribute.text)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var isPoll = false
|
||||
if messageText.isEmpty {
|
||||
for media in message.media {
|
||||
|
@ -116,7 +116,7 @@ public class ComposeControllerImpl: ViewController, ComposeController {
|
||||
self?.activateSearch()
|
||||
}
|
||||
|
||||
self.contactsNode.contactListNode.openPeer = { [weak self] peer, _ in
|
||||
self.contactsNode.contactListNode.openPeer = { [weak self] peer, _, _, _ in
|
||||
if case let .peer(peer, _, _) = peer {
|
||||
self?.openPeer(peerId: peer.id)
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import CounterContollerTitleView
|
||||
import EditableTokenListNode
|
||||
import PremiumUI
|
||||
import UndoUI
|
||||
import ContextUI
|
||||
|
||||
private func peerTokenTitle(accountPeerId: PeerId, peer: Peer, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) -> String {
|
||||
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.strings.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.strings.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
|
||||
guard let self else {
|
||||
return
|
||||
@ -765,3 +798,17 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import EditableTokenListNode
|
||||
import SolidRoundedButtonNode
|
||||
import ContextUI
|
||||
|
||||
private struct SearchResultEntry: Identifiable {
|
||||
let index: Int
|
||||
@ -58,6 +59,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
var requestDeactivateSearch: (() -> Void)?
|
||||
var requestOpenPeerFromSearch: ((ContactListPeerId) -> Void)?
|
||||
var openPeer: ((ContactListPeer) -> Void)?
|
||||
var openPeerMore: ((ContactListPeer, ASDisplayNode?, ContextGesture?) -> Void)?
|
||||
var openDisabledPeer: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
|
||||
var removeSelectedPeer: ((ContactListPeerId) -> Void)?
|
||||
var removeSelectedCategory: ((Int) -> Void)?
|
||||
@ -262,8 +264,12 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
|
||||
switch self.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
contactsNode.openPeer = { [weak self] peer, _ in
|
||||
self?.openPeer?(peer)
|
||||
contactsNode.openPeer = { [weak self] peer, action, sourceNode, gesture in
|
||||
if case .more = action {
|
||||
self?.openPeerMore?(peer, sourceNode, gesture)
|
||||
} else {
|
||||
self?.openPeer?(peer)
|
||||
}
|
||||
}
|
||||
contactsNode.openDisabledPeer = { [weak self] peer, reason in
|
||||
guard let self else {
|
||||
@ -362,7 +368,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
globalSearch: globalSearch,
|
||||
displaySavedMessages: displaySavedMessages
|
||||
))), 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?.openPeer?(peer)
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,14 @@
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import LegacyComponents
|
||||
import Display
|
||||
import WebPBinding
|
||||
|
||||
private func scaleImage(_ image: UIImage, size: CGSize, boundiingSize: CGSize) -> UIImage? {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = 1.0
|
||||
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
||||
return renderer.image { _ in
|
||||
image.draw(in: CGRect(origin: .zero, size: size))
|
||||
}
|
||||
} else {
|
||||
return TGScaleImageToPixelSize(image, size)
|
||||
let format = UIGraphicsImageRendererFormat()
|
||||
format.scale = 1.0
|
||||
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
||||
return renderer.image { _ in
|
||||
image.draw(in: CGRect(origin: .zero, size: size))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2164,15 +2164,33 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mode = .premiumGifting(birthdays: nil, selectToday: false)
|
||||
}
|
||||
|
||||
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) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}, limit: limit, reachedLimit: { limit in
|
||||
reachedLimitImpl?(limit)
|
||||
}))
|
||||
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) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
limit: limit,
|
||||
reachedLimit: { limit in
|
||||
reachedLimitImpl?(limit)
|
||||
},
|
||||
openProfile: { peer in
|
||||
openProfileImpl?(peer)
|
||||
},
|
||||
sendMessage: { peer in
|
||||
sendMessageImpl?(peer)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
reachedLimitImpl = { [weak controller] limit in
|
||||
guard let controller else {
|
||||
@ -2227,6 +2245,36 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
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
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user