Various fixes

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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))"

View File

@ -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
}

View File

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

View File

@ -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) {

View File

@ -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
}
}

View File

@ -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 {
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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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)

View File

@ -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",

View File

@ -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 {
@ -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: {},

View File

@ -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

View File

@ -1164,6 +1164,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

View File

@ -670,11 +670,9 @@ 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 {
if file.isSticker {
loadStickerSaveStatus = file.fileId
}
}
if loadStickerSaveStatus == nil {
loadCopyMediaResource = file.resource
}

View File

@ -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)
}

View File

@ -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.stings.Premium_Gift_ContactSelection_SendMessage, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor)
}, iconPosition: .left, action: { [weak self] _, a in
a(.default)
if let self {
self.params.sendMessage?(EnginePeer(peer))
}
})))
items.append(.action(ContextMenuActionItem(text: presentationData.stings.Premium_Gift_ContactSelection_OpenProfile, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor)
}, iconPosition: .left, action: { [weak self] _, a in
a(.default)
if let self {
self.params.openProfile?(EnginePeer(peer))
}
})))
let contextController = ContextController(presentationData: presentationData, source: .reference(ContactContextReferenceContentSource(controller: self, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
self.present(contextController, in: .window(.root))
}
self.contactsNode.openDisabledPeer = { [weak self] peer, reason in
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)
}
}

View File

@ -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,9 +264,13 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
switch self.contentNode {
case let .contacts(contactsNode):
contactsNode.openPeer = { [weak self] peer, _ in
contactsNode.openPeer = { [weak self] peer, action, sourceNode, gesture in
if case .more = action {
self?.openPeerMore?(peer, sourceNode, gesture)
} else {
self?.openPeer?(peer)
}
}
contactsNode.openDisabledPeer = { [weak self] peer, reason in
guard let self else {
return
@ -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)
}

View File

@ -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)
}

View File

@ -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
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
},
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
}