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.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";
|
||||||
|
@ -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 {
|
||||||
|
@ -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 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 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.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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))"
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import Display
|
|||||||
private func generateHistogram(cgImage: CGImage) -> ([[vImagePixelCount]], Int)? {
|
private func generateHistogram(cgImage: CGImage) -> ([[vImagePixelCount]], Int)? {
|
||||||
var sourceBuffer = vImage_Buffer()
|
var sourceBuffer = vImage_Buffer()
|
||||||
defer {
|
defer {
|
||||||
free(sourceBuffer.data)
|
sourceBuffer.data?.deallocate()
|
||||||
}
|
}
|
||||||
|
|
||||||
var cgImageFormat = vImage_CGImageFormat(
|
var cgImageFormat = vImage_CGImageFormat(
|
||||||
|
@ -5,6 +5,7 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import ImageCompression
|
import ImageCompression
|
||||||
import Accelerate.vImage
|
import Accelerate.vImage
|
||||||
|
import CoreImage
|
||||||
|
|
||||||
private final class RequestId {
|
private final class RequestId {
|
||||||
var id: PHImageRequestID?
|
var id: PHImageRequestID?
|
||||||
@ -15,24 +16,43 @@ private func resizedImage(_ image: UIImage, for size: CGSize) -> UIImage? {
|
|||||||
guard let cgImage = image.cgImage else {
|
guard let cgImage = image.cgImage else {
|
||||||
return nil
|
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,
|
var format = vImage_CGImageFormat(bitsPerComponent: 8,
|
||||||
bitsPerPixel: 32,
|
bitsPerPixel: 32,
|
||||||
colorSpace: nil,
|
colorSpace: nil,
|
||||||
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
|
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
|
||||||
version: 0,
|
version: 0,
|
||||||
decode: nil,
|
decode: nil,
|
||||||
renderingIntent: .defaultIntent)
|
renderingIntent: cgImage.renderingIntent)
|
||||||
|
|
||||||
var error: vImage_Error
|
var error: vImage_Error
|
||||||
var sourceBuffer = vImage_Buffer()
|
var sourceBuffer = vImage_Buffer()
|
||||||
defer { sourceBuffer.data.deallocate() }
|
defer { sourceBuffer.data?.deallocate() }
|
||||||
error = vImageBuffer_InitWithCGImage(&sourceBuffer,
|
error = vImageBuffer_InitWithCGImage(&sourceBuffer,
|
||||||
&format,
|
&format,
|
||||||
nil,
|
nil,
|
||||||
cgImage,
|
cgImage,
|
||||||
vImage_Flags(kvImageNoFlags))
|
vImage_Flags(kvImageNoFlags))
|
||||||
guard error == kvImageNoError else { return nil }
|
guard error == kvImageNoError else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var destinationBuffer = vImage_Buffer()
|
var destinationBuffer = vImage_Buffer()
|
||||||
error = vImageBuffer_Init(&destinationBuffer,
|
error = vImageBuffer_Init(&destinationBuffer,
|
||||||
@ -84,7 +104,7 @@ extension UIImage.Orientation {
|
|||||||
|
|
||||||
private let fetchPhotoWorkers = ThreadPool(threadCount: 3, threadPriority: 0.2)
|
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
|
return Signal { subscriber in
|
||||||
let queue = ThreadPoolQueue(threadPool: fetchPhotoWorkers)
|
let queue = ThreadPoolQueue(threadPool: fetchPhotoWorkers)
|
||||||
|
|
||||||
@ -96,7 +116,7 @@ public func fetchPhotoLibraryResource(localIdentifier: String, width: Int32?, he
|
|||||||
option.deliveryMode = .highQualityFormat
|
option.deliveryMode = .highQualityFormat
|
||||||
option.isNetworkAccessAllowed = true
|
option.isNetworkAccessAllowed = true
|
||||||
option.isSynchronous = false
|
option.isSynchronous = false
|
||||||
|
|
||||||
let size: CGSize
|
let size: CGSize
|
||||||
if let width, let height {
|
if let width, let height {
|
||||||
size = CGSize(width: CGFloat(width), height: CGFloat(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)
|
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
|
queue.addTask(ThreadPoolTask({ _ in
|
||||||
let startTime = CACurrentMediaTime()
|
let startTime = CACurrentMediaTime()
|
||||||
|
|
||||||
let semaphore = DispatchSemaphore(value: 0)
|
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 {
|
Queue.concurrentDefaultQueue().async {
|
||||||
requestId.with { current -> Void in
|
requestId.with { current -> Void in
|
||||||
if !current.invalidated {
|
if !current.invalidated {
|
||||||
|
@ -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 {
|
||||||
hasCamera = false
|
if case .assets(_, .story) = subject {
|
||||||
story = true
|
hasCamera = false
|
||||||
|
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
|
||||||
|
@ -35,15 +35,11 @@ public enum PreparedShareItems {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func scalePhotoImage(_ image: UIImage, dimensions: CGSize) -> UIImage? {
|
private func scalePhotoImage(_ image: UIImage, dimensions: CGSize) -> UIImage? {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
let format = UIGraphicsImageRendererFormat()
|
||||||
let format = UIGraphicsImageRendererFormat()
|
format.scale = 1.0
|
||||||
format.scale = 1.0
|
let renderer = UIGraphicsImageRenderer(size: dimensions, format: format)
|
||||||
let renderer = UIGraphicsImageRenderer(size: dimensions, format: format)
|
return renderer.image { _ in
|
||||||
return renderer.image { _ in
|
image.draw(in: CGRect(origin: .zero, size: dimensions))
|
||||||
image.draw(in: CGRect(origin: .zero, size: dimensions))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return TGScaleImageToPixelSize(image, dimensions)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,7 +230,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} 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)!
|
let imageData = scaledImage.jpegData(compressionQuality: 0.54)!
|
||||||
return .single(.preparing(false))
|
return .single(.preparing(false))
|
||||||
|> then(
|
|> then(
|
||||||
|
@ -166,7 +166,7 @@ public final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
}
|
}
|
||||||
let textColor = strongSelf.theme.chat.inputPanel.primaryTextColor
|
let textColor = strongSelf.theme.chat.inputPanel.primaryTextColor
|
||||||
if entities.count > 0 {
|
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 {
|
} else {
|
||||||
messageText = NSAttributedString(string: text, font: textFont, textColor: isMedia ? strongSelf.theme.chat.inputPanel.secondaryTextColor : strongSelf.theme.chat.inputPanel.primaryTextColor)
|
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,
|
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
|
||||||
|
@ -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
|
||||||
|
@ -228,20 +228,48 @@ func findEdgePoints(in pixelBuffer: CVPixelBuffer) -> [CGPoint] {
|
|||||||
return pixel >= 235
|
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
|
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 {
|
for x in 0..<width {
|
||||||
if isPixelWhiteAt(x: x, y: y) {
|
let point = Point(x: x, y: y)
|
||||||
startPoint = Point(x: x, y: y)
|
if isPixelWhiteAt(x: x, y: y) && !visited.contains(point) {
|
||||||
break outerLoop
|
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)
|
edgePoints.insert(startingPoint)
|
||||||
edgePath.append(startingPoint)
|
edgePath.append(startingPoint)
|
||||||
var currentPoint = startingPoint
|
var currentPoint = startingPoint
|
||||||
@ -326,7 +354,7 @@ private func getEdgesBitmap(_ ciImage: CIImage) -> CVPixelBuffer? {
|
|||||||
context.fill(CGRect(origin: .zero, size: size))
|
context.fill(CGRect(origin: .zero, size: size))
|
||||||
image.draw(in: CGRect(origin: .zero, size: size))
|
image.draw(in: CGRect(origin: .zero, size: size))
|
||||||
UIGraphicsPopContext()
|
UIGraphicsPopContext()
|
||||||
|
|
||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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",
|
||||||
|
@ -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 {
|
||||||
@ -102,7 +112,7 @@ public final class ForwardInfoPanelComponent: Component {
|
|||||||
iconView.frame = CGRect(origin: CGPoint(x: sideInset + UIScreenPixel, y: 5.0), size: image.size)
|
iconView.frame = CGRect(origin: CGPoint(x: sideInset + UIScreenPixel, y: 5.0), size: image.size)
|
||||||
}
|
}
|
||||||
titleOffset += 13.0
|
titleOffset += 13.0
|
||||||
|
|
||||||
let titleSize = self.title.update(
|
let titleSize = self.title.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
component: AnyComponent(MultilineTextComponent(
|
component: AnyComponent(MultilineTextComponent(
|
||||||
@ -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: {},
|
||||||
|
@ -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
|
||||||
|
@ -757,15 +757,11 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
let size = image.size.aspectFitted(CGSize(width: 512.0, height: 512.0))
|
let size = image.size.aspectFitted(CGSize(width: 512.0, height: 512.0))
|
||||||
|
|
||||||
func scaleImage(_ image: UIImage, size: CGSize, boundiingSize: CGSize) -> UIImage? {
|
func scaleImage(_ image: UIImage, size: CGSize, boundiingSize: CGSize) -> UIImage? {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
let format = UIGraphicsImageRendererFormat()
|
||||||
let format = UIGraphicsImageRendererFormat()
|
format.scale = 1.0
|
||||||
format.scale = 1.0
|
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
||||||
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
return renderer.image { _ in
|
||||||
return renderer.image { _ in
|
image.draw(in: CGRect(origin: .zero, size: size))
|
||||||
image.draw(in: CGRect(origin: .zero, size: size))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return TGScaleImageToPixelSize(image, size)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,12 +41,23 @@ public func makeTelegramAccountAuxiliaryMethods(uploadInBackground: ((Postbox, M
|
|||||||
}
|
}
|
||||||
|> castError(MediaResourceDataFetchError.self)
|
|> castError(MediaResourceDataFetchError.self)
|
||||||
|> mapToSignal { useModernPipeline -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> in
|
|> 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 {
|
} else if let resource = resource as? LocalFileGifMediaResource {
|
||||||
return fetchLocalFileGifMediaResource(resource: resource)
|
return fetchLocalFileGifMediaResource(resource: resource)
|
||||||
} else if let photoLibraryResource = resource as? PhotoLibraryMediaResource {
|
} 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 {
|
} else if let resource = resource as? ICloudFileResource {
|
||||||
return fetchICloudFileResource(resource: resource)
|
return fetchICloudFileResource(resource: resource)
|
||||||
} else if let resource = resource as? SecureIdLocalImageResource {
|
} else if let resource = resource as? SecureIdLocalImageResource {
|
||||||
|
@ -1163,6 +1163,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
|
||||||
|
@ -670,10 +670,8 @@ 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
|
||||||
@ -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
|
var isPoll = false
|
||||||
if messageText.isEmpty {
|
if messageText.isEmpty {
|
||||||
for media in message.media {
|
for media in message.media {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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.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
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,8 +264,12 @@ 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
|
||||||
self?.openPeer?(peer)
|
if case .more = action {
|
||||||
|
self?.openPeerMore?(peer, sourceNode, gesture)
|
||||||
|
} else {
|
||||||
|
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 {
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
import UIKit
|
import UIKit
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import LegacyComponents
|
|
||||||
import Display
|
import Display
|
||||||
import WebPBinding
|
import WebPBinding
|
||||||
|
|
||||||
private func scaleImage(_ image: UIImage, size: CGSize, boundiingSize: CGSize) -> UIImage? {
|
private func scaleImage(_ image: UIImage, size: CGSize, boundiingSize: CGSize) -> UIImage? {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
let format = UIGraphicsImageRendererFormat()
|
||||||
let format = UIGraphicsImageRendererFormat()
|
format.scale = 1.0
|
||||||
format.scale = 1.0
|
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
||||||
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
return renderer.image { _ in
|
||||||
return renderer.image { _ in
|
image.draw(in: CGRect(origin: .zero, size: size))
|
||||||
image.draw(in: CGRect(origin: .zero, size: size))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return TGScaleImageToPixelSize(image, size)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)?
|
||||||
if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) {
|
var sendMessageImpl: ((EnginePeer) -> Void)?
|
||||||
return true
|
|
||||||
} else {
|
let controller = context.sharedContext.makeContactMultiselectionController(
|
||||||
return false
|
ContactMultiselectionControllerParams(
|
||||||
}
|
context: context,
|
||||||
}, limit: limit, reachedLimit: { limit in
|
mode: mode,
|
||||||
reachedLimitImpl?(limit)
|
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
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user