mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-19 21:00:10 +00:00
Various improvements
This commit is contained in:
parent
c7ab78a8a1
commit
fdb4b80e27
@ -155,6 +155,7 @@ public struct ChatAvailableMessageActionOptions: OptionSet {
|
|||||||
public static let sendScheduledNow = ChatAvailableMessageActionOptions(rawValue: 1 << 8)
|
public static let sendScheduledNow = ChatAvailableMessageActionOptions(rawValue: 1 << 8)
|
||||||
public static let editScheduledTime = ChatAvailableMessageActionOptions(rawValue: 1 << 9)
|
public static let editScheduledTime = ChatAvailableMessageActionOptions(rawValue: 1 << 9)
|
||||||
public static let externalShare = ChatAvailableMessageActionOptions(rawValue: 1 << 10)
|
public static let externalShare = ChatAvailableMessageActionOptions(rawValue: 1 << 10)
|
||||||
|
public static let sendGift = ChatAvailableMessageActionOptions(rawValue: 1 << 11)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ChatAvailableMessageActions {
|
public struct ChatAvailableMessageActions {
|
||||||
@ -802,6 +803,7 @@ public protocol TelegramRootControllerInterface: NavigationController {
|
|||||||
func getPrivacySettings() -> Promise<AccountPrivacySettings?>?
|
func getPrivacySettings() -> Promise<AccountPrivacySettings?>?
|
||||||
func openSettings()
|
func openSettings()
|
||||||
func openBirthdaySetup()
|
func openBirthdaySetup()
|
||||||
|
func openPhotoSetup()
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol QuickReplySetupScreenInitialData: AnyObject {
|
public protocol QuickReplySetupScreenInitialData: AnyObject {
|
||||||
|
|||||||
@ -959,6 +959,7 @@ public protocol PeerInfoScreen: ViewController {
|
|||||||
func toggleStorySelection(ids: [Int32], isSelected: Bool)
|
func toggleStorySelection(ids: [Int32], isSelected: Bool)
|
||||||
func togglePaneIsReordering(isReordering: Bool)
|
func togglePaneIsReordering(isReordering: Bool)
|
||||||
func cancelItemSelection()
|
func cancelItemSelection()
|
||||||
|
func openAvatarSetup()
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Peer {
|
public extension Peer {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ private let repliesIcon = generateTintedImage(image: UIImage(bundleImageName: "A
|
|||||||
private let anonymousSavedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: .white)
|
private let anonymousSavedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: .white)
|
||||||
private let anonymousSavedMessagesDarkIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: UIColor(white: 1.0, alpha: 0.4))
|
private let anonymousSavedMessagesDarkIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/AnonymousSenderIcon"), color: UIColor(white: 1.0, alpha: 0.4))
|
||||||
private let myNotesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/MyNotesIcon"), color: .white)
|
private let myNotesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/MyNotesIcon"), color: .white)
|
||||||
|
private let cameraIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/CameraIcon"), color: .white)
|
||||||
|
|
||||||
public func avatarPlaceholderFont(size: CGFloat) -> UIFont {
|
public func avatarPlaceholderFont(size: CGFloat) -> UIFont {
|
||||||
return Font.with(size: size, design: .round, weight: .bold)
|
return Font.with(size: size, design: .round, weight: .bold)
|
||||||
@ -86,7 +87,7 @@ public func calculateAvatarColors(context: AccountContext?, explicitColorIndex:
|
|||||||
}
|
}
|
||||||
|
|
||||||
let colors: [UIColor]
|
let colors: [UIColor]
|
||||||
if icon != .none {
|
if icon != .none && icon != .cameraIcon {
|
||||||
if case .deletedIcon = icon {
|
if case .deletedIcon = icon {
|
||||||
colors = AvatarNode.grayscaleColors
|
colors = AvatarNode.grayscaleColors
|
||||||
} else if case .phoneIcon = icon {
|
} else if case .phoneIcon = icon {
|
||||||
@ -196,6 +197,7 @@ public enum AvatarNodeIcon: Equatable {
|
|||||||
case deletedIcon
|
case deletedIcon
|
||||||
case phoneIcon
|
case phoneIcon
|
||||||
case repostIcon
|
case repostIcon
|
||||||
|
case cameraIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AvatarNodeImageOverride: Equatable {
|
public enum AvatarNodeImageOverride: Equatable {
|
||||||
@ -210,6 +212,7 @@ public enum AvatarNodeImageOverride: Equatable {
|
|||||||
case deletedIcon
|
case deletedIcon
|
||||||
case phoneIcon
|
case phoneIcon
|
||||||
case repostIcon
|
case repostIcon
|
||||||
|
case cameraIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum AvatarNodeColorOverride {
|
public enum AvatarNodeColorOverride {
|
||||||
@ -540,6 +543,9 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
case .phoneIcon:
|
case .phoneIcon:
|
||||||
representation = nil
|
representation = nil
|
||||||
icon = .phoneIcon
|
icon = .phoneIcon
|
||||||
|
case .cameraIcon:
|
||||||
|
representation = nil
|
||||||
|
icon = .cameraIcon
|
||||||
}
|
}
|
||||||
} else if peer?.restrictionText(platform: "ios", contentSettings: contentSettings) == nil {
|
} else if peer?.restrictionText(platform: "ios", contentSettings: contentSettings) == nil {
|
||||||
representation = peer?.smallProfileImage
|
representation = peer?.smallProfileImage
|
||||||
@ -716,6 +722,9 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
case .phoneIcon:
|
case .phoneIcon:
|
||||||
representation = nil
|
representation = nil
|
||||||
icon = .phoneIcon
|
icon = .phoneIcon
|
||||||
|
case .cameraIcon:
|
||||||
|
representation = nil
|
||||||
|
icon = .cameraIcon
|
||||||
}
|
}
|
||||||
} else if peer?.restrictionText(platform: "ios", contentSettings: genericContext.currentContentSettings.with { $0 }) == nil {
|
} else if peer?.restrictionText(platform: "ios", contentSettings: genericContext.currentContentSettings.with { $0 }) == nil {
|
||||||
representation = peer?.smallProfileImage
|
representation = peer?.smallProfileImage
|
||||||
@ -959,6 +968,15 @@ public final class AvatarNode: ASDisplayNode {
|
|||||||
if let myNotesIcon = myNotesIcon {
|
if let myNotesIcon = myNotesIcon {
|
||||||
context.draw(myNotesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - myNotesIcon.size.width) / 2.0), y: floor((bounds.size.height - myNotesIcon.size.height) / 2.0)), size: myNotesIcon.size))
|
context.draw(myNotesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - myNotesIcon.size.width) / 2.0), y: floor((bounds.size.height - myNotesIcon.size.height) / 2.0)), size: myNotesIcon.size))
|
||||||
}
|
}
|
||||||
|
} else if case .cameraIcon = parameters.icon {
|
||||||
|
let factor = bounds.size.width / 40.0
|
||||||
|
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||||
|
context.scaleBy(x: factor, y: -factor)
|
||||||
|
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
|
||||||
|
|
||||||
|
if let cameraIcon = cameraIcon {
|
||||||
|
context.draw(cameraIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - cameraIcon.size.width) / 2.0), y: floor((bounds.size.height - cameraIcon.size.height) / 2.0)), size: cameraIcon.size))
|
||||||
|
}
|
||||||
} else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme, !parameters.hasImage {
|
} else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme, !parameters.hasImage {
|
||||||
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
|
||||||
context.scaleBy(x: 1.0, y: -1.0)
|
context.scaleBy(x: 1.0, y: -1.0)
|
||||||
|
|||||||
@ -246,7 +246,9 @@ public func peerAvatarImage(postbox: Postbox, network: Network, peerReference: P
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
let filledSize = CGSize(width: dataImage.width, height: dataImage.height).aspectFilled(displayDimensions)
|
||||||
|
|
||||||
|
context.draw(dataImage, in: CGRect(origin: CGPoint(x: floor((displayDimensions.width - filledSize.width) / 2.0), y: floor((displayDimensions.height - filledSize.height) / 2.0)), size: filledSize).insetBy(dx: inset, dy: inset))
|
||||||
if blurred {
|
if blurred {
|
||||||
context.setBlendMode(.normal)
|
context.setBlendMode(.normal)
|
||||||
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.45).cgColor)
|
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.45).cgColor)
|
||||||
|
|||||||
@ -162,6 +162,7 @@ public final class BrowserBookmarksScreen: ViewController {
|
|||||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||||
}, playMessageEffect: { _ in
|
}, playMessageEffect: { _ in
|
||||||
}, editMessageFactCheck: { _ in
|
}, editMessageFactCheck: { _ in
|
||||||
|
}, sendGift: { _ in
|
||||||
}, requestMessageUpdate: { _, _ in
|
}, requestMessageUpdate: { _, _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, dismissTextInput: {
|
}, dismissTextInput: {
|
||||||
|
|||||||
@ -214,7 +214,8 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
|||||||
theme: PresentationTheme,
|
theme: PresentationTheme,
|
||||||
mode: HorizontalPeerItemMode,
|
mode: HorizontalPeerItemMode,
|
||||||
strings: PresentationStrings,
|
strings: PresentationStrings,
|
||||||
peerSelected: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void, isPeerSelected: @escaping (EnginePeer.Id) -> Bool, share: Bool = false) {
|
peerSelected: @escaping (EnginePeer) -> Void, peerContextAction: @escaping (EnginePeer, ASDisplayNode, ContextGesture?, CGPoint?) -> Void, isPeerSelected: @escaping (EnginePeer.Id) -> Bool, share: Bool = false)
|
||||||
|
{
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.themeAndStringsPromise = Promise((self.theme, self.strings))
|
self.themeAndStringsPromise = Promise((self.theme, self.strings))
|
||||||
@ -225,6 +226,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
|||||||
self.isPeerSelected = isPeerSelected
|
self.isPeerSelected = isPeerSelected
|
||||||
|
|
||||||
self.listView = ListView()
|
self.listView = ListView()
|
||||||
|
self.listView.preloadPages = false
|
||||||
self.listView.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
self.listView.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
self.listView.accessibilityPageScrolledString = { row, count in
|
self.listView.accessibilityPageScrolledString = { row, count in
|
||||||
return strings.VoiceOver_ScrollStatus(row, count).string
|
return strings.VoiceOver_ScrollStatus(row, count).string
|
||||||
@ -340,11 +342,6 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
|||||||
)
|
)
|
||||||
|
|
||||||
strongSelf.enqueueTransition(transition)
|
strongSelf.enqueueTransition(transition)
|
||||||
|
|
||||||
if !strongSelf.didSetReady {
|
|
||||||
strongSelf.ready.set(.single(true))
|
|
||||||
strongSelf.didSetReady = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
if case .actionSheet = mode {
|
if case .actionSheet = mode {
|
||||||
@ -371,7 +368,20 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
|||||||
} else if transition.animated {
|
} else if transition.animated {
|
||||||
options.insert(.AnimateInsertion)
|
options.insert(.AnimateInsertion)
|
||||||
}
|
}
|
||||||
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { _ in })
|
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.didSetReady {
|
||||||
|
self.ready.set(.single(true))
|
||||||
|
self.didSetReady = true
|
||||||
|
}
|
||||||
|
if !self.listView.preloadPages {
|
||||||
|
Queue.mainQueue().after(0.5) {
|
||||||
|
self.listView.preloadPages = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1202,6 +1202,15 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.chatListDisplayNode.mainContainerNode.openPhotoSetup = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let rootController = self.navigationController as? TelegramRootControllerInterface {
|
||||||
|
rootController.openPhotoSetup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.chatListDisplayNode.mainContainerNode.openPremiumManagement = { [weak self] in
|
self.chatListDisplayNode.mainContainerNode.openPremiumManagement = { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -354,6 +354,9 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
|
|||||||
itemNode.listNode.openWebApp = { [weak self] amount in
|
itemNode.listNode.openWebApp = { [weak self] amount in
|
||||||
self?.openWebApp?(amount)
|
self?.openWebApp?(amount)
|
||||||
}
|
}
|
||||||
|
itemNode.listNode.openPhotoSetup = { [weak self] in
|
||||||
|
self?.openPhotoSetup?()
|
||||||
|
}
|
||||||
|
|
||||||
self.currentItemStateValue.set(itemNode.listNode.state |> map { state in
|
self.currentItemStateValue.set(itemNode.listNode.state |> map { state in
|
||||||
let filterId: Int32?
|
let filterId: Int32?
|
||||||
@ -421,6 +424,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
|
|||||||
var openStories: ((ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void)?
|
var openStories: ((ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void)?
|
||||||
var openStarsTopup: ((Int64?) -> Void)?
|
var openStarsTopup: ((Int64?) -> Void)?
|
||||||
var openWebApp: ((TelegramUser) -> Void)?
|
var openWebApp: ((TelegramUser) -> Void)?
|
||||||
|
var openPhotoSetup: (() -> Void)?
|
||||||
var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)?
|
var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)?
|
||||||
var didBeginSelectingChats: (() -> Void)?
|
var didBeginSelectingChats: (() -> Void)?
|
||||||
var canExpandHiddenItems: (() -> Bool)?
|
var canExpandHiddenItems: (() -> Bool)?
|
||||||
|
|||||||
@ -115,7 +115,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
presentationData: ChatListPresentationData,
|
presentationData: ChatListPresentationData,
|
||||||
filter: ChatListNodePeersFilter,
|
filter: ChatListNodePeersFilter,
|
||||||
key: ChatListSearchPaneKey,
|
key: ChatListSearchPaneKey,
|
||||||
peerSelected: @escaping (EnginePeer, Int64?, Bool) -> Void,
|
peerSelected: @escaping (EnginePeer, Int64?, Bool, OpenPeerAction) -> Void,
|
||||||
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
|
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
|
||||||
peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?,
|
peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?,
|
||||||
clearRecentlySearchedPeers: @escaping () -> Void,
|
clearRecentlySearchedPeers: @escaping () -> Void,
|
||||||
@ -130,7 +130,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .topPeers(peers, theme, strings):
|
case let .topPeers(peers, theme, strings):
|
||||||
return ChatListRecentPeersListItem(theme: theme, strings: strings, context: context, peers: peers, peerSelected: { peer in
|
return ChatListRecentPeersListItem(theme: theme, strings: strings, context: context, peers: peers, peerSelected: { peer in
|
||||||
peerSelected(peer, nil, false)
|
peerSelected(peer, nil, false, .generic)
|
||||||
}, peerContextAction: { peer, node, gesture, location in
|
}, peerContextAction: { peer, node, gesture, location in
|
||||||
if let peerContextAction = peerContextAction {
|
if let peerContextAction = peerContextAction {
|
||||||
peerContextAction(peer, .recentPeers, node, gesture, location)
|
peerContextAction(peer, .recentPeers, node, gesture, location)
|
||||||
@ -287,21 +287,28 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var buttonAction: ContactsPeerItemButtonAction?
|
var buttonAction: ContactsPeerItemButtonAction?
|
||||||
if case .chats = key, case let .user(user) = primaryPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
|
if [.chats, .apps].contains(key), case let .user(user) = primaryPeer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp) {
|
||||||
buttonAction = ContactsPeerItemButtonAction(
|
buttonAction = ContactsPeerItemButtonAction(
|
||||||
title: presentationData.strings.ChatList_Search_Open,
|
title: presentationData.strings.ChatList_Search_Open,
|
||||||
action: { peer, _, _ in
|
action: { peer, _, _ in
|
||||||
peerSelected(primaryPeer, nil, true)
|
peerSelected(primaryPeer, nil, false, .openApp)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var peerMode: ContactsPeerItemPeerMode
|
||||||
|
if case .apps = key {
|
||||||
|
peerMode = .app(isPopular: section == .popularApps)
|
||||||
|
} else {
|
||||||
|
peerMode = .generalSearch(isSavedMessages: false)
|
||||||
|
}
|
||||||
|
|
||||||
return ContactsPeerItem(
|
return ContactsPeerItem(
|
||||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
||||||
sortOrder: nameSortOrder,
|
sortOrder: nameSortOrder,
|
||||||
displayOrder: nameDisplayOrder,
|
displayOrder: nameDisplayOrder,
|
||||||
context: context,
|
context: context,
|
||||||
peerMode: .generalSearch(isSavedMessages: false),
|
peerMode: peerMode,
|
||||||
peer: .peer(peer: primaryPeer, chatPeer: chatPeer),
|
peer: .peer(peer: primaryPeer, chatPeer: chatPeer),
|
||||||
status: status,
|
status: status,
|
||||||
badge: badge,
|
badge: badge,
|
||||||
@ -315,7 +322,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
|||||||
alwaysShowLastSeparator: key == .apps,
|
alwaysShowLastSeparator: key == .apps,
|
||||||
action: { _ in
|
action: { _ in
|
||||||
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
|
if let chatPeer = peer.peer.peers[peer.peer.peerId] {
|
||||||
peerSelected(EnginePeer(chatPeer), nil, section == .recommendedChannels || section == .popularApps)
|
peerSelected(EnginePeer(chatPeer), nil, section == .recommendedChannels, section == .popularApps ? .info : .generic)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
disabledAction: { _ in
|
disabledAction: { _ in
|
||||||
@ -1067,6 +1074,12 @@ public struct ChatListSearchContainerTransition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum OpenPeerAction {
|
||||||
|
case generic
|
||||||
|
case info
|
||||||
|
case openApp
|
||||||
|
}
|
||||||
|
|
||||||
private func chatListSearchContainerPreparedRecentTransition(
|
private func chatListSearchContainerPreparedRecentTransition(
|
||||||
from fromEntries: [ChatListRecentEntry],
|
from fromEntries: [ChatListRecentEntry],
|
||||||
to toEntries: [ChatListRecentEntry],
|
to toEntries: [ChatListRecentEntry],
|
||||||
@ -1075,7 +1088,7 @@ private func chatListSearchContainerPreparedRecentTransition(
|
|||||||
presentationData: ChatListPresentationData,
|
presentationData: ChatListPresentationData,
|
||||||
filter: ChatListNodePeersFilter,
|
filter: ChatListNodePeersFilter,
|
||||||
key: ChatListSearchPaneKey,
|
key: ChatListSearchPaneKey,
|
||||||
peerSelected: @escaping (EnginePeer, Int64?, Bool) -> Void,
|
peerSelected: @escaping (EnginePeer, Int64?, Bool, OpenPeerAction) -> Void,
|
||||||
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
|
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
|
||||||
peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?,
|
peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?,
|
||||||
clearRecentlySearchedPeers: @escaping () -> Void,
|
clearRecentlySearchedPeers: @escaping () -> Void,
|
||||||
@ -1468,6 +1481,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
self.selectedMessagesPromise.set(.single(self.selectedMessages))
|
self.selectedMessagesPromise.set(.single(self.selectedMessages))
|
||||||
|
|
||||||
self.recentListNode = ListView()
|
self.recentListNode = ListView()
|
||||||
|
self.recentListNode.preloadPages = false
|
||||||
self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||||
self.recentListNode.accessibilityPageScrolledString = { row, count in
|
self.recentListNode.accessibilityPageScrolledString = { row, count in
|
||||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
||||||
@ -3014,6 +3028,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}, dismissNotice: { _ in
|
}, dismissNotice: { _ in
|
||||||
}, editPeer: { _ in
|
}, editPeer: { _ in
|
||||||
}, openWebApp: { _ in
|
}, openWebApp: { _ in
|
||||||
|
}, openPhotoSetup: {
|
||||||
})
|
})
|
||||||
chatListInteraction.isSearchMode = true
|
chatListInteraction.isSearchMode = true
|
||||||
|
|
||||||
@ -3822,7 +3837,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let transition = chatListSearchContainerPreparedRecentTransition(from: previousRecentItems?.entries ?? [], to: recentItems.entries, forceUpdateAll: forceUpdateAll, context: context, presentationData: presentationData, filter: peersFilter, key: key, peerSelected: { peer, threadId, isRecommended in
|
let transition = chatListSearchContainerPreparedRecentTransition(from: previousRecentItems?.entries ?? [], to: recentItems.entries, forceUpdateAll: forceUpdateAll, context: context, presentationData: presentationData, filter: peersFilter, key: key, peerSelected: { peer, threadId, isRecommended, action in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -3848,11 +3863,20 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}
|
}
|
||||||
} else if case .apps = key {
|
} else if case .apps = key {
|
||||||
if let navigationController = self.navigationController {
|
if let navigationController = self.navigationController {
|
||||||
if isRecommended {
|
switch action {
|
||||||
|
case .generic:
|
||||||
|
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
||||||
|
navigationController: navigationController,
|
||||||
|
context: self.context,
|
||||||
|
chatLocation: .peer(peer),
|
||||||
|
keepStack: .always
|
||||||
|
))
|
||||||
|
case .info:
|
||||||
if let peerInfoScreen = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
if let peerInfoScreen = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
|
||||||
navigationController.pushViewController(peerInfoScreen)
|
navigationController.pushViewController(peerInfoScreen)
|
||||||
}
|
}
|
||||||
} else if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.parentController {
|
case .openApp:
|
||||||
|
if let parentController = self.parentController {
|
||||||
self.context.sharedContext.openWebApp(
|
self.context.sharedContext.openWebApp(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
parentController: parentController,
|
parentController: parentController,
|
||||||
@ -3867,17 +3891,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
skipTermsOfService: true,
|
skipTermsOfService: true,
|
||||||
payload: nil
|
payload: nil
|
||||||
)
|
)
|
||||||
} else {
|
}
|
||||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(
|
|
||||||
navigationController: navigationController,
|
|
||||||
context: self.context,
|
|
||||||
chatLocation: .peer(peer),
|
|
||||||
keepStack: .always
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if isRecommended, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.parentController {
|
if case .openApp = action, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.parentController {
|
||||||
self.context.sharedContext.openWebApp(
|
self.context.sharedContext.openWebApp(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
parentController: parentController,
|
parentController: parentController,
|
||||||
@ -4581,6 +4599,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
strongSelf.ready.set(.single(true))
|
strongSelf.ready.set(.single(true))
|
||||||
}
|
}
|
||||||
strongSelf.didSetReady = true
|
strongSelf.didSetReady = true
|
||||||
|
if !strongSelf.recentListNode.preloadPages {
|
||||||
|
Queue.mainQueue().after(0.5) {
|
||||||
|
strongSelf.recentListNode.preloadPages = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.emptyRecentAnimationNode?.isHidden = !transition.isEmpty
|
strongSelf.emptyRecentAnimationNode?.isHidden = !transition.isEmpty
|
||||||
strongSelf.emptyRecentTitleNode?.isHidden = !transition.isEmpty
|
strongSelf.emptyRecentTitleNode?.isHidden = !transition.isEmpty
|
||||||
@ -4907,6 +4930,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
|
|||||||
}, dismissNotice: { _ in
|
}, dismissNotice: { _ in
|
||||||
}, editPeer: { _ in
|
}, editPeer: { _ in
|
||||||
}, openWebApp: { _ in
|
}, openWebApp: { _ in
|
||||||
|
}, openPhotoSetup: {
|
||||||
})
|
})
|
||||||
var isInlineMode = false
|
var isInlineMode = false
|
||||||
if case .topics = key {
|
if case .topics = key {
|
||||||
|
|||||||
@ -160,6 +160,7 @@ public final class ChatListShimmerNode: ASDisplayNode {
|
|||||||
}, dismissNotice: { _ in
|
}, dismissNotice: { _ in
|
||||||
}, editPeer: { _ in
|
}, editPeer: { _ in
|
||||||
}, openWebApp: { _ in
|
}, openWebApp: { _ in
|
||||||
|
}, openPhotoSetup: {
|
||||||
})
|
})
|
||||||
interaction.isInlineMode = isInlineMode
|
interaction.isInlineMode = isInlineMode
|
||||||
|
|
||||||
|
|||||||
@ -113,6 +113,7 @@ public final class ChatListNodeInteraction {
|
|||||||
let dismissNotice: (ChatListNotice) -> Void
|
let dismissNotice: (ChatListNotice) -> Void
|
||||||
let editPeer: (ChatListItem) -> Void
|
let editPeer: (ChatListItem) -> Void
|
||||||
let openWebApp: (TelegramUser) -> Void
|
let openWebApp: (TelegramUser) -> Void
|
||||||
|
let openPhotoSetup: () -> Void
|
||||||
|
|
||||||
public var searchTextHighightState: String?
|
public var searchTextHighightState: String?
|
||||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||||
@ -169,7 +170,8 @@ public final class ChatListNodeInteraction {
|
|||||||
openStarsTopup: @escaping (Int64?) -> Void,
|
openStarsTopup: @escaping (Int64?) -> Void,
|
||||||
dismissNotice: @escaping (ChatListNotice) -> Void,
|
dismissNotice: @escaping (ChatListNotice) -> Void,
|
||||||
editPeer: @escaping (ChatListItem) -> Void,
|
editPeer: @escaping (ChatListItem) -> Void,
|
||||||
openWebApp: @escaping (TelegramUser) -> Void
|
openWebApp: @escaping (TelegramUser) -> Void,
|
||||||
|
openPhotoSetup: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.activateSearch = activateSearch
|
self.activateSearch = activateSearch
|
||||||
self.peerSelected = peerSelected
|
self.peerSelected = peerSelected
|
||||||
@ -214,6 +216,7 @@ public final class ChatListNodeInteraction {
|
|||||||
self.dismissNotice = dismissNotice
|
self.dismissNotice = dismissNotice
|
||||||
self.editPeer = editPeer
|
self.editPeer = editPeer
|
||||||
self.openWebApp = openWebApp
|
self.openWebApp = openWebApp
|
||||||
|
self.openPhotoSetup = openPhotoSetup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,10 +522,13 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
} else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) {
|
||||||
|
canManage = true
|
||||||
|
} else if case let .channel(peer) = peer, case .broadcast = peer.info, peer.hasPermission(.addAdmins) {
|
||||||
|
canManage = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if canManage {
|
if canManage {
|
||||||
} else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) {
|
|
||||||
} else {
|
} else {
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
@ -759,6 +765,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
break
|
break
|
||||||
case let .starsSubscriptionLowBalance(amount, _):
|
case let .starsSubscriptionLowBalance(amount, _):
|
||||||
nodeInteraction?.openStarsTopup(amount.value)
|
nodeInteraction?.openStarsTopup(amount.value)
|
||||||
|
case .setupPhoto:
|
||||||
|
nodeInteraction?.openPhotoSetup()
|
||||||
}
|
}
|
||||||
case .hide:
|
case .hide:
|
||||||
nodeInteraction?.dismissNotice(notice)
|
nodeInteraction?.dismissNotice(notice)
|
||||||
@ -1103,6 +1111,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
break
|
break
|
||||||
case let .starsSubscriptionLowBalance(amount, _):
|
case let .starsSubscriptionLowBalance(amount, _):
|
||||||
nodeInteraction?.openStarsTopup(amount.value)
|
nodeInteraction?.openStarsTopup(amount.value)
|
||||||
|
case .setupPhoto:
|
||||||
|
nodeInteraction?.openPhotoSetup()
|
||||||
}
|
}
|
||||||
case .hide:
|
case .hide:
|
||||||
nodeInteraction?.dismissNotice(notice)
|
nodeInteraction?.dismissNotice(notice)
|
||||||
@ -1224,6 +1234,7 @@ public final class ChatListNode: ListView {
|
|||||||
public var openPremiumManagement: (() -> Void)?
|
public var openPremiumManagement: (() -> Void)?
|
||||||
public var openStarsTopup: ((Int64?) -> Void)?
|
public var openStarsTopup: ((Int64?) -> Void)?
|
||||||
public var openWebApp: ((TelegramUser) -> Void)?
|
public var openWebApp: ((TelegramUser) -> Void)?
|
||||||
|
public var openPhotoSetup: (() -> Void)?
|
||||||
|
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
|
|
||||||
@ -1876,6 +1887,11 @@ public final class ChatListNode: ListView {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.openWebApp?(user)
|
self.openWebApp?(user)
|
||||||
|
}, openPhotoSetup: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.openPhotoSetup?()
|
||||||
})
|
})
|
||||||
nodeInteraction.isInlineMode = isInlineMode
|
nodeInteraction.isInlineMode = isInlineMode
|
||||||
|
|
||||||
@ -1970,11 +1986,16 @@ public final class ChatListNode: ListView {
|
|||||||
context.engine.notices.getServerDismissedSuggestions(),
|
context.engine.notices.getServerDismissedSuggestions(),
|
||||||
twoStepData,
|
twoStepData,
|
||||||
newSessionReviews(postbox: context.account.postbox),
|
newSessionReviews(postbox: context.account.postbox),
|
||||||
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)),
|
context.engine.data.subscribe(
|
||||||
|
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
|
||||||
|
TelegramEngine.EngineData.Item.Peer.Birthday(id: context.account.peerId)
|
||||||
|
),
|
||||||
context.account.stateManager.contactBirthdays,
|
context.account.stateManager.contactBirthdays,
|
||||||
starsSubscriptionsContextPromise.get()
|
starsSubscriptionsContextPromise.get()
|
||||||
)
|
)
|
||||||
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, birthday, birthdays, starsSubscriptionsContext -> Signal<ChatListNotice?, NoError> in
|
|> mapToSignal { suggestions, dismissedSuggestions, configuration, newSessionReviews, data, birthdays, starsSubscriptionsContext -> Signal<ChatListNotice?, NoError> in
|
||||||
|
let (accountPeer, birthday) = data
|
||||||
|
|
||||||
if let newSessionReview = newSessionReviews.first {
|
if let newSessionReview = newSessionReviews.first {
|
||||||
return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count))
|
return .single(.reviewLogin(newSessionReview: newSessionReview, totalCount: newSessionReviews.count))
|
||||||
}
|
}
|
||||||
@ -2025,6 +2046,8 @@ public final class ChatListNode: ListView {
|
|||||||
starsSubscriptionsContextPromise.set(.single(context.engine.payments.peerStarsSubscriptionsContext(starsContext: nil, missingBalance: true)))
|
starsSubscriptionsContextPromise.set(.single(context.engine.payments.peerStarsSubscriptionsContext(starsContext: nil, missingBalance: true)))
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
|
} else if suggestions.contains(.setupPhoto), let accountPeer, accountPeer.smallProfileImage == nil {
|
||||||
|
return .single(.setupPhoto(accountPeer))
|
||||||
} else if suggestions.contains(.gracePremium) {
|
} else if suggestions.contains(.gracePremium) {
|
||||||
return .single(.premiumGrace)
|
return .single(.premiumGrace)
|
||||||
} else if suggestions.contains(.setupBirthday) && birthday == nil {
|
} else if suggestions.contains(.setupBirthday) && birthday == nil {
|
||||||
@ -2413,10 +2436,13 @@ public final class ChatListNode: ListView {
|
|||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
} else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) {
|
||||||
|
canManage = true
|
||||||
|
} else if case let .channel(peer) = peer, case .broadcast = peer.info, peer.hasPermission(.addAdmins) {
|
||||||
|
canManage = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if canManage {
|
if canManage {
|
||||||
} else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) {
|
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,6 +91,7 @@ public enum ChatListNotice: Equatable {
|
|||||||
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
|
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
|
||||||
case premiumGrace
|
case premiumGrace
|
||||||
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
|
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
|
||||||
|
case setupPhoto(EnginePeer)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||||
@ -605,6 +606,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
|
|||||||
|
|
||||||
var result: [ChatListNodeEntry] = []
|
var result: [ChatListNodeEntry] = []
|
||||||
|
|
||||||
|
var hasContacts = false
|
||||||
if !view.hasEarlier {
|
if !view.hasEarlier {
|
||||||
var existingPeerIds = Set<EnginePeer.Id>()
|
var existingPeerIds = Set<EnginePeer.Id>()
|
||||||
for item in view.items {
|
for item in view.items {
|
||||||
@ -620,8 +622,9 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
|
|||||||
peer: contact.peer,
|
peer: contact.peer,
|
||||||
presence: contact.presence
|
presence: contact.presence
|
||||||
)))
|
)))
|
||||||
|
hasContacts = true
|
||||||
}
|
}
|
||||||
if !contacts.isEmpty {
|
if hasContacts {
|
||||||
result.append(.SectionHeader(presentationData: state.presentationData, displayHide: !view.items.isEmpty))
|
result.append(.SectionHeader(presentationData: state.presentationData, displayHide: !view.items.isEmpty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import AccountContext
|
|||||||
import MergedAvatarsNode
|
import MergedAvatarsNode
|
||||||
import TextNodeWithEntities
|
import TextNodeWithEntities
|
||||||
import TextFormat
|
import TextFormat
|
||||||
|
import AvatarNode
|
||||||
|
|
||||||
class ChatListNoticeItem: ListViewItem {
|
class ChatListNoticeItem: ListViewItem {
|
||||||
enum Action {
|
enum Action {
|
||||||
@ -94,6 +95,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
|
|||||||
private let arrowNode: ASImageNode
|
private let arrowNode: ASImageNode
|
||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
|
|
||||||
|
private var avatarNode: AvatarNode?
|
||||||
private var avatarsNode: MergedAvatarsNode?
|
private var avatarsNode: MergedAvatarsNode?
|
||||||
|
|
||||||
private var closeButton: HighlightableButtonNode?
|
private var closeButton: HighlightableButtonNode?
|
||||||
@ -175,6 +177,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let titleString: NSAttributedString
|
let titleString: NSAttributedString
|
||||||
let textString: NSAttributedString
|
let textString: NSAttributedString
|
||||||
|
var avatarPeer: EnginePeer?
|
||||||
var avatarPeers: [EnginePeer] = []
|
var avatarPeers: [EnginePeer] = []
|
||||||
|
|
||||||
var okButtonLayout: (TextNodeLayout, () -> TextNode)?
|
var okButtonLayout: (TextNodeLayout, () -> TextNode)?
|
||||||
@ -285,12 +288,20 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
titleString = attributedTitle
|
titleString = attributedTitle
|
||||||
textString = NSAttributedString(string: text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
textString = NSAttributedString(string: text, font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||||
|
case let .setupPhoto(accountPeer):
|
||||||
|
//TODO:localize
|
||||||
|
titleString = NSAttributedString(string: "Add your photo! 📸", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)
|
||||||
|
textString = NSAttributedString(string: "Help your friends spot you easily.", font: smallTextFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||||
|
avatarPeer = accountPeer
|
||||||
}
|
}
|
||||||
|
|
||||||
var leftInset: CGFloat = sideInset
|
var leftInset: CGFloat = sideInset
|
||||||
if !avatarPeers.isEmpty {
|
if !avatarPeers.isEmpty {
|
||||||
let avatarsWidth = 30.0 + CGFloat(avatarPeers.count - 1) * 16.0
|
let avatarsWidth = 30.0 + CGFloat(avatarPeers.count - 1) * 16.0
|
||||||
leftInset += avatarsWidth + 4.0
|
leftInset += avatarsWidth + 4.0
|
||||||
|
} else if let _ = avatarPeer {
|
||||||
|
let avatarsWidth: CGFloat = 40.0
|
||||||
|
leftInset += avatarsWidth + 6.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - titleRightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18))
|
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - titleRightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18))
|
||||||
@ -349,6 +360,25 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
|
|||||||
strongSelf.avatarsNode = nil
|
strongSelf.avatarsNode = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let avatarPeer {
|
||||||
|
let avatarNode: AvatarNode
|
||||||
|
if let current = strongSelf.avatarNode {
|
||||||
|
avatarNode = current
|
||||||
|
} else {
|
||||||
|
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 13.0))
|
||||||
|
avatarNode.isUserInteractionEnabled = false
|
||||||
|
strongSelf.addSubnode(avatarNode)
|
||||||
|
strongSelf.avatarNode = avatarNode
|
||||||
|
|
||||||
|
avatarNode.setPeer(context: item.context, theme: item.theme, peer: avatarPeer, overrideImage: .cameraIcon)
|
||||||
|
}
|
||||||
|
let avatarSize = CGSize(width: 40.0, height: 40.0)
|
||||||
|
avatarNode.frame = CGRect(origin: CGPoint(x: sideInset - 6.0, y: floor((layout.size.height - avatarSize.height) / 2.0)), size: avatarSize)
|
||||||
|
} else if let avatarNode = strongSelf.avatarNode {
|
||||||
|
avatarNode.removeFromSupernode()
|
||||||
|
strongSelf.avatarNode = nil
|
||||||
|
}
|
||||||
|
|
||||||
if let image = strongSelf.arrowNode.image {
|
if let image = strongSelf.arrowNode.image {
|
||||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - image.size.width + 8.0, y: floor((layout.size.height - image.size.height) / 2.0)), size: image.size)
|
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - image.size.width + 8.0, y: floor((layout.size.height - image.size.height) / 2.0)), size: image.size)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1277,9 +1277,20 @@ public final class ChatPresentationInterfaceState: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func canBypassRestrictions(chatPresentationInterfaceState: ChatPresentationInterfaceState) -> Bool {
|
||||||
|
guard let boostsToUnrestrict = chatPresentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0 else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let appliedBoosts = chatPresentationInterfaceState.appliedBoosts, appliedBoosts >= boostsToUnrestrict {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
public func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bool {
|
public func canSendMessagesToChat(_ state: ChatPresentationInterfaceState) -> Bool {
|
||||||
if let peer = state.renderedPeer?.peer {
|
if let peer = state.renderedPeer?.peer {
|
||||||
if canSendMessagesToPeer(peer) {
|
let canBypassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: state)
|
||||||
|
if canSendMessagesToPeer(peer, ignoreDefault: canBypassRestrictions) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -68,6 +68,7 @@ public enum ContactsPeerItemPeerMode: Equatable {
|
|||||||
case generalSearch(isSavedMessages: Bool)
|
case generalSearch(isSavedMessages: Bool)
|
||||||
case peer
|
case peer
|
||||||
case memberList
|
case memberList
|
||||||
|
case app(isPopular: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ContactsPeerItemBadgeType {
|
public enum ContactsPeerItemBadgeType {
|
||||||
@ -722,7 +723,15 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||||
let titleBoldFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize)
|
let titleBoldFont = Font.medium(item.presentationData.fontSize.itemListBaseFontSize)
|
||||||
let statusFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 13.0 / 17.0))
|
|
||||||
|
let statusFontSize: CGFloat
|
||||||
|
if case .app = item.peerMode {
|
||||||
|
statusFontSize = 15.0
|
||||||
|
} else {
|
||||||
|
statusFontSize = 13.0
|
||||||
|
}
|
||||||
|
let statusFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * statusFontSize / 17.0))
|
||||||
|
|
||||||
let badgeFont = Font.regular(14.0)
|
let badgeFont = Font.regular(14.0)
|
||||||
let avatarDiameter = min(40.0, floor(item.presentationData.fontSize.itemListBaseFontSize * 40.0 / 17.0))
|
let avatarDiameter = min(40.0, floor(item.presentationData.fontSize.itemListBaseFontSize * 40.0 / 17.0))
|
||||||
|
|
||||||
@ -1039,8 +1048,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleVerticalInset: CGFloat = statusAttributedString == nil ? 13.0 : 6.0
|
var verticalInset: CGFloat = statusAttributedString == nil ? 13.0 : 6.0
|
||||||
let verticalInset: CGFloat = statusAttributedString == nil ? 13.0 : 6.0
|
if case .app = item.peerMode {
|
||||||
|
verticalInset += 2.0
|
||||||
|
}
|
||||||
|
|
||||||
let statusHeightComponent: CGFloat
|
let statusHeightComponent: CGFloat
|
||||||
if statusAttributedString == nil {
|
if statusAttributedString == nil {
|
||||||
@ -1058,7 +1069,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
let titleFrame: CGRect
|
let titleFrame: CGRect
|
||||||
if statusAttributedString != nil {
|
if statusAttributedString != nil {
|
||||||
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: titleVerticalInset), size: titleLayout.size)
|
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: verticalInset), size: titleLayout.size)
|
||||||
} else {
|
} else {
|
||||||
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((nodeLayout.contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((nodeLayout.contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
||||||
}
|
}
|
||||||
@ -1136,14 +1147,19 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
} else if peer.isDeleted {
|
} else if peer.isDeleted {
|
||||||
overrideImage = .deletedIcon
|
overrideImage = .deletedIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var displayDimensions = CGSize(width: 60.0, height: 60.0)
|
||||||
let clipStyle: AvatarNodeClipStyle
|
let clipStyle: AvatarNodeClipStyle
|
||||||
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
if case .app(true) = item.peerMode {
|
||||||
|
clipStyle = .roundedRect
|
||||||
|
displayDimensions = CGSize(width: displayDimensions.width, height: displayDimensions.width * 1.2)
|
||||||
|
} else if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
||||||
clipStyle = .roundedRect
|
clipStyle = .roundedRect
|
||||||
} else {
|
} else {
|
||||||
clipStyle = .round
|
clipStyle = .round
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: clipStyle, synchronousLoad: synchronousLoads)
|
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, clipStyle: clipStyle, synchronousLoad: synchronousLoads, displayDimensions: displayDimensions)
|
||||||
}
|
}
|
||||||
case let .deviceContact(_, contact):
|
case let .deviceContact(_, contact):
|
||||||
let letters: [String]
|
let letters: [String]
|
||||||
@ -1230,7 +1246,14 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let avatarFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset - 50.0, y: floor((nodeLayout.contentSize.height - avatarDiameter) / 2.0)), size: CGSize(width: avatarDiameter, height: avatarDiameter))
|
var avatarSize: CGSize
|
||||||
|
if case .app(true) = item.peerMode {
|
||||||
|
avatarSize = CGSize(width: avatarDiameter, height: avatarDiameter * 1.2)
|
||||||
|
} else {
|
||||||
|
avatarSize = CGSize(width: avatarDiameter, height: avatarDiameter)
|
||||||
|
}
|
||||||
|
|
||||||
|
let avatarFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset - 50.0, y: floor((nodeLayout.contentSize.height - avatarSize.height) / 2.0)), size: avatarSize)
|
||||||
|
|
||||||
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(), size: avatarFrame.size)
|
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(), size: avatarFrame.size)
|
||||||
|
|
||||||
|
|||||||
@ -97,7 +97,11 @@ final class NavigationTransitionCoordinator {
|
|||||||
case .Push:
|
case .Push:
|
||||||
self.container.addSubnode(topNode)
|
self.container.addSubnode(topNode)
|
||||||
case .Pop:
|
case .Pop:
|
||||||
|
if topNode.supernode == self.container {
|
||||||
self.container.insertSubnode(bottomNode, belowSubnode: topNode)
|
self.container.insertSubnode(bottomNode, belowSubnode: topNode)
|
||||||
|
} else {
|
||||||
|
self.container.addSubnode(topNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.isFlat {
|
if !self.isFlat {
|
||||||
|
|||||||
@ -477,6 +477,10 @@ public struct MessageFlags: OptionSet {
|
|||||||
rawValue |= MessageFlags.IsForumTopic.rawValue
|
rawValue |= MessageFlags.IsForumTopic.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.contains(StoreMessageFlags.ReactionsArePossible) {
|
||||||
|
rawValue |= MessageFlags.ReactionsArePossible.rawValue
|
||||||
|
}
|
||||||
|
|
||||||
self.rawValue = rawValue
|
self.rawValue = rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,6 +494,7 @@ public struct MessageFlags: OptionSet {
|
|||||||
public static let CountedAsIncoming = MessageFlags(rawValue: 256)
|
public static let CountedAsIncoming = MessageFlags(rawValue: 256)
|
||||||
public static let CopyProtected = MessageFlags(rawValue: 512)
|
public static let CopyProtected = MessageFlags(rawValue: 512)
|
||||||
public static let IsForumTopic = MessageFlags(rawValue: 1024)
|
public static let IsForumTopic = MessageFlags(rawValue: 1024)
|
||||||
|
public static let ReactionsArePossible = MessageFlags(rawValue: 2048)
|
||||||
|
|
||||||
public static let IsIncomingMask = MessageFlags([.Incoming, .CountedAsIncoming])
|
public static let IsIncomingMask = MessageFlags([.Incoming, .CountedAsIncoming])
|
||||||
}
|
}
|
||||||
@ -843,6 +848,10 @@ public struct StoreMessageFlags: OptionSet {
|
|||||||
rawValue |= StoreMessageFlags.IsForumTopic.rawValue
|
rawValue |= StoreMessageFlags.IsForumTopic.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if flags.contains(.ReactionsArePossible) {
|
||||||
|
rawValue |= StoreMessageFlags.ReactionsArePossible.rawValue
|
||||||
|
}
|
||||||
|
|
||||||
self.rawValue = rawValue
|
self.rawValue = rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -856,6 +865,7 @@ public struct StoreMessageFlags: OptionSet {
|
|||||||
public static let CountedAsIncoming = StoreMessageFlags(rawValue: 256)
|
public static let CountedAsIncoming = StoreMessageFlags(rawValue: 256)
|
||||||
public static let CopyProtected = StoreMessageFlags(rawValue: 512)
|
public static let CopyProtected = StoreMessageFlags(rawValue: 512)
|
||||||
public static let IsForumTopic = StoreMessageFlags(rawValue: 1024)
|
public static let IsForumTopic = StoreMessageFlags(rawValue: 1024)
|
||||||
|
public static let ReactionsArePossible = StoreMessageFlags(rawValue: 2048)
|
||||||
|
|
||||||
public static let IsIncomingMask = StoreMessageFlags([.Incoming, .CountedAsIncoming])
|
public static let IsIncomingMask = StoreMessageFlags([.Incoming, .CountedAsIncoming])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -231,6 +231,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
|
|||||||
}, dismissNotice: { _ in
|
}, dismissNotice: { _ in
|
||||||
}, editPeer: { _ in
|
}, editPeer: { _ in
|
||||||
}, openWebApp: { _ in
|
}, openWebApp: { _ in
|
||||||
|
}, openPhotoSetup: {
|
||||||
})
|
})
|
||||||
|
|
||||||
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||||
|
|||||||
@ -380,6 +380,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}, dismissNotice: { _ in
|
}, dismissNotice: { _ in
|
||||||
}, editPeer: { _ in
|
}, editPeer: { _ in
|
||||||
}, openWebApp: { _ in
|
}, openWebApp: { _ in
|
||||||
|
}, openPhotoSetup: {
|
||||||
})
|
})
|
||||||
|
|
||||||
func makeChatListItem(
|
func makeChatListItem(
|
||||||
|
|||||||
@ -537,7 +537,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) }
|
dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) }
|
||||||
dict[-1808510398] = { return Api.Message.parse_message($0) }
|
dict[-1808510398] = { return Api.Message.parse_message($0) }
|
||||||
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
|
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
|
||||||
dict[721967202] = { return Api.Message.parse_messageService($0) }
|
dict[-741178048] = { return Api.Message.parse_messageService($0) }
|
||||||
dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) }
|
dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) }
|
||||||
dict[-988359047] = { return Api.MessageAction.parse_messageActionBotAllowed($0) }
|
dict[-988359047] = { return Api.MessageAction.parse_messageActionBotAllowed($0) }
|
||||||
dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) }
|
dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) }
|
||||||
|
|||||||
@ -62,7 +62,7 @@ public extension Api {
|
|||||||
indirect enum Message: TypeConstructorDescription {
|
indirect enum Message: TypeConstructorDescription {
|
||||||
case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?)
|
case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?)
|
||||||
case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?)
|
case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?)
|
||||||
case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, ttlPeriod: Int32?)
|
case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?)
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
@ -115,9 +115,9 @@ public extension Api {
|
|||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
if Int(flags) & Int(1 << 0) != 0 {peerId!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 0) != 0 {peerId!.serialize(buffer, true)}
|
||||||
break
|
break
|
||||||
case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let ttlPeriod):
|
case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let reactions, let ttlPeriod):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(721967202)
|
buffer.appendInt32(-741178048)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeInt32(id, buffer: buffer, boxed: false)
|
serializeInt32(id, buffer: buffer, boxed: false)
|
||||||
@ -126,6 +126,7 @@ public extension Api {
|
|||||||
if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)}
|
if Int(flags) & Int(1 << 3) != 0 {replyTo!.serialize(buffer, true)}
|
||||||
serializeInt32(date, buffer: buffer, boxed: false)
|
serializeInt32(date, buffer: buffer, boxed: false)
|
||||||
action.serialize(buffer, true)
|
action.serialize(buffer, true)
|
||||||
|
if Int(flags) & Int(1 << 20) != 0 {reactions!.serialize(buffer, true)}
|
||||||
if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)}
|
if Int(flags) & Int(1 << 25) != 0 {serializeInt32(ttlPeriod!, buffer: buffer, boxed: false)}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -137,8 +138,8 @@ public extension Api {
|
|||||||
return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any)])
|
return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any)])
|
||||||
case .messageEmpty(let flags, let id, let peerId):
|
case .messageEmpty(let flags, let id, let peerId):
|
||||||
return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)])
|
return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)])
|
||||||
case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let ttlPeriod):
|
case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let reactions, let ttlPeriod):
|
||||||
return ("messageService", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("peerId", peerId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("action", action as Any), ("ttlPeriod", ttlPeriod as Any)])
|
return ("messageService", [("flags", flags as Any), ("id", id as Any), ("fromId", fromId as Any), ("peerId", peerId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("action", action as Any), ("reactions", reactions as Any), ("ttlPeriod", ttlPeriod as Any)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,8 +301,12 @@ public extension Api {
|
|||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
_7 = Api.parse(reader, signature: signature) as? Api.MessageAction
|
_7 = Api.parse(reader, signature: signature) as? Api.MessageAction
|
||||||
}
|
}
|
||||||
var _8: Int32?
|
var _8: Api.MessageReactions?
|
||||||
if Int(_1!) & Int(1 << 25) != 0 {_8 = reader.readInt32() }
|
if Int(_1!) & Int(1 << 20) != 0 {if let signature = reader.readInt32() {
|
||||||
|
_8 = Api.parse(reader, signature: signature) as? Api.MessageReactions
|
||||||
|
} }
|
||||||
|
var _9: Int32?
|
||||||
|
if Int(_1!) & Int(1 << 25) != 0 {_9 = reader.readInt32() }
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil
|
let _c3 = (Int(_1!) & Int(1 << 8) == 0) || _3 != nil
|
||||||
@ -309,9 +314,10 @@ public extension Api {
|
|||||||
let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
|
let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil
|
||||||
let _c6 = _6 != nil
|
let _c6 = _6 != nil
|
||||||
let _c7 = _7 != nil
|
let _c7 = _7 != nil
|
||||||
let _c8 = (Int(_1!) & Int(1 << 25) == 0) || _8 != nil
|
let _c8 = (Int(_1!) & Int(1 << 20) == 0) || _8 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
let _c9 = (Int(_1!) & Int(1 << 25) == 0) || _9 != nil
|
||||||
return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, replyTo: _5, date: _6!, action: _7!, ttlPeriod: _8)
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||||
|
return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, replyTo: _5, date: _6!, action: _7!, reactions: _8, ttlPeriod: _9)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -135,7 +135,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
|
|||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case let .messageService(_, _, _, chatPeerId, _, _, _, _):
|
case let .messageService(_, _, _, chatPeerId, _, _, _, _, _):
|
||||||
return chatPeerId.peerId
|
return chatPeerId.peerId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,7 +216,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
|
|||||||
return result
|
return result
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
return []
|
return []
|
||||||
case let .messageService(_, _, fromId, chatPeerId, _, _, action, _):
|
case let .messageService(_, _, fromId, chatPeerId, _, _, action, _, _):
|
||||||
let peerId: PeerId = chatPeerId.peerId
|
let peerId: PeerId = chatPeerId.peerId
|
||||||
var result = [peerId]
|
var result = [peerId]
|
||||||
|
|
||||||
@ -295,7 +295,7 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere
|
|||||||
}
|
}
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
break
|
break
|
||||||
case let .messageService(_, id, _, chatPeerId, replyHeader, _, _, _):
|
case let .messageService(_, id, _, chatPeerId, replyHeader, _, _, _, _):
|
||||||
if let replyHeader = replyHeader {
|
if let replyHeader = replyHeader {
|
||||||
switch replyHeader {
|
switch replyHeader {
|
||||||
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities, quoteOffset):
|
case let .messageReplyHeader(_, replyToMsgId, replyToPeerId, replyHeader, replyMedia, replyToTopId, quoteText, quoteEntities, quoteOffset):
|
||||||
@ -1015,7 +1015,7 @@ extension StoreMessage {
|
|||||||
self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias)
|
self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: groupingId, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: forwardInfo, authorId: authorId, text: messageText, attributes: attributes, media: medias)
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
return nil
|
return nil
|
||||||
case let .messageService(flags, id, fromId, chatPeerId, replyTo, date, action, ttlPeriod):
|
case let .messageService(flags, id, fromId, chatPeerId, replyTo, date, action, reactions, ttlPeriod):
|
||||||
let peerId: PeerId = chatPeerId.peerId
|
let peerId: PeerId = chatPeerId.peerId
|
||||||
let authorId: PeerId? = fromId?.peerId ?? chatPeerId.peerId
|
let authorId: PeerId? = fromId?.peerId ?? chatPeerId.peerId
|
||||||
|
|
||||||
@ -1075,6 +1075,10 @@ extension StoreMessage {
|
|||||||
attributes.append(ContentRequiresValidationMessageAttribute())
|
attributes.append(ContentRequiresValidationMessageAttribute())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let reactions = reactions {
|
||||||
|
attributes.append(ReactionsMessageAttribute(apiReactions: reactions))
|
||||||
|
}
|
||||||
|
|
||||||
var storeFlags = StoreMessageFlags()
|
var storeFlags = StoreMessageFlags()
|
||||||
if (flags & 2) == 0 {
|
if (flags & 2) == 0 {
|
||||||
let _ = storeFlags.insert(.Incoming)
|
let _ = storeFlags.insert(.Incoming)
|
||||||
@ -1121,6 +1125,10 @@ extension StoreMessage {
|
|||||||
storeFlags.insert(.IsForumTopic)
|
storeFlags.insert(.IsForumTopic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flags & (1 << 9)) != 0 {
|
||||||
|
storeFlags.insert(.ReactionsArePossible)
|
||||||
|
}
|
||||||
|
|
||||||
self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: attributes, media: media)
|
self.init(id: MessageId(peerId: peerId, namespace: namespace, id: id), globallyUniqueId: nil, groupingKey: nil, threadId: threadId, timestamp: date, flags: storeFlags, tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: attributes, media: media)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ public enum TelegramChannelPermission {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public extension TelegramChannel {
|
public extension TelegramChannel {
|
||||||
func hasPermission(_ permission: TelegramChannelPermission) -> Bool {
|
func hasPermission(_ permission: TelegramChannelPermission, ignoreDefault: Bool = false) -> Bool {
|
||||||
if self.flags.contains(.isCreator) {
|
if self.flags.contains(.isCreator) {
|
||||||
if case .canBeAnonymous = permission {
|
if case .canBeAnonymous = permission {
|
||||||
if let adminRights = self.adminRights {
|
if let adminRights = self.adminRights {
|
||||||
@ -50,7 +50,7 @@ public extension TelegramChannel {
|
|||||||
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendText) {
|
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendText) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendText) {
|
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendText) && !ignoreDefault {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -69,7 +69,7 @@ public extension TelegramChannel {
|
|||||||
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendPhotos) {
|
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendPhotos) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendPhotos) {
|
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendPhotos) && !ignoreDefault {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -88,7 +88,7 @@ public extension TelegramChannel {
|
|||||||
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendVideos) {
|
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendVideos) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendVideos) {
|
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendVideos) && !ignoreDefault {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@ -121,7 +121,7 @@ public extension TelegramChannel {
|
|||||||
if let bannedRights = self.bannedRights, bannedRights.flags.intersection(flags) == flags {
|
if let bannedRights = self.bannedRights, bannedRights.flags.intersection(flags) == flags {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.intersection(flags) == flags {
|
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.intersection(flags) == flags && !ignoreDefault {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|||||||
@ -108,7 +108,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
|||||||
updatedTimestamp = date
|
updatedTimestamp = date
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
break
|
break
|
||||||
case let .messageService(_, _, _, _, _, date, _, _):
|
case let .messageService(_, _, _, _, _, date, _, _, _):
|
||||||
updatedTimestamp = date
|
updatedTimestamp = date
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
|||||||
|
|
||||||
public class Serialization: NSObject, MTSerialization {
|
public class Serialization: NSObject, MTSerialization {
|
||||||
public func currentLayer() -> UInt {
|
public func currentLayer() -> UInt {
|
||||||
return 195
|
return 196
|
||||||
}
|
}
|
||||||
|
|
||||||
public func parseMessage(_ data: Data!) -> Any! {
|
public func parseMessage(_ data: Data!) -> Any! {
|
||||||
|
|||||||
@ -108,7 +108,7 @@ extension Api.Message {
|
|||||||
return id
|
return id
|
||||||
case let .messageEmpty(_, id, _):
|
case let .messageEmpty(_, id, _):
|
||||||
return id
|
return id
|
||||||
case let .messageService(_, id, _, _, _, _, _, _):
|
case let .messageService(_, id, _, _, _, _, _, _, _):
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,7 +128,7 @@ extension Api.Message {
|
|||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case let .messageService(_, id, _, chatPeerId, _, _, _, _):
|
case let .messageService(_, id, _, chatPeerId, _, _, _, _, _):
|
||||||
let peerId: PeerId = chatPeerId.peerId
|
let peerId: PeerId = chatPeerId.peerId
|
||||||
return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)
|
return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ extension Api.Message {
|
|||||||
return peerId
|
return peerId
|
||||||
case let .messageEmpty(_, _, peerId):
|
case let .messageEmpty(_, _, peerId):
|
||||||
return peerId?.peerId
|
return peerId?.peerId
|
||||||
case let .messageService(_, _, _, chatPeerId, _, _, _, _):
|
case let .messageService(_, _, _, chatPeerId, _, _, _, _, _):
|
||||||
let peerId: PeerId = chatPeerId.peerId
|
let peerId: PeerId = chatPeerId.peerId
|
||||||
return peerId
|
return peerId
|
||||||
}
|
}
|
||||||
@ -151,7 +151,7 @@ extension Api.Message {
|
|||||||
switch self {
|
switch self {
|
||||||
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||||
return date
|
return date
|
||||||
case let .messageService(_, _, _, _, _, date, _, _):
|
case let .messageService(_, _, _, _, _, date, _, _, _):
|
||||||
return date
|
return date
|
||||||
case .messageEmpty:
|
case .messageEmpty:
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -17,6 +17,7 @@ public enum ServerProvidedSuggestion: String {
|
|||||||
case todayBirthdays = "BIRTHDAY_CONTACTS_TODAY"
|
case todayBirthdays = "BIRTHDAY_CONTACTS_TODAY"
|
||||||
case gracePremium = "PREMIUM_GRACE"
|
case gracePremium = "PREMIUM_GRACE"
|
||||||
case starsSubscriptionLowBalance = "STARS_SUBSCRIPTION_LOW_BALANCE"
|
case starsSubscriptionLowBalance = "STARS_SUBSCRIPTION_LOW_BALANCE"
|
||||||
|
case setupPhoto = "USERPIC_SETUP"
|
||||||
}
|
}
|
||||||
|
|
||||||
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])
|
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])
|
||||||
@ -40,7 +41,6 @@ func _internal_getServerProvidedSuggestions(account: Account) -> Signal<[ServerP
|
|||||||
guard let data = appConfiguration.data, let listItems = data["pending_suggestions"] as? [String] else {
|
guard let data = appConfiguration.data, let listItems = data["pending_suggestions"] as? [String] else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return listItems.compactMap { item -> ServerProvidedSuggestion? in
|
return listItems.compactMap { item -> ServerProvidedSuggestion? in
|
||||||
return ServerProvidedSuggestion(rawValue: item)
|
return ServerProvidedSuggestion(rawValue: item)
|
||||||
}.filter { !dismissedSuggestions.contains($0) }
|
}.filter { !dismissedSuggestions.contains($0) }
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import Postbox
|
|||||||
private final class LinkHelperClass: NSObject {
|
private final class LinkHelperClass: NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func canSendMessagesToPeer(_ peer: Peer) -> Bool {
|
public func canSendMessagesToPeer(_ peer: Peer, ignoreDefault: Bool = false) -> Bool {
|
||||||
if let peer = peer as? TelegramUser, peer.addressName == "replies" {
|
if let peer = peer as? TelegramUser, peer.addressName == "replies" {
|
||||||
return false
|
return false
|
||||||
} else if peer is TelegramUser || peer is TelegramGroup {
|
} else if peer is TelegramUser || peer is TelegramGroup {
|
||||||
@ -14,7 +14,7 @@ public func canSendMessagesToPeer(_ peer: Peer) -> Bool {
|
|||||||
} else if let peer = peer as? TelegramSecretChat {
|
} else if let peer = peer as? TelegramSecretChat {
|
||||||
return peer.embeddedState == .active
|
return peer.embeddedState == .active
|
||||||
} else if let peer = peer as? TelegramChannel {
|
} else if let peer = peer as? TelegramChannel {
|
||||||
return peer.hasPermission(.sendSomething)
|
return peer.hasPermission(.sendSomething, ignoreDefault: ignoreDefault)
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -671,6 +671,8 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
|||||||
editPeer: { _ in
|
editPeer: { _ in
|
||||||
},
|
},
|
||||||
openWebApp: { _ in
|
openWebApp: { _ in
|
||||||
|
},
|
||||||
|
openPhotoSetup: {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.chatListNodeInteraction = chatListNodeInteraction
|
self.chatListNodeInteraction = chatListNodeInteraction
|
||||||
|
|||||||
@ -230,7 +230,8 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
var backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0)
|
var backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0)
|
||||||
if let _ = image {
|
if let _ = image {
|
||||||
backgroundSize.height += imageSize.height + 10
|
backgroundSize.width = imageSize.width + 2.0
|
||||||
|
backgroundSize.height += imageSize.height + 10.0
|
||||||
}
|
}
|
||||||
return (backgroundSize.width, { boundingWidth in
|
return (backgroundSize.width, { boundingWidth in
|
||||||
return (backgroundSize, { [weak self] animation, synchronousLoads, _ in
|
return (backgroundSize, { [weak self] animation, synchronousLoads, _ in
|
||||||
@ -239,7 +240,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
|
|
||||||
let maskPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: imageSize), cornerRadius: 15.5)
|
let maskPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: imageSize), cornerRadius: 15.5)
|
||||||
|
|
||||||
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 10 + 2), size: imageSize)
|
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 12.0), size: imageSize)
|
||||||
if let image = image {
|
if let image = image {
|
||||||
let imageNode: TransformImageNode
|
let imageNode: TransformImageNode
|
||||||
if let current = strongSelf.imageNode {
|
if let current = strongSelf.imageNode {
|
||||||
@ -319,7 +320,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
attemptSynchronous: synchronousLoads
|
attemptSynchronous: synchronousLoads
|
||||||
))
|
))
|
||||||
|
|
||||||
let labelFrame = CGRect(origin: CGPoint(x: 8.0, y: image != nil ? 2 : floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size)
|
let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - labelLayout.size.width) / 2.0) - 1.0, y: image != nil ? 2.0 : floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size)
|
||||||
|
|
||||||
if story != nil {
|
if story != nil {
|
||||||
let leadingIconView: UIImageView
|
let leadingIconView: UIImageView
|
||||||
|
|||||||
@ -1301,7 +1301,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
associatedData: item.associatedData,
|
associatedData: item.associatedData,
|
||||||
accountPeer: item.associatedData.accountPeer,
|
accountPeer: item.associatedData.accountPeer,
|
||||||
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
|
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
|
||||||
constrainedWidth: maxReactionsWidth
|
constrainedWidth: maxReactionsWidth,
|
||||||
|
centerAligned: false
|
||||||
))
|
))
|
||||||
maxContentWidth = max(maxContentWidth, minWidth)
|
maxContentWidth = max(maxContentWidth, minWidth)
|
||||||
reactionButtonsFinalize = buttonsLayout
|
reactionButtonsFinalize = buttonsLayout
|
||||||
|
|||||||
@ -208,6 +208,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||||||
isAction = true
|
isAction = true
|
||||||
if case .phoneCall = action.action {
|
if case .phoneCall = action.action {
|
||||||
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
|
needReactions = false
|
||||||
} else if case .giftPremium = action.action {
|
} else if case .giftPremium = action.action {
|
||||||
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
} else if case .giftStars = action.action {
|
} else if case .giftStars = action.action {
|
||||||
@ -225,10 +226,16 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
|||||||
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
} else if case .joinedChannel = action.action {
|
} else if case .joinedChannel = action.action {
|
||||||
result.append((message, ChatMessageJoinedChannelBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessageJoinedChannelBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
|
needReactions = false
|
||||||
} else {
|
} else {
|
||||||
|
switch action.action {
|
||||||
|
case .photoUpdated:
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
needReactions = false
|
||||||
|
}
|
||||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||||
}
|
}
|
||||||
needReactions = false
|
|
||||||
} else if let _ = media as? TelegramMediaMap {
|
} else if let _ = media as? TelegramMediaMap {
|
||||||
result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
||||||
} else if let _ = media as? TelegramMediaGame {
|
} else if let _ = media as? TelegramMediaGame {
|
||||||
@ -2700,6 +2707,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
|
|
||||||
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
|
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
|
||||||
if !bubbleReactions.reactions.isEmpty && !item.presentationData.isPreview {
|
if !bubbleReactions.reactions.isEmpty && !item.presentationData.isPreview {
|
||||||
|
var centerAligned = false
|
||||||
|
for media in item.message.media {
|
||||||
|
if media is TelegramMediaAction {
|
||||||
|
centerAligned = true
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
var maximumNodeWidth = maximumNodeWidth
|
var maximumNodeWidth = maximumNodeWidth
|
||||||
if hasInstantVideo {
|
if hasInstantVideo {
|
||||||
maximumNodeWidth = min(309, baseWidth - 84)
|
maximumNodeWidth = min(309, baseWidth - 84)
|
||||||
@ -2715,7 +2730,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
associatedData: item.associatedData,
|
associatedData: item.associatedData,
|
||||||
accountPeer: item.associatedData.accountPeer,
|
accountPeer: item.associatedData.accountPeer,
|
||||||
isIncoming: incoming,
|
isIncoming: incoming,
|
||||||
constrainedWidth: maximumNodeWidth
|
constrainedWidth: maximumNodeWidth,
|
||||||
|
centerAligned: centerAligned
|
||||||
))
|
))
|
||||||
maxContentWidth = max(maxContentWidth, minWidth)
|
maxContentWidth = max(maxContentWidth, minWidth)
|
||||||
reactionButtonsFinalize = buttonsLayout
|
reactionButtonsFinalize = buttonsLayout
|
||||||
|
|||||||
@ -616,7 +616,8 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco
|
|||||||
associatedData: item.associatedData,
|
associatedData: item.associatedData,
|
||||||
accountPeer: item.associatedData.accountPeer,
|
accountPeer: item.associatedData.accountPeer,
|
||||||
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
|
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
|
||||||
constrainedWidth: maxReactionsWidth
|
constrainedWidth: maxReactionsWidth,
|
||||||
|
centerAligned: false
|
||||||
))
|
))
|
||||||
maxContentWidth = max(maxContentWidth, minWidth)
|
maxContentWidth = max(maxContentWidth, minWidth)
|
||||||
reactionButtonsFinalize = buttonsLayout
|
reactionButtonsFinalize = buttonsLayout
|
||||||
|
|||||||
@ -285,7 +285,7 @@ public func canAddMessageReactions(message: Message) -> Bool {
|
|||||||
}
|
}
|
||||||
for media in message.media {
|
for media in message.media {
|
||||||
if let _ = media as? TelegramMediaAction {
|
if let _ = media as? TelegramMediaAction {
|
||||||
return false
|
return message.flags.contains(.ReactionsArePossible)
|
||||||
} else if let story = media as? TelegramMediaStory {
|
} else if let story = media as? TelegramMediaStory {
|
||||||
if story.isMention {
|
if story.isMention {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -30,6 +30,7 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
|||||||
public enum DisplayAlignment {
|
public enum DisplayAlignment {
|
||||||
case left
|
case left
|
||||||
case right
|
case right
|
||||||
|
case center
|
||||||
}
|
}
|
||||||
|
|
||||||
private var bubbleBackgroundNode: WallpaperBubbleBackgroundNode?
|
private var bubbleBackgroundNode: WallpaperBubbleBackgroundNode?
|
||||||
@ -224,27 +225,29 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
|||||||
constrainedWidth: constrainedWidth
|
constrainedWidth: constrainedWidth
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let itemSpacing: CGFloat = 6.0
|
||||||
|
|
||||||
var reactionButtonsSize = CGSize()
|
var reactionButtonsSize = CGSize()
|
||||||
var currentRowWidth: CGFloat = 0.0
|
var currentRowWidth: CGFloat = 0.0
|
||||||
for item in reactionButtonsResult.items {
|
for item in reactionButtonsResult.items {
|
||||||
if currentRowWidth + item.size.width > constrainedWidth {
|
if currentRowWidth + item.size.width > constrainedWidth {
|
||||||
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
|
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
|
||||||
if !reactionButtonsSize.height.isZero {
|
if !reactionButtonsSize.height.isZero {
|
||||||
reactionButtonsSize.height += 6.0
|
reactionButtonsSize.height += itemSpacing
|
||||||
}
|
}
|
||||||
reactionButtonsSize.height += item.size.height
|
reactionButtonsSize.height += item.size.height
|
||||||
currentRowWidth = 0.0
|
currentRowWidth = 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
if !currentRowWidth.isZero {
|
if !currentRowWidth.isZero {
|
||||||
currentRowWidth += 6.0
|
currentRowWidth += itemSpacing
|
||||||
}
|
}
|
||||||
currentRowWidth += item.size.width
|
currentRowWidth += item.size.width
|
||||||
}
|
}
|
||||||
if !currentRowWidth.isZero && !reactionButtonsResult.items.isEmpty {
|
if !currentRowWidth.isZero && !reactionButtonsResult.items.isEmpty {
|
||||||
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
|
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
|
||||||
if !reactionButtonsSize.height.isZero {
|
if !reactionButtonsSize.height.isZero {
|
||||||
reactionButtonsSize.height += 6.0
|
reactionButtonsSize.height += itemSpacing
|
||||||
}
|
}
|
||||||
reactionButtonsSize.height += reactionButtonsResult.items[0].size.height
|
reactionButtonsSize.height += reactionButtonsResult.items[0].size.height
|
||||||
}
|
}
|
||||||
@ -296,11 +299,14 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var reactionButtonPosition: CGPoint
|
var reactionButtonPosition: CGPoint
|
||||||
|
|
||||||
switch alignment {
|
switch alignment {
|
||||||
case .left:
|
case .left:
|
||||||
reactionButtonPosition = CGPoint(x: -1.0, y: topInset)
|
reactionButtonPosition = CGPoint(x: -1.0, y: topInset)
|
||||||
case .right:
|
case .right:
|
||||||
reactionButtonPosition = CGPoint(x: size.width + 1.0, y: topInset)
|
reactionButtonPosition = CGPoint(x: size.width + 1.0, y: topInset)
|
||||||
|
case .center:
|
||||||
|
reactionButtonPosition = CGPoint(x: 0.0, y: topInset)
|
||||||
}
|
}
|
||||||
|
|
||||||
let reactionButtons = reactionButtonsResult.apply(
|
let reactionButtons = reactionButtonsResult.apply(
|
||||||
@ -312,32 +318,8 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
|||||||
)
|
)
|
||||||
|
|
||||||
var validIds = Set<MessageReaction.Reaction>()
|
var validIds = Set<MessageReaction.Reaction>()
|
||||||
for item in reactionButtons.items {
|
|
||||||
validIds.insert(item.value)
|
|
||||||
|
|
||||||
switch alignment {
|
|
||||||
case .left:
|
|
||||||
if reactionButtonPosition.x + item.size.width > boundingWidth {
|
|
||||||
reactionButtonPosition.x = -1.0
|
|
||||||
reactionButtonPosition.y += item.size.height + 6.0
|
|
||||||
}
|
|
||||||
case .right:
|
|
||||||
if reactionButtonPosition.x - item.size.width < -1.0 {
|
|
||||||
reactionButtonPosition.x = size.width + 1.0
|
|
||||||
reactionButtonPosition.y += item.size.height + 6.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let itemFrame: CGRect
|
|
||||||
switch alignment {
|
|
||||||
case .left:
|
|
||||||
itemFrame = CGRect(origin: reactionButtonPosition, size: item.size)
|
|
||||||
reactionButtonPosition.x += item.size.width + 6.0
|
|
||||||
case .right:
|
|
||||||
itemFrame = CGRect(origin: CGPoint(x: reactionButtonPosition.x - item.size.width, y: reactionButtonPosition.y), size: item.size)
|
|
||||||
reactionButtonPosition.x -= item.size.width + 6.0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let layoutItem: (ReactionButtonsAsyncLayoutContainer.ApplyResult.Item, CGRect) -> Void = { item, itemFrame in
|
||||||
let itemMaskFrame = itemFrame.offsetBy(dx: backgroundInsets, dy: backgroundInsets)
|
let itemMaskFrame = itemFrame.offsetBy(dx: backgroundInsets, dy: backgroundInsets)
|
||||||
|
|
||||||
let itemMaskView: UIView
|
let itemMaskView: UIView
|
||||||
@ -397,6 +379,77 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if alignment == .center {
|
||||||
|
var lines: [[ReactionButtonsAsyncLayoutContainer.ApplyResult.Item]] = []
|
||||||
|
var currentLine: [ReactionButtonsAsyncLayoutContainer.ApplyResult.Item] = []
|
||||||
|
var currentLineWidth: CGFloat = 0.0
|
||||||
|
|
||||||
|
for item in reactionButtons.items {
|
||||||
|
validIds.insert(item.value)
|
||||||
|
let itemWidth = item.size.width
|
||||||
|
|
||||||
|
if currentLineWidth + itemWidth + (currentLine.isEmpty ? 0 : itemSpacing) > boundingWidth + itemSpacing {
|
||||||
|
lines.append(currentLine)
|
||||||
|
currentLine = [item]
|
||||||
|
currentLineWidth = itemWidth
|
||||||
|
} else {
|
||||||
|
currentLine.append(item)
|
||||||
|
currentLineWidth += (currentLine.isEmpty ? 0 : itemSpacing) + itemWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !currentLine.isEmpty {
|
||||||
|
lines.append(currentLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
var yPosition = topInset
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
let totalItemWidth = line.reduce(0.0) { $0 + $1.size.width } + CGFloat(line.count - 1) * itemSpacing
|
||||||
|
let startX = (boundingWidth - totalItemWidth) / 2.0
|
||||||
|
var xPosition = startX
|
||||||
|
|
||||||
|
for item in line {
|
||||||
|
let itemFrame = CGRect(origin: CGPoint(x: xPosition, y: yPosition), size: item.size)
|
||||||
|
xPosition += item.size.width + itemSpacing
|
||||||
|
|
||||||
|
layoutItem(item, itemFrame)
|
||||||
|
}
|
||||||
|
yPosition += line.first!.size.height + itemSpacing
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for item in reactionButtons.items {
|
||||||
|
validIds.insert(item.value)
|
||||||
|
|
||||||
|
switch alignment {
|
||||||
|
case .left:
|
||||||
|
if reactionButtonPosition.x + item.size.width > boundingWidth {
|
||||||
|
reactionButtonPosition.x = -1.0
|
||||||
|
reactionButtonPosition.y += item.size.height + itemSpacing
|
||||||
|
}
|
||||||
|
case .right:
|
||||||
|
if reactionButtonPosition.x - item.size.width < -1.0 {
|
||||||
|
reactionButtonPosition.x = size.width + 1.0
|
||||||
|
reactionButtonPosition.y += item.size.height + itemSpacing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let itemFrame: CGRect
|
||||||
|
switch alignment {
|
||||||
|
case .left, .center:
|
||||||
|
itemFrame = CGRect(origin: reactionButtonPosition, size: item.size)
|
||||||
|
reactionButtonPosition.x += item.size.width + itemSpacing
|
||||||
|
case .right:
|
||||||
|
itemFrame = CGRect(origin: CGPoint(x: reactionButtonPosition.x - item.size.width, y: reactionButtonPosition.y), size: item.size)
|
||||||
|
reactionButtonPosition.x -= item.size.width + itemSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
layoutItem(item, itemFrame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var removeMaskIds: [MessageReaction.Reaction] = []
|
var removeMaskIds: [MessageReaction.Reaction] = []
|
||||||
for (id, view) in strongSelf.backgroundMaskButtons {
|
for (id, view) in strongSelf.backgroundMaskButtons {
|
||||||
if !validIds.contains(id) {
|
if !validIds.contains(id) {
|
||||||
@ -629,6 +682,7 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
|||||||
public let accountPeer: EnginePeer?
|
public let accountPeer: EnginePeer?
|
||||||
public let isIncoming: Bool
|
public let isIncoming: Bool
|
||||||
public let constrainedWidth: CGFloat
|
public let constrainedWidth: CGFloat
|
||||||
|
public let centerAligned: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
@ -641,7 +695,8 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
|||||||
associatedData: ChatMessageItemAssociatedData,
|
associatedData: ChatMessageItemAssociatedData,
|
||||||
accountPeer: EnginePeer?,
|
accountPeer: EnginePeer?,
|
||||||
isIncoming: Bool,
|
isIncoming: Bool,
|
||||||
constrainedWidth: CGFloat
|
constrainedWidth: CGFloat,
|
||||||
|
centerAligned: Bool
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
@ -654,6 +709,7 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
|||||||
self.accountPeer = accountPeer
|
self.accountPeer = accountPeer
|
||||||
self.isIncoming = isIncoming
|
self.isIncoming = isIncoming
|
||||||
self.constrainedWidth = constrainedWidth
|
self.constrainedWidth = constrainedWidth
|
||||||
|
self.centerAligned = centerAligned
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -682,6 +738,13 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
|||||||
return { arguments in
|
return { arguments in
|
||||||
let node = maybeNode ?? ChatMessageReactionButtonsNode()
|
let node = maybeNode ?? ChatMessageReactionButtonsNode()
|
||||||
|
|
||||||
|
let alignment: MessageReactionButtonsNode.DisplayAlignment
|
||||||
|
if arguments.centerAligned {
|
||||||
|
alignment = .center
|
||||||
|
} else {
|
||||||
|
alignment = arguments.isIncoming ? .left : .right
|
||||||
|
}
|
||||||
|
|
||||||
let buttonsUpdate = node.buttonsNode.prepareUpdate(
|
let buttonsUpdate = node.buttonsNode.prepareUpdate(
|
||||||
context: arguments.context,
|
context: arguments.context,
|
||||||
presentationData: arguments.presentationData,
|
presentationData: arguments.presentationData,
|
||||||
@ -692,7 +755,7 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
|||||||
accountPeer: arguments.accountPeer,
|
accountPeer: arguments.accountPeer,
|
||||||
message: arguments.message,
|
message: arguments.message,
|
||||||
associatedData: arguments.associatedData,
|
associatedData: arguments.associatedData,
|
||||||
alignment: arguments.isIncoming ? .left : .right,
|
alignment: alignment,
|
||||||
constrainedWidth: arguments.constrainedWidth,
|
constrainedWidth: arguments.constrainedWidth,
|
||||||
type: .freeform
|
type: .freeform
|
||||||
)
|
)
|
||||||
|
|||||||
@ -432,8 +432,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
|||||||
buttons = [
|
buttons = [
|
||||||
self.deleteButton,
|
self.deleteButton,
|
||||||
tagButton,
|
tagButton,
|
||||||
self.forwardButton,
|
self.shareButton,
|
||||||
self.shareButton
|
self.forwardButton
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
buttons = [
|
buttons = [
|
||||||
|
|||||||
@ -863,7 +863,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
associatedData: item.associatedData,
|
associatedData: item.associatedData,
|
||||||
accountPeer: item.associatedData.accountPeer,
|
accountPeer: item.associatedData.accountPeer,
|
||||||
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
|
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
|
||||||
constrainedWidth: maxReactionsWidth
|
constrainedWidth: maxReactionsWidth,
|
||||||
|
centerAligned: false
|
||||||
))
|
))
|
||||||
maxContentWidth = max(maxContentWidth, minWidth)
|
maxContentWidth = max(maxContentWidth, minWidth)
|
||||||
reactionButtonsFinalize = buttonsLayout
|
reactionButtonsFinalize = buttonsLayout
|
||||||
|
|||||||
@ -631,6 +631,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||||
}, playMessageEffect: { _ in
|
}, playMessageEffect: { _ in
|
||||||
}, editMessageFactCheck: { _ in
|
}, editMessageFactCheck: { _ in
|
||||||
|
}, sendGift: { _ in
|
||||||
}, requestMessageUpdate: { _, _ in
|
}, requestMessageUpdate: { _, _ in
|
||||||
|
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
|
|||||||
@ -488,6 +488,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
|
|||||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||||
}, playMessageEffect: { _ in
|
}, playMessageEffect: { _ in
|
||||||
}, editMessageFactCheck: { _ in
|
}, editMessageFactCheck: { _ in
|
||||||
|
}, sendGift: { _ in
|
||||||
}, requestMessageUpdate: { _, _ in
|
}, requestMessageUpdate: { _, _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, dismissTextInput: {
|
}, dismissTextInput: {
|
||||||
|
|||||||
@ -267,6 +267,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||||||
public let openAgeRestrictedMessageMedia: (Message, @escaping () -> Void) -> Void
|
public let openAgeRestrictedMessageMedia: (Message, @escaping () -> Void) -> Void
|
||||||
public let playMessageEffect: (Message) -> Void
|
public let playMessageEffect: (Message) -> Void
|
||||||
public let editMessageFactCheck: (MessageId) -> Void
|
public let editMessageFactCheck: (MessageId) -> Void
|
||||||
|
public let sendGift: (EnginePeer.Id) -> Void
|
||||||
|
|
||||||
public let requestMessageUpdate: (MessageId, Bool) -> Void
|
public let requestMessageUpdate: (MessageId, Bool) -> Void
|
||||||
public let cancelInteractiveKeyboardGestures: () -> Void
|
public let cancelInteractiveKeyboardGestures: () -> Void
|
||||||
@ -400,6 +401,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||||||
openAgeRestrictedMessageMedia: @escaping (Message, @escaping () -> Void) -> Void,
|
openAgeRestrictedMessageMedia: @escaping (Message, @escaping () -> Void) -> Void,
|
||||||
playMessageEffect: @escaping (Message) -> Void,
|
playMessageEffect: @escaping (Message) -> Void,
|
||||||
editMessageFactCheck: @escaping (MessageId) -> Void,
|
editMessageFactCheck: @escaping (MessageId) -> Void,
|
||||||
|
sendGift: @escaping (EnginePeer.Id) -> Void,
|
||||||
requestMessageUpdate: @escaping (MessageId, Bool) -> Void,
|
requestMessageUpdate: @escaping (MessageId, Bool) -> Void,
|
||||||
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
||||||
dismissTextInput: @escaping () -> Void,
|
dismissTextInput: @escaping () -> Void,
|
||||||
@ -512,6 +514,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||||||
self.openAgeRestrictedMessageMedia = openAgeRestrictedMessageMedia
|
self.openAgeRestrictedMessageMedia = openAgeRestrictedMessageMedia
|
||||||
self.playMessageEffect = playMessageEffect
|
self.playMessageEffect = playMessageEffect
|
||||||
self.editMessageFactCheck = editMessageFactCheck
|
self.editMessageFactCheck = editMessageFactCheck
|
||||||
|
self.sendGift = sendGift
|
||||||
|
|
||||||
self.requestMessageUpdate = requestMessageUpdate
|
self.requestMessageUpdate = requestMessageUpdate
|
||||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||||
|
|||||||
@ -406,7 +406,7 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
purpose: .starGift(peerId: component.peerId, requiredStars: starGift.price),
|
purpose: .starGift(peerId: component.peerId, requiredStars: starGift.price),
|
||||||
completion: { [weak starsContext] stars in
|
completion: { [weak starsContext] stars in
|
||||||
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0))
|
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||||
Queue.mainQueue().after(0.1) {
|
Queue.mainQueue().after(2.0) {
|
||||||
proceed()
|
proceed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -186,6 +186,7 @@ public final class LoadingOverlayNode: ASDisplayNode {
|
|||||||
}, dismissNotice: { _ in
|
}, dismissNotice: { _ in
|
||||||
}, editPeer: { _ in
|
}, editPeer: { _ in
|
||||||
}, openWebApp: { _ in
|
}, openWebApp: { _ in
|
||||||
|
}, openPhotoSetup: {
|
||||||
})
|
})
|
||||||
|
|
||||||
let items = (0 ..< 1).map { _ -> ChatListItem in
|
let items = (0 ..< 1).map { _ -> ChatListItem in
|
||||||
@ -542,6 +543,8 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
|
|||||||
editPeer: { _ in
|
editPeer: { _ in
|
||||||
},
|
},
|
||||||
openWebApp: { _ in
|
openWebApp: { _ in
|
||||||
|
},
|
||||||
|
openPhotoSetup: {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -3621,6 +3621,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||||
}, playMessageEffect: { _ in
|
}, playMessageEffect: { _ in
|
||||||
}, editMessageFactCheck: { _ in
|
}, editMessageFactCheck: { _ in
|
||||||
|
}, sendGift: { _ in
|
||||||
}, requestMessageUpdate: { _, _ in
|
}, requestMessageUpdate: { _, _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, dismissTextInput: {
|
}, dismissTextInput: {
|
||||||
@ -9825,7 +9826,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
legacyController.bind(controller: navigationController)
|
legacyController.bind(controller: navigationController)
|
||||||
|
|
||||||
strongSelf.view.endEditing(true)
|
strongSelf.view.endEditing(true)
|
||||||
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.present(legacyController, in: .window(.root))
|
|
||||||
|
let parentController = (strongSelf.context.sharedContext.mainWindow?.viewController as? NavigationController)?.topViewController as? ViewController
|
||||||
|
|
||||||
|
parentController?.present(legacyController, in: .window(.root))
|
||||||
|
|
||||||
var hasPhotos = false
|
var hasPhotos = false
|
||||||
if !peer.profileImageRepresentations.isEmpty {
|
if !peer.profileImageRepresentations.isEmpty {
|
||||||
@ -9876,7 +9880,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings || strongSelf.isMyProfile, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title, isSuggesting: [.custom, .suggest].contains(mode))!
|
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasDeleteButton, hasViewButton: false, personalPhoto: strongSelf.isSettings || strongSelf.isMyProfile, isVideo: currentIsVideo, saveEditedPhotos: false, saveCapturedMedia: false, signup: false, forum: isForum, title: title, isSuggesting: [.custom, .suggest].contains(mode))!
|
||||||
mixin.stickersContext = LegacyPaintStickersContext(context: strongSelf.context)
|
mixin.stickersContext = LegacyPaintStickersContext(context: strongSelf.context)
|
||||||
let _ = strongSelf.currentAvatarMixin.swap(mixin)
|
let _ = strongSelf.currentAvatarMixin.swap(mixin)
|
||||||
mixin.requestSearchController = { [weak self] assetsController in
|
mixin.requestSearchController = { [weak self, weak parentController] assetsController in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -9885,14 +9889,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
self?.updateProfilePhoto(result, mode: mode)
|
self?.updateProfilePhoto(result, mode: mode)
|
||||||
}))
|
}))
|
||||||
controller.navigationPresentation = .modal
|
controller.navigationPresentation = .modal
|
||||||
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller)
|
parentController?.push(controller)
|
||||||
|
|
||||||
if fromGallery {
|
if fromGallery {
|
||||||
completion(nil)
|
completion(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var isFromEditor = false
|
var isFromEditor = false
|
||||||
mixin.requestAvatarEditor = { [weak self] imageCompletion, videoCompletion in
|
mixin.requestAvatarEditor = { [weak self, weak parentController] imageCompletion, videoCompletion in
|
||||||
guard let strongSelf = self, let imageCompletion, let videoCompletion else {
|
guard let strongSelf = self, let imageCompletion, let videoCompletion else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -9913,27 +9917,33 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
let controller = AvatarEditorScreen(context: strongSelf.context, inputData: keyboardInputData.get(), peerType: peerType, markup: emojiMarkup)
|
let controller = AvatarEditorScreen(context: strongSelf.context, inputData: keyboardInputData.get(), peerType: peerType, markup: emojiMarkup)
|
||||||
controller.imageCompletion = imageCompletion
|
controller.imageCompletion = imageCompletion
|
||||||
controller.videoCompletion = videoCompletion
|
controller.videoCompletion = videoCompletion
|
||||||
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller)
|
parentController?.push(controller)
|
||||||
isFromEditor = true
|
isFromEditor = true
|
||||||
|
|
||||||
|
Queue.mainQueue().after(1.0) {
|
||||||
|
if let rootController = strongSelf.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||||
|
rootController.openSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let confirmationTextPhoto, let confirmationAction {
|
if let confirmationTextPhoto, let confirmationAction {
|
||||||
mixin.willFinishWithImage = { [weak self] image, commit in
|
mixin.willFinishWithImage = { [weak self, weak parentController] image, commit in
|
||||||
if let strongSelf = self, let image {
|
if let strongSelf = self, let image {
|
||||||
let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextPhoto, doneTitle: confirmationAction, commit: {
|
let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextPhoto, doneTitle: confirmationAction, commit: {
|
||||||
commit?()
|
commit?()
|
||||||
})
|
})
|
||||||
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.presentInGlobalOverlay(controller)
|
parentController?.presentInGlobalOverlay(controller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let confirmationTextVideo, let confirmationAction {
|
if let confirmationTextVideo, let confirmationAction {
|
||||||
mixin.willFinishWithVideo = { [weak self] image, commit in
|
mixin.willFinishWithVideo = { [weak self, weak parentController] image, commit in
|
||||||
if let strongSelf = self, let image {
|
if let strongSelf = self, let image {
|
||||||
let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextVideo, doneTitle: confirmationAction, isDark: !isFromEditor, commit: {
|
let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextVideo, doneTitle: confirmationAction, isDark: !isFromEditor, commit: {
|
||||||
commit?()
|
commit?()
|
||||||
})
|
})
|
||||||
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.presentInGlobalOverlay(controller)
|
parentController?.presentInGlobalOverlay(controller)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -13008,6 +13018,20 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func openAvatarSetup() {
|
||||||
|
let proceed = { [weak self] in
|
||||||
|
self?.controllerNode.openAvatarForEditing()
|
||||||
|
}
|
||||||
|
if !self.isNodeLoaded {
|
||||||
|
self.loadDisplayNode()
|
||||||
|
Queue.mainQueue().after(0.1) {
|
||||||
|
proceed()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proceed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func updateProfilePhoto(_ image: UIImage, mode: PeerInfoAvatarEditingMode) {
|
public func updateProfilePhoto(_ image: UIImage, mode: PeerInfoAvatarEditingMode) {
|
||||||
if !self.isNodeLoaded {
|
if !self.isNodeLoaded {
|
||||||
self.loadDisplayNode()
|
self.loadDisplayNode()
|
||||||
|
|||||||
@ -209,6 +209,8 @@ final class GreetingMessageListItemComponent: Component {
|
|||||||
editPeer: { _ in
|
editPeer: { _ in
|
||||||
},
|
},
|
||||||
openWebApp: { _ in
|
openWebApp: { _ in
|
||||||
|
},
|
||||||
|
openPhotoSetup: {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.chatListNodeInteraction = chatListNodeInteraction
|
self.chatListNodeInteraction = chatListNodeInteraction
|
||||||
|
|||||||
@ -230,6 +230,8 @@ final class QuickReplySetupScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
openWebApp: { _ in
|
openWebApp: { _ in
|
||||||
|
},
|
||||||
|
openPhotoSetup: {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -874,6 +874,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
|
|||||||
}, dismissNotice: { _ in
|
}, dismissNotice: { _ in
|
||||||
}, editPeer: { _ in
|
}, editPeer: { _ in
|
||||||
}, openWebApp: { _ in
|
}, openWebApp: { _ in
|
||||||
|
}, openPhotoSetup: {
|
||||||
})
|
})
|
||||||
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||||
|
|
||||||
|
|||||||
12
submodules/TelegramUI/Images.xcassets/Avatar/CameraIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Avatar/CameraIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "setavatar.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/Avatar/CameraIcon.imageset/setavatar.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Avatar/CameraIcon.imageset/setavatar.pdf
vendored
Normal file
Binary file not shown.
@ -2093,7 +2093,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendStickers) != nil {
|
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendStickers) != nil {
|
||||||
if let boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict {
|
if !canBypassRestrictions(chatPresentationInterfaceState: strongSelf.presentationInterfaceState) {
|
||||||
strongSelf.interfaceInteraction?.openBoostToUnrestrict()
|
strongSelf.interfaceInteraction?.openBoostToUnrestrict()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -2245,7 +2245,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendGifs) != nil {
|
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendGifs) != nil {
|
||||||
if let boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict {
|
if !canBypassRestrictions(chatPresentationInterfaceState: strongSelf.presentationInterfaceState) {
|
||||||
strongSelf.interfaceInteraction?.openBoostToUnrestrict()
|
strongSelf.interfaceInteraction?.openBoostToUnrestrict()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -2296,7 +2296,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendGifs) != nil {
|
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, peer.hasBannedPermission(.banSendGifs) != nil {
|
||||||
if let boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict {
|
if !canBypassRestrictions(chatPresentationInterfaceState: strongSelf.presentationInterfaceState) {
|
||||||
strongSelf.interfaceInteraction?.openBoostToUnrestrict()
|
strongSelf.interfaceInteraction?.openBoostToUnrestrict()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -4554,6 +4554,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.openEditMessageFactCheck(messageId: messageId)
|
self.openEditMessageFactCheck(messageId: messageId)
|
||||||
|
}, sendGift: { [weak self] peerId in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = (self.context.engine.payments.premiumGiftCodeOptions(peerId: nil, onlyCached: true)
|
||||||
|
|> filter { !$0.isEmpty }
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] giftOptions in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let premiumOptions = giftOptions.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
|
||||||
|
let controller = self.context.sharedContext.makeGiftOptionsController(context: context, peerId: peerId, premiumOptions: premiumOptions, hasBirthday: false)
|
||||||
|
self.push(controller)
|
||||||
|
})
|
||||||
}, requestMessageUpdate: { [weak self] id, scroll in
|
}, requestMessageUpdate: { [weak self] id, scroll in
|
||||||
if let self {
|
if let self {
|
||||||
self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll)
|
self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll)
|
||||||
|
|||||||
@ -339,7 +339,8 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS
|
|||||||
case .peer:
|
case .peer:
|
||||||
if let channel = peer as? TelegramChannel {
|
if let channel = peer as? TelegramChannel {
|
||||||
if case .member = channel.participationStatus {
|
if case .member = channel.participationStatus {
|
||||||
canReply = channel.hasPermission(.sendSomething)
|
let canBypassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: chatPresentationInterfaceState)
|
||||||
|
canReply = channel.hasPermission(.sendSomething, ignoreDefault: canBypassRestrictions)
|
||||||
}
|
}
|
||||||
if case .broadcast = channel.info {
|
if case .broadcast = channel.info {
|
||||||
canReply = true
|
canReply = true
|
||||||
@ -1120,6 +1121,23 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if data.messageActions.options.contains(.sendGift) {
|
||||||
|
//TODO:localize
|
||||||
|
let sendGiftTitle: String
|
||||||
|
if message.effectivelyIncoming(context.account.peerId) {
|
||||||
|
let peerName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? ""
|
||||||
|
sendGiftTitle = "Send Gift to \(peerName)"
|
||||||
|
} else {
|
||||||
|
sendGiftTitle = "Send Another Gift"
|
||||||
|
}
|
||||||
|
actions.append(.action(ContextMenuActionItem(text: sendGiftTitle, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Gift"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
let _ = controllerInteraction.sendGift(message.id.peerId)
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
var isReplyThreadHead = false
|
var isReplyThreadHead = false
|
||||||
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
|
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
|
||||||
isReplyThreadHead = messages[0].id == replyThreadMessage.effectiveTopId
|
isReplyThreadHead = messages[0].id == replyThreadMessage.effectiveTopId
|
||||||
@ -2220,8 +2238,15 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else if let action = media as? TelegramMediaAction, case .phoneCall = action.action {
|
} else if let action = media as? TelegramMediaAction {
|
||||||
|
switch action.action {
|
||||||
|
case .phoneCall:
|
||||||
optionsMap[id]!.insert(.rateCall)
|
optionsMap[id]!.insert(.rateCall)
|
||||||
|
case .starGift:
|
||||||
|
optionsMap[id]!.insert(.sendGift)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
} else if let story = media as? TelegramMediaStory {
|
} else if let story = media as? TelegramMediaStory {
|
||||||
if let story = message.associatedStories[story.storyId], story.data.isEmpty {
|
if let story = message.associatedStories[story.storyId], story.data.isEmpty {
|
||||||
isShareProtected = true
|
isShareProtected = true
|
||||||
|
|||||||
@ -9,16 +9,6 @@ import ChatBotStartInputPanelNode
|
|||||||
import ChatChannelSubscriberInputPanelNode
|
import ChatChannelSubscriberInputPanelNode
|
||||||
import ChatMessageSelectionInputPanelNode
|
import ChatMessageSelectionInputPanelNode
|
||||||
|
|
||||||
func canBypassRestrictions(chatPresentationInterfaceState: ChatPresentationInterfaceState) -> Bool {
|
|
||||||
guard let boostsToUnrestrict = chatPresentationInterfaceState.boostsToUnrestrict else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if let appliedBoosts = chatPresentationInterfaceState.appliedBoosts, appliedBoosts >= boostsToUnrestrict {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputPanelNode?, currentSecondaryPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> (primary: ChatInputPanelNode?, secondary: ChatInputPanelNode?) {
|
func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputPanelNode?, currentSecondaryPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> (primary: ChatInputPanelNode?, secondary: ChatInputPanelNode?) {
|
||||||
if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
|
if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
|
||||||
return (nil, nil)
|
return (nil, nil)
|
||||||
|
|||||||
@ -294,6 +294,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe
|
|||||||
}, dismissNotice: { _ in
|
}, dismissNotice: { _ in
|
||||||
}, editPeer: { _ in
|
}, editPeer: { _ in
|
||||||
}, openWebApp: { _ in
|
}, openWebApp: { _ in
|
||||||
|
}, openPhotoSetup: {
|
||||||
})
|
})
|
||||||
interaction.searchTextHighightState = searchQuery
|
interaction.searchTextHighightState = searchQuery
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
|||||||
@ -179,6 +179,8 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
|
|||||||
editPeer: { _ in
|
editPeer: { _ in
|
||||||
},
|
},
|
||||||
openWebApp: { _ in
|
openWebApp: { _ in
|
||||||
|
},
|
||||||
|
openPhotoSetup: {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -180,6 +180,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||||
}, playMessageEffect: { _ in
|
}, playMessageEffect: { _ in
|
||||||
}, editMessageFactCheck: { _ in
|
}, editMessageFactCheck: { _ in
|
||||||
|
}, sendGift: { _ in
|
||||||
}, requestMessageUpdate: { _, _ in
|
}, requestMessageUpdate: { _, _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, dismissTextInput: {
|
}, dismissTextInput: {
|
||||||
|
|||||||
@ -1796,6 +1796,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||||
}, playMessageEffect: { _ in
|
}, playMessageEffect: { _ in
|
||||||
}, editMessageFactCheck: { _ in
|
}, editMessageFactCheck: { _ in
|
||||||
|
}, sendGift: { _ in
|
||||||
}, requestMessageUpdate: { _, _ in
|
}, requestMessageUpdate: { _, _ in
|
||||||
}, cancelInteractiveKeyboardGestures: {
|
}, cancelInteractiveKeyboardGestures: {
|
||||||
}, dismissTextInput: {
|
}, dismissTextInput: {
|
||||||
|
|||||||
@ -744,6 +744,10 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||||||
public func openBirthdaySetup() {
|
public func openBirthdaySetup() {
|
||||||
self.accountSettingsController?.openBirthdaySetup()
|
self.accountSettingsController?.openBirthdaySetup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func openPhotoSetup() {
|
||||||
|
self.accountSettingsController?.openAvatarSetup()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Xcode 16
|
//Xcode 16
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user