mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-07-25 12:40:36 +00:00
Merge commit 'cf913d65d8c7eed8d97b8b7d79beabe37d6059ba'
This commit is contained in:
commit
b12fc72626
@ -279,6 +279,19 @@ public class ContactsController: ViewController {
|
|||||||
openPeer(peer, false)
|
openPeer(peer, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.contactsNode.requestAddContact = { [weak self] phoneNumber in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.view.endEditing(true)
|
||||||
|
strongSelf.context.sharedContext.openAddContact(context: strongSelf.context, firstName: "", lastName: "", phoneNumber: phoneNumber, label: defaultContactLabel, present: { [weak self] controller, arguments in
|
||||||
|
self?.present(controller, in: .window(.root), with: arguments)
|
||||||
|
}, pushController: { [weak self] controller in
|
||||||
|
(self?.navigationController as? NavigationController)?.pushViewController(controller)
|
||||||
|
}, completed: {
|
||||||
|
self?.deactivateSearch(animated: false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.contactsNode.openPeopleNearby = { [weak self] in
|
self.contactsNode.openPeopleNearby = { [weak self] in
|
||||||
let _ = (DeviceAccess.authorizationStatus(subject: .location(.tracking))
|
let _ = (DeviceAccess.authorizationStatus(subject: .location(.tracking))
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|
@ -55,6 +55,7 @@ final class ContactsControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
var requestDeactivateSearch: (() -> Void)?
|
var requestDeactivateSearch: (() -> Void)?
|
||||||
var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)?
|
var requestOpenPeerFromSearch: ((ContactListPeer) -> Void)?
|
||||||
|
var requestAddContact: ((String) -> Void)?
|
||||||
var openPeopleNearby: (() -> Void)?
|
var openPeopleNearby: (() -> Void)?
|
||||||
var openInvite: (() -> Void)?
|
var openInvite: (() -> Void)?
|
||||||
|
|
||||||
@ -184,7 +185,11 @@ final class ContactsControllerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global, .deviceContacts], openPeer: { [weak self] peer in
|
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global, .deviceContacts], addContact: { [weak self] phoneNumber in
|
||||||
|
if let requestAddContact = self?.requestAddContact {
|
||||||
|
requestAddContact(phoneNumber)
|
||||||
|
}
|
||||||
|
}, openPeer: { [weak self] peer in
|
||||||
if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch {
|
if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch {
|
||||||
requestOpenPeerFromSearch(peer)
|
requestOpenPeerFromSearch(peer)
|
||||||
}
|
}
|
||||||
|
@ -23,93 +23,151 @@ private enum ContactListSearchGroup {
|
|||||||
case deviceContacts
|
case deviceContacts
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ContactListSearchEntry: Identifiable, Comparable {
|
private enum ContactListSearchEntryId: Hashable {
|
||||||
let index: Int
|
case addContact
|
||||||
let theme: PresentationTheme
|
case peerId(ContactListPeerId)
|
||||||
let strings: PresentationStrings
|
|
||||||
let peer: ContactListPeer
|
|
||||||
let presence: PeerPresence?
|
|
||||||
let group: ContactListSearchGroup
|
|
||||||
let enabled: Bool
|
|
||||||
|
|
||||||
var stableId: ContactListPeerId {
|
static func <(lhs: ContactListSearchEntryId, rhs: ContactListSearchEntryId) -> Bool {
|
||||||
return self.peer.id
|
return lhs.hashValue < rhs.hashValue
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ContactListSearchEntryId, rhs: ContactListSearchEntryId) -> Bool {
|
||||||
|
switch lhs {
|
||||||
|
case .addContact:
|
||||||
|
switch rhs {
|
||||||
|
case .addContact:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .peerId(lhsId):
|
||||||
|
switch rhs {
|
||||||
|
case let .peerId(rhsId):
|
||||||
|
return lhsId == rhsId
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum ContactListSearchEntry: Comparable, Identifiable {
|
||||||
|
case addContact(PresentationTheme, PresentationStrings, String)
|
||||||
|
case peer(Int, PresentationTheme, PresentationStrings, ContactListPeer, PeerPresence?, ContactListSearchGroup, Bool)
|
||||||
|
|
||||||
|
var stableId: ContactListSearchEntryId {
|
||||||
|
switch self {
|
||||||
|
case .addContact:
|
||||||
|
return .addContact
|
||||||
|
case let .peer(_, _, _, peer, _, _, _):
|
||||||
|
return .peerId(peer.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: ContactListSearchEntry, rhs: ContactListSearchEntry) -> Bool {
|
static func ==(lhs: ContactListSearchEntry, rhs: ContactListSearchEntry) -> Bool {
|
||||||
if lhs.index != rhs.index {
|
switch lhs {
|
||||||
return false
|
case let .addContact(lhsTheme, lhsStrings, lhsPhoneNumber):
|
||||||
|
if case let .addContact(rhsTheme, rhsStrings, rhsPhoneNumber) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsPhoneNumber == rhsPhoneNumber {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case let .peer(lhsIndex, lhsTheme, lhsStrings, lhsPeer, lhsPresence, lhsGroup, lhsEnabled):
|
||||||
|
switch rhs {
|
||||||
|
case let .peer(rhsIndex, rhsTheme, rhsStrings, rhsPeer, rhsPresence, rhsGroup, rhsEnabled):
|
||||||
|
if lhsIndex != rhsIndex {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsTheme !== rhsTheme {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsStrings !== rhsStrings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsPeer != rhsPeer {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
|
||||||
|
if !lhsPresence.isEqual(to: rhsPresence) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (lhsPresence != nil) != (rhsPresence != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsGroup != rhsGroup {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsEnabled != rhsEnabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if lhs.theme !== rhs.theme {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.strings !== rhs.strings {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.peer != rhs.peer {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if let lhsPresence = lhs.presence, let rhsPresence = rhs.presence {
|
|
||||||
if !lhsPresence.isEqual(to: rhsPresence) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else if (lhs.presence != nil) != (rhs.presence != nil) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.group != rhs.group {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if lhs.enabled != rhs.enabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func <(lhs: ContactListSearchEntry, rhs: ContactListSearchEntry) -> Bool {
|
static func <(lhs: ContactListSearchEntry, rhs: ContactListSearchEntry) -> Bool {
|
||||||
return lhs.index < rhs.index
|
switch lhs {
|
||||||
|
case .addContact:
|
||||||
|
return true
|
||||||
|
case let .peer(lhsIndex, _, _, _, _, _, _):
|
||||||
|
switch rhs {
|
||||||
|
case .addContact:
|
||||||
|
return false
|
||||||
|
case let .peer(rhsIndex, _, _, _, _, _, _):
|
||||||
|
return lhsIndex < rhsIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem {
|
func item(context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem {
|
||||||
let header: ListViewItemHeader
|
switch self {
|
||||||
let status: ContactsPeerItemStatus
|
case let .addContact(theme, strings, phoneNumber):
|
||||||
switch self.group {
|
return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: {
|
||||||
case .contacts:
|
addContact?(phoneNumber)
|
||||||
header = ChatListSearchItemHeader(type: .contacts, theme: self.theme, strings: self.strings, actionTitle: nil, action: nil)
|
})
|
||||||
if let presence = self.presence {
|
case let .peer(_, theme, strings, peer, presence, group, enabled):
|
||||||
status = .presence(presence, timeFormat)
|
let header: ListViewItemHeader
|
||||||
} else {
|
let status: ContactsPeerItemStatus
|
||||||
status = .none
|
switch group {
|
||||||
|
case .contacts:
|
||||||
|
header = ChatListSearchItemHeader(type: .contacts, theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||||
|
if let presence = presence {
|
||||||
|
status = .presence(presence, timeFormat)
|
||||||
|
} else {
|
||||||
|
status = .none
|
||||||
|
}
|
||||||
|
case .global:
|
||||||
|
header = ChatListSearchItemHeader(type: .globalPeers, theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||||
|
if case let .peer(peer, _, _) = peer, let _ = peer.addressName {
|
||||||
|
status = .addressName("")
|
||||||
|
} else {
|
||||||
|
status = .none
|
||||||
|
}
|
||||||
|
case .deviceContacts:
|
||||||
|
header = ChatListSearchItemHeader(type: .deviceContacts, theme: theme, strings: strings, actionTitle: nil, action: nil)
|
||||||
|
status = .none
|
||||||
}
|
}
|
||||||
case .global:
|
var nativePeer: Peer?
|
||||||
header = ChatListSearchItemHeader(type: .globalPeers, theme: self.theme, strings: self.strings, actionTitle: nil, action: nil)
|
let peerItem: ContactsPeerItemPeer
|
||||||
if case let .peer(peer, _, _) = self.peer, let _ = peer.addressName {
|
switch peer {
|
||||||
status = .addressName("")
|
case let .peer(peer, _, _):
|
||||||
} else {
|
peerItem = .peer(peer: peer, chatPeer: peer)
|
||||||
status = .none
|
nativePeer = peer
|
||||||
|
case let .deviceContact(stableId, contact):
|
||||||
|
peerItem = .deviceContact(stableId: stableId, contact: contact)
|
||||||
}
|
}
|
||||||
case .deviceContacts:
|
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: peerItem, status: status, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
|
||||||
header = ChatListSearchItemHeader(type: .deviceContacts, theme: self.theme, strings: self.strings, actionTitle: nil, action: nil)
|
openPeer(peer)
|
||||||
status = .none
|
}, contextAction: contextAction.flatMap { contextAction in
|
||||||
|
return nativePeer.flatMap { nativePeer in
|
||||||
|
return { node, gesture in
|
||||||
|
contextAction(nativePeer, node, gesture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
let peer = self.peer
|
|
||||||
var nativePeer: Peer?
|
|
||||||
let peerItem: ContactsPeerItemPeer
|
|
||||||
switch peer {
|
|
||||||
case let .peer(peer, _, _):
|
|
||||||
peerItem = .peer(peer: peer, chatPeer: peer)
|
|
||||||
nativePeer = peer
|
|
||||||
case let .deviceContact(stableId, contact):
|
|
||||||
peerItem = .deviceContact(stableId: stableId, contact: contact)
|
|
||||||
}
|
|
||||||
return ContactsPeerItem(presentationData: ItemListPresentationData(presentationData), sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, context: context, peerMode: .peer, peer: peerItem, status: status, enabled: self.enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
|
|
||||||
openPeer(peer)
|
|
||||||
}, contextAction: contextAction.flatMap { contextAction in
|
|
||||||
return nativePeer.flatMap { nativePeer in
|
|
||||||
return { node, gesture in
|
|
||||||
contextAction(nativePeer, node, gesture)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,12 +178,12 @@ struct ContactListSearchContainerTransition {
|
|||||||
let isSearching: Bool
|
let isSearching: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) -> ContactListSearchContainerTransition {
|
private func contactListSearchContainerPreparedRecentTransition(from fromEntries: [ContactListSearchEntry], to toEntries: [ContactListSearchEntry], isSearching: Bool, context: AccountContext, presentationData: PresentationData, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, timeFormat: PresentationDateTimeFormat, addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) -> ContactListSearchContainerTransition {
|
||||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, timeFormat: timeFormat, openPeer: openPeer, contextAction: contextAction), directionHint: nil) }
|
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, timeFormat: timeFormat, addContact: addContact, openPeer: openPeer, contextAction: contextAction), directionHint: nil) }
|
||||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, timeFormat: timeFormat, openPeer: openPeer, contextAction: contextAction), directionHint: nil) }
|
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, nameSortOrder: nameSortOrder, nameDisplayOrder: nameDisplayOrder, timeFormat: timeFormat, addContact: addContact, openPeer: openPeer, contextAction: contextAction), directionHint: nil) }
|
||||||
|
|
||||||
return ContactListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching)
|
return ContactListSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, isSearching: isSearching)
|
||||||
}
|
}
|
||||||
@ -144,6 +202,7 @@ public struct ContactsSearchCategories: OptionSet {
|
|||||||
|
|
||||||
public final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
|
public final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
|
private let addContact: ((String) -> Void)?
|
||||||
private let openPeer: (ContactListPeer) -> Void
|
private let openPeer: (ContactListPeer) -> Void
|
||||||
private let contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?
|
private let contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
|
|
||||||
@ -163,8 +222,9 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(context: AccountContext, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) {
|
public init(context: AccountContext, onlyWriteable: Bool, categories: ContactsSearchCategories, filters: [ContactListFilter] = [.excludeSelf], addContact: ((String) -> Void)?, openPeer: @escaping (ContactListPeer) -> Void, contextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
|
self.addContact = addContact
|
||||||
self.openPeer = openPeer
|
self.openPeer = openPeer
|
||||||
self.contextAction = contextAction
|
self.contextAction = contextAction
|
||||||
|
|
||||||
@ -250,7 +310,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
if onlyWriteable {
|
if onlyWriteable {
|
||||||
enabled = canSendMessagesToPeer(peer)
|
enabled = canSendMessagesToPeer(peer)
|
||||||
}
|
}
|
||||||
entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer, isGlobal: false, participantCount: nil), presence: localPeersAndPresences.1[peer.id], group: .contacts, enabled: enabled))
|
entries.append(.peer(index, themeAndStrings.0, themeAndStrings.1, .peer(peer: peer, isGlobal: false, participantCount: nil), localPeersAndPresences.1[peer.id], .contacts, enabled))
|
||||||
if searchDeviceContacts, let user = peer as? TelegramUser, let phone = user.phone {
|
if searchDeviceContacts, let user = peer as? TelegramUser, let phone = user.phone {
|
||||||
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
|
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
|
||||||
}
|
}
|
||||||
@ -269,7 +329,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
enabled = canSendMessagesToPeer(peer.peer)
|
enabled = canSendMessagesToPeer(peer.peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), presence: nil, group: .global, enabled: enabled))
|
entries.append(.peer(index, themeAndStrings.0, themeAndStrings.1, .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), nil, .global, enabled))
|
||||||
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
|
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
|
||||||
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
|
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
|
||||||
}
|
}
|
||||||
@ -288,7 +348,7 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
enabled = canSendMessagesToPeer(peer.peer)
|
enabled = canSendMessagesToPeer(peer.peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), presence: nil, group: .global, enabled: enabled))
|
entries.append(.peer(index, themeAndStrings.0, themeAndStrings.1, .peer(peer: peer.peer, isGlobal: true, participantCount: peer.subscribers), nil, .global, enabled))
|
||||||
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
|
if searchDeviceContacts, let user = peer.peer as? TelegramUser, let phone = user.phone {
|
||||||
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
|
existingNormalizedPhoneNumbers.insert(DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phone)))
|
||||||
}
|
}
|
||||||
@ -309,10 +369,15 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
continue outer
|
continue outer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .deviceContact(stableId, contact.0), presence: nil, group: .deviceContacts, enabled: true))
|
entries.append(.peer(index, themeAndStrings.0, themeAndStrings.1, .deviceContact(stableId, contact.0), nil, .deviceContacts, true))
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let _ = addContact, isViablePhoneNumber(query) {
|
||||||
|
entries.append(.addContact(themeAndStrings.0, themeAndStrings.1, query))
|
||||||
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -328,7 +393,16 @@ public final class ContactsSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let previousItems = previousSearchItems.swap(items ?? [])
|
let previousItems = previousSearchItems.swap(items ?? [])
|
||||||
|
|
||||||
let transition = contactListSearchContainerPreparedRecentTransition(from: previousItems, to: items ?? [], isSearching: items != nil, context: context, presentationData: strongSelf.presentationData, nameSortOrder: strongSelf.presentationData.nameSortOrder, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder, timeFormat: strongSelf.presentationData.dateTimeFormat, openPeer: { peer in self?.listNode.clearHighlightAnimated(true)
|
var addContact: ((String) -> Void)?
|
||||||
|
if let originalAddContact = strongSelf.addContact {
|
||||||
|
addContact = { [weak self] phoneNumber in
|
||||||
|
self?.listNode.clearHighlightAnimated(true)
|
||||||
|
originalAddContact(phoneNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let transition = contactListSearchContainerPreparedRecentTransition(from: previousItems, to: items ?? [], isSearching: items != nil, context: context, presentationData: strongSelf.presentationData, nameSortOrder: strongSelf.presentationData.nameSortOrder, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder, timeFormat: strongSelf.presentationData.dateTimeFormat, addContact: addContact, openPeer: { peer in
|
||||||
|
self?.listNode.clearHighlightAnimated(true)
|
||||||
self?.openPeer(peer)
|
self?.openPeer(peer)
|
||||||
}, contextAction: strongSelf.contextAction)
|
}, contextAction: strongSelf.contextAction)
|
||||||
|
|
||||||
|
@ -505,7 +505,7 @@ final class InviteContactsControllerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.deviceContacts], openPeer: { [weak self] peer in
|
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.deviceContacts], addContact: nil, openPeer: { [weak self] peer in
|
||||||
if let strongSelf = self, case let .deviceContact(id, _) = peer {
|
if let strongSelf = self, case let .deviceContact(id, _) = peer {
|
||||||
strongSelf.selectionState = strongSelf.selectionState.withSelectedContactId(id)
|
strongSelf.selectionState = strongSelf.selectionState.withSelectedContactId(id)
|
||||||
strongSelf.requestDeactivateSearch?()
|
strongSelf.requestDeactivateSearch?()
|
||||||
|
@ -442,12 +442,14 @@ public class Window1 {
|
|||||||
self.keyboardFrameChangeObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: nil, using: { [weak self] notification in
|
self.keyboardFrameChangeObserver = NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: nil, using: { [weak self] notification in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var keyboardFrame: CGRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect()
|
var keyboardFrame: CGRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect()
|
||||||
if let keyboardView = strongSelf.statusBarHost?.keyboardView {
|
if #available(iOSApplicationExtension 14.2, iOS 14.2, *), UIAccessibility.prefersCrossFadeTransitions {
|
||||||
|
} else if let keyboardView = strongSelf.statusBarHost?.keyboardView {
|
||||||
if keyboardFrame.width.isEqual(to: keyboardView.bounds.width) && keyboardFrame.height.isEqual(to: keyboardView.bounds.height) && keyboardFrame.minX.isEqual(to: keyboardView.frame.minX) {
|
if keyboardFrame.width.isEqual(to: keyboardView.bounds.width) && keyboardFrame.height.isEqual(to: keyboardView.bounds.height) && keyboardFrame.minX.isEqual(to: keyboardView.frame.minX) {
|
||||||
keyboardFrame.origin.y = keyboardView.frame.minY
|
keyboardFrame.origin.y = keyboardView.frame.minY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var popoverDelta: CGFloat = 0.0
|
var popoverDelta: CGFloat = 0.0
|
||||||
|
|
||||||
let screenHeight: CGFloat
|
let screenHeight: CGFloat
|
||||||
@ -493,11 +495,7 @@ public class Window1 {
|
|||||||
} else {
|
} else {
|
||||||
keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
|
keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
|
||||||
if inPopover && !keyboardHeight.isZero {
|
if inPopover && !keyboardHeight.isZero {
|
||||||
if #available(iOSApplicationExtension 13.0, iOS 13.0, *) {
|
keyboardHeight = max(0.0, keyboardHeight - popoverDelta)
|
||||||
keyboardHeight = max(0.0, keyboardHeight - popoverDelta)
|
|
||||||
} else {
|
|
||||||
keyboardHeight = max(0.0, keyboardHeight - popoverDelta)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,12 +4,84 @@ import Photos
|
|||||||
import Postbox
|
import Postbox
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import ImageCompression
|
import ImageCompression
|
||||||
|
import Accelerate.vImage
|
||||||
|
|
||||||
private final class RequestId {
|
private final class RequestId {
|
||||||
var id: PHImageRequestID?
|
var id: PHImageRequestID?
|
||||||
var invalidated: Bool = false
|
var invalidated: Bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func resizedImage(_ image: UIImage, for size: CGSize) -> UIImage? {
|
||||||
|
guard let cgImage = image.cgImage else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var format = vImage_CGImageFormat(bitsPerComponent: 8,
|
||||||
|
bitsPerPixel: 32,
|
||||||
|
colorSpace: nil,
|
||||||
|
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.first.rawValue),
|
||||||
|
version: 0,
|
||||||
|
decode: nil,
|
||||||
|
renderingIntent: .defaultIntent)
|
||||||
|
|
||||||
|
var error: vImage_Error
|
||||||
|
var sourceBuffer = vImage_Buffer()
|
||||||
|
defer { sourceBuffer.data.deallocate() }
|
||||||
|
error = vImageBuffer_InitWithCGImage(&sourceBuffer,
|
||||||
|
&format,
|
||||||
|
nil,
|
||||||
|
cgImage,
|
||||||
|
vImage_Flags(kvImageNoFlags))
|
||||||
|
guard error == kvImageNoError else { return nil }
|
||||||
|
|
||||||
|
var destinationBuffer = vImage_Buffer()
|
||||||
|
error = vImageBuffer_Init(&destinationBuffer,
|
||||||
|
vImagePixelCount(size.height),
|
||||||
|
vImagePixelCount(size.width),
|
||||||
|
format.bitsPerPixel,
|
||||||
|
vImage_Flags(kvImageNoFlags))
|
||||||
|
guard error == kvImageNoError else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
error = vImageScale_ARGB8888(&sourceBuffer,
|
||||||
|
&destinationBuffer,
|
||||||
|
nil,
|
||||||
|
vImage_Flags(kvImageHighQualityResampling))
|
||||||
|
guard error == kvImageNoError else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let resizedImage =
|
||||||
|
vImageCreateCGImageFromBuffer(&destinationBuffer,
|
||||||
|
&format,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
vImage_Flags(kvImageNoAllocate),
|
||||||
|
&error)?.takeRetainedValue(),
|
||||||
|
error == kvImageNoError
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return UIImage(cgImage: resizedImage)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UIImage.Orientation {
|
||||||
|
init(_ cgOrientation: CGImagePropertyOrientation) {
|
||||||
|
switch cgOrientation {
|
||||||
|
case .up: self = .up
|
||||||
|
case .upMirrored: self = .upMirrored
|
||||||
|
case .down: self = .down
|
||||||
|
case .downMirrored: self = .downMirrored
|
||||||
|
case .left: self = .left
|
||||||
|
case .leftMirrored: self = .leftMirrored
|
||||||
|
case .right: self = .right
|
||||||
|
case .rightMirrored: self = .rightMirrored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
|
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
|
||||||
@ -28,7 +100,9 @@ public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaRe
|
|||||||
}
|
}
|
||||||
let size = CGSize(width: 1280.0, height: 1280.0)
|
let size = CGSize(width: 1280.0, height: 1280.0)
|
||||||
|
|
||||||
let requestIdValue = PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFit, options: option, resultHandler: { (image, info) -> Void in
|
let startTime = CACurrentMediaTime()
|
||||||
|
|
||||||
|
let requestIdValue = PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, 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 {
|
||||||
@ -42,17 +116,24 @@ public func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaRe
|
|||||||
//subscriber.putNext(.reset)
|
//subscriber.putNext(.reset)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_ = madeProgress.swap(true)
|
#if DEBUG
|
||||||
|
print("load completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_ = madeProgress.swap(true)
|
||||||
|
|
||||||
let scale = min(1.0, min(size.width / max(1.0, image.size.width), size.height / max(1.0, image.size.height)))
|
let scale = min(1.0, min(size.width / max(1.0, image.size.width), size.height / max(1.0, image.size.height)))
|
||||||
let scaledSize = CGSize(width: floor(image.size.width * scale), height: floor(image.size.height * scale))
|
let scaledSize = CGSize(width: floor(image.size.width * scale), height: floor(image.size.height * scale))
|
||||||
|
let scaledImage = resizedImage(image, for: scaledSize)
|
||||||
UIGraphicsBeginImageContextWithOptions(scaledSize, true, 1.0)
|
|
||||||
image.draw(in: CGRect(origin: CGPoint(), size: scaledSize))
|
#if DEBUG
|
||||||
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
|
print("scaled completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
||||||
UIGraphicsEndImageContext()
|
#endif
|
||||||
|
|
||||||
if let scaledImage = scaledImage, let data = compressImageToJPEG(scaledImage, quality: 0.6) {
|
if let scaledImage = scaledImage, let data = compressImageToJPEG(scaledImage, quality: 0.6) {
|
||||||
|
#if DEBUG
|
||||||
|
print("compression completion \((CACurrentMediaTime() - startTime) * 1000.0) ms")
|
||||||
|
#endif
|
||||||
subscriber.putNext(.dataPart(resourceOffset: 0, data: data, range: 0 ..< data.count, complete: true))
|
subscriber.putNext(.dataPart(resourceOffset: 0, data: data, range: 0 ..< data.count, complete: true))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
} else {
|
} else {
|
||||||
@ -149,3 +230,4 @@ public func fetchPhotoLibraryImage(localIdentifier: String, thumbnail: Bool) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ func stringForEstimatedDuration(strings: PresentationStrings, eta: Double) -> St
|
|||||||
if hours == 1 && minutes == 0 {
|
if hours == 1 && minutes == 0 {
|
||||||
string = strings.Map_ETAHours(1)
|
string = strings.Map_ETAHours(1)
|
||||||
} else {
|
} else {
|
||||||
string = strings.Map_ETAHours(9999).replacingOccurrences(of: "9999", with: String(format: "%d:%02d", arguments: [hours, minutes]))
|
string = strings.Map_ETAHours(10).replacingOccurrences(of: "10", with: String(format: "%d:%02d", arguments: [hours, minutes]))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
string = strings.Map_ETAMinutes(minutes)
|
string = strings.Map_ETAMinutes(minutes)
|
||||||
|
@ -125,7 +125,7 @@ NSData * _Nullable compressJPEGData(UIImage * _Nonnull sourceImage) {
|
|||||||
cinfo.arith_code = FALSE;
|
cinfo.arith_code = FALSE;
|
||||||
cinfo.dct_method = JDCT_ISLOW;
|
cinfo.dct_method = JDCT_ISLOW;
|
||||||
cinfo.optimize_coding = TRUE;
|
cinfo.optimize_coding = TRUE;
|
||||||
jpeg_set_quality(&cinfo, 78, 1);
|
jpeg_set_quality(&cinfo, 72, 1);
|
||||||
jpeg_simple_progression(&cinfo);
|
jpeg_simple_progression(&cinfo);
|
||||||
jpeg_start_compress(&cinfo, 1);
|
jpeg_start_compress(&cinfo, 1);
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
return self.updateLayout(width: width, previousSubtitle: nil, transition: transition)
|
return self.updateLayout(width: width, previousSubtitle: self.subtitle, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateLayout(width: CGFloat, previousSubtitle: String?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
private func updateLayout(width: CGFloat, previousSubtitle: String?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
|
@ -2089,6 +2089,9 @@ final class SharedApplicationContext {
|
|||||||
if #available(iOS 12.0, *) {
|
if #available(iOS 12.0, *) {
|
||||||
authorizationOptions.insert(.providesAppNotificationSettings)
|
authorizationOptions.insert(.providesAppNotificationSettings)
|
||||||
}
|
}
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
authorizationOptions.insert(.announcement)
|
||||||
|
}
|
||||||
notificationCenter.requestAuthorization(options: authorizationOptions, completionHandler: { result, _ in
|
notificationCenter.requestAuthorization(options: authorizationOptions, completionHandler: { result, _ in
|
||||||
completion(result)
|
completion(result)
|
||||||
if result {
|
if result {
|
||||||
|
@ -681,7 +681,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor
|
self.backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0)
|
||||||
|
|
||||||
self.collectionListPanel.addSubnode(self.listView)
|
self.collectionListPanel.addSubnode(self.listView)
|
||||||
self.collectionListPanel.addSubnode(self.gifListView)
|
self.collectionListPanel.addSubnode(self.gifListView)
|
||||||
@ -934,10 +934,12 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
|
|
||||||
if let currentView = strongSelf.currentView, let (topIndex, topItem) = visibleItems.top, let (bottomIndex, bottomItem) = visibleItems.bottom {
|
if let currentView = strongSelf.currentView, let (topIndex, topItem) = visibleItems.top, let (bottomIndex, bottomItem) = visibleItems.bottom {
|
||||||
if topIndex <= 10 && currentView.lower != nil {
|
if topIndex <= 10 && currentView.lower != nil {
|
||||||
let position: StickerPacksCollectionPosition = clipScrollPosition(.scroll(aroundIndex: (topItem as! ChatMediaInputStickerGridItem).index))
|
if let topItem = topItem as? ChatMediaInputStickerGridItem {
|
||||||
if strongSelf.currentStickerPacksCollectionPosition != position {
|
let position: StickerPacksCollectionPosition = clipScrollPosition(.scroll(aroundIndex: topItem.index))
|
||||||
strongSelf.currentStickerPacksCollectionPosition = position
|
if strongSelf.currentStickerPacksCollectionPosition != position {
|
||||||
strongSelf.itemCollectionsViewPosition.set(.single(position))
|
strongSelf.currentStickerPacksCollectionPosition = position
|
||||||
|
strongSelf.itemCollectionsViewPosition.set(.single(position))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if bottomIndex >= visibleItems.count - 10 && currentView.higher != nil {
|
} else if bottomIndex >= visibleItems.count - 10 && currentView.higher != nil {
|
||||||
var position: StickerPacksCollectionPosition?
|
var position: StickerPacksCollectionPosition?
|
||||||
@ -1052,7 +1054,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.collectionListSeparator.backgroundColor = theme.chat.inputMediaPanel.panelSeparatorColor
|
self.collectionListSeparator.backgroundColor = theme.chat.inputMediaPanel.panelSeparatorColor
|
||||||
self.backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor
|
self.backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0)
|
||||||
|
|
||||||
self.searchContainerNode?.updateThemeAndStrings(theme: theme, strings: strings)
|
self.searchContainerNode?.updateThemeAndStrings(theme: theme, strings: strings)
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
placeholderNode.frame = placeholderFrame
|
placeholderNode.frame = placeholderFrame
|
||||||
|
|
||||||
let theme = item.theme
|
let theme = item.theme
|
||||||
placeholderNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.15), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), data: item.stickerItem.file.immediateThumbnailData, size: placeholderFrame.size)
|
placeholderNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0), foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.15), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), data: item.stickerItem.file.immediateThumbnailData, size: placeholderFrame.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -966,7 +966,6 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
if let immediateThumbnailData = file?.immediateThumbnailData, let placeholderNode = strongSelf.placeholderNode {
|
if let immediateThumbnailData = file?.immediateThumbnailData, let placeholderNode = strongSelf.placeholderNode {
|
||||||
placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0x748391, alpha: 0.2), shimmeringColor: UIColor(rgb: 0x748391, alpha: 0.35), data: immediateThumbnailData, size: animationNodeFrame.size)
|
placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0x748391, alpha: 0.2), shimmeringColor: UIColor(rgb: 0x748391, alpha: 0.35), data: immediateThumbnailData, size: animationNodeFrame.size)
|
||||||
placeholderNode.frame = animationNodeFrame
|
placeholderNode.frame = animationNodeFrame
|
||||||
strongSelf.animationNode?.isHidden = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let animationNode = strongSelf.animationNode, let parentNode = strongSelf.greetingStickerParentNode, strongSelf.animateGreeting {
|
if let animationNode = strongSelf.animationNode, let parentNode = strongSelf.greetingStickerParentNode, strongSelf.animateGreeting {
|
||||||
@ -1304,6 +1303,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let beatingHearts: [UInt32] = [0x2764, 0x1F90E, 0x1F9E1, 0x1F499, 0x1F49A, 0x1F49C, 0x1F49B, 0x1F5A4, 0x1F90D]
|
let beatingHearts: [UInt32] = [0x2764, 0x1F90E, 0x1F9E1, 0x1F499, 0x1F49A, 0x1F49C, 0x1F49B, 0x1F5A4, 0x1F90D]
|
||||||
|
let heart = 0x2764
|
||||||
let peach = 0x1F351
|
let peach = 0x1F351
|
||||||
let coffin = 0x26B0
|
let coffin = 0x26B0
|
||||||
|
|
||||||
@ -1313,7 +1313,12 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
return view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
|
return view.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? .defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
if let text = self.item?.message.text, let firstScalar = text.unicodeScalars.first {
|
if let text = self.item?.message.text, var firstScalar = text.unicodeScalars.first {
|
||||||
|
var textEmoji = text.strippedEmoji
|
||||||
|
if beatingHearts.contains(firstScalar.value) {
|
||||||
|
textEmoji = "❤️"
|
||||||
|
firstScalar = UnicodeScalar(heart)!
|
||||||
|
}
|
||||||
return .optionalAction({
|
return .optionalAction({
|
||||||
if shouldPlay {
|
if shouldPlay {
|
||||||
let _ = (appConfiguration
|
let _ = (appConfiguration
|
||||||
@ -1323,7 +1328,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
let emojiSounds = AnimatedEmojiSoundsConfiguration.with(appConfiguration: appConfiguration, account: item.context.account)
|
let emojiSounds = AnimatedEmojiSoundsConfiguration.with(appConfiguration: appConfiguration, account: item.context.account)
|
||||||
for (emoji, file) in emojiSounds.sounds {
|
for (emoji, file) in emojiSounds.sounds {
|
||||||
if emoji.strippedEmoji == text.strippedEmoji {
|
if emoji.strippedEmoji == textEmoji.strippedEmoji {
|
||||||
let mediaManager = item.context.sharedContext.mediaManager
|
let mediaManager = item.context.sharedContext.mediaManager
|
||||||
let mediaPlayer = MediaPlayer(audioSessionManager: mediaManager.audioSession, postbox: item.context.account.postbox, resourceReference: .standalone(resource: file.resource), streamable: .none, video: false, preferSoftwareDecoding: false, enableSound: true, fetchAutomatically: true, ambient: true)
|
let mediaPlayer = MediaPlayer(audioSessionManager: mediaManager.audioSession, postbox: item.context.account.postbox, resourceReference: .standalone(resource: file.resource), streamable: .none, video: false, preferSoftwareDecoding: false, enableSound: true, fetchAutomatically: true, ambient: true)
|
||||||
mediaPlayer.togglePlayPause()
|
mediaPlayer.togglePlayPause()
|
||||||
@ -1335,24 +1340,24 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
strongSelf.mediaStatusDisposable.set((mediaPlayer.status
|
strongSelf.mediaStatusDisposable.set((mediaPlayer.status
|
||||||
|> deliverOnMainQueue).start(next: { [weak self, weak animationNode] status in
|
|> deliverOnMainQueue).start(next: { [weak self, weak animationNode] status in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if firstScalar.value == coffin || firstScalar.value == peach {
|
|
||||||
var haptic: EmojiHaptic
|
var haptic: EmojiHaptic?
|
||||||
if let current = strongSelf.haptic {
|
if let current = strongSelf.haptic {
|
||||||
haptic = current
|
haptic = current
|
||||||
} else {
|
} else {
|
||||||
if beatingHearts.contains(firstScalar.value) {
|
if firstScalar.value == heart {
|
||||||
haptic = HeartbeatHaptic()
|
haptic = HeartbeatHaptic()
|
||||||
} else if firstScalar.value == coffin {
|
} else if firstScalar.value == coffin {
|
||||||
haptic = CoffinHaptic()
|
haptic = CoffinHaptic()
|
||||||
} else {
|
} else if firstScalar.value == peach {
|
||||||
haptic = PeachHaptic()
|
haptic = PeachHaptic()
|
||||||
}
|
|
||||||
haptic.enabled = true
|
|
||||||
strongSelf.haptic = haptic
|
|
||||||
}
|
|
||||||
if !haptic.active {
|
|
||||||
haptic.start(time: 0.0)
|
|
||||||
}
|
}
|
||||||
|
haptic?.enabled = true
|
||||||
|
strongSelf.haptic = haptic
|
||||||
|
}
|
||||||
|
|
||||||
|
if let haptic = haptic, !haptic.active {
|
||||||
|
haptic.start(time: 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch status.status {
|
switch status.status {
|
||||||
|
@ -349,7 +349,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
|
|||||||
|
|
||||||
loop: for media in self.message.media {
|
loop: for media in self.message.media {
|
||||||
if let telegramFile = media as? TelegramMediaFile {
|
if let telegramFile = media as? TelegramMediaFile {
|
||||||
if telegramFile.isAnimatedSticker, (self.message.id.peerId.namespace == Namespaces.Peer.SecretChat || !telegramFile.previewRepresentations.isEmpty), let size = telegramFile.size, size > 0 && size <= 128 * 1024 {
|
if telegramFile.isAnimatedSticker, let size = telegramFile.size, size > 0 && size <= 128 * 1024 {
|
||||||
if self.message.id.peerId.namespace == Namespaces.Peer.SecretChat {
|
if self.message.id.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||||
if telegramFile.fileId.namespace == Namespaces.Media.CloudFile {
|
if telegramFile.fileId.namespace == Namespaces.Media.CloudFile {
|
||||||
var isValidated = false
|
var isValidated = false
|
||||||
|
@ -124,7 +124,7 @@ final class ComposeControllerNode: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global], openPeer: { [weak self] peer in
|
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: [.cloudContacts, .global], addContact: nil, openPeer: { [weak self] peer in
|
||||||
if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch, case let .peer(peer, _, _) = peer {
|
if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch, case let .peer(peer, _, _) = peer {
|
||||||
requestOpenPeerFromSearch(peer.id)
|
requestOpenPeerFromSearch(peer.id)
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ final class ContactSelectionControllerNode: ASDisplayNode {
|
|||||||
} else {
|
} else {
|
||||||
categories.insert(.global)
|
categories.insert(.global)
|
||||||
}
|
}
|
||||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: categories, openPeer: { [weak self] peer in
|
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: false, categories: categories, addContact: nil, openPeer: { [weak self] peer in
|
||||||
self?.requestOpenPeerFromSearch?(peer)
|
self?.requestOpenPeerFromSearch?(peer)
|
||||||
}, contextAction: nil), cancel: { [weak self] in
|
}, contextAction: nil), cancel: { [weak self] in
|
||||||
if let requestDeactivateSearch = self?.requestDeactivateSearch {
|
if let requestDeactivateSearch = self?.requestDeactivateSearch {
|
||||||
|
@ -30,7 +30,7 @@ final class MultiplexedVideoPlaceholderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.effectNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.effectNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.effectNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.2), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), shapes: [.rect(rect: CGRect(origin: CGPoint(), size: size))], size: bounds.size)
|
self.effectNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0), foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.2), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), shapes: [.rect(rect: CGRect(origin: CGPoint(), size: size))], size: bounds.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
||||||
|
@ -97,7 +97,7 @@ final class PaneSearchContainerNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
self.backgroundNode.backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor
|
self.backgroundNode.backgroundColor = theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0)
|
||||||
self.contentNode.updateThemeAndStrings(theme: theme, strings: strings)
|
self.contentNode.updateThemeAndStrings(theme: theme, strings: strings)
|
||||||
self.searchBar.updateThemeAndStrings(theme: theme, strings: strings)
|
self.searchBar.updateThemeAndStrings(theme: theme, strings: strings)
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
|||||||
}, placeholder: placeholderNode)
|
}, placeholder: placeholderNode)
|
||||||
|
|
||||||
} else if let contactListNode = self.contactListNode, contactListNode.supernode != nil {
|
} else if let contactListNode = self.contactListNode, contactListNode.supernode != nil {
|
||||||
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: true, categories: [.cloudContacts, .global], openPeer: { [weak self] peer in
|
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ContactsSearchContainerNode(context: self.context, onlyWriteable: true, categories: [.cloudContacts, .global], addContact: nil, openPeer: { [weak self] peer in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
switch peer {
|
switch peer {
|
||||||
case let .peer(peer, _, _):
|
case let .peer(peer, _, _):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user