mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +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 editScheduledTime = ChatAvailableMessageActionOptions(rawValue: 1 << 9)
|
||||
public static let externalShare = ChatAvailableMessageActionOptions(rawValue: 1 << 10)
|
||||
public static let sendGift = ChatAvailableMessageActionOptions(rawValue: 1 << 11)
|
||||
}
|
||||
|
||||
public struct ChatAvailableMessageActions {
|
||||
@ -802,6 +803,7 @@ public protocol TelegramRootControllerInterface: NavigationController {
|
||||
func getPrivacySettings() -> Promise<AccountPrivacySettings?>?
|
||||
func openSettings()
|
||||
func openBirthdaySetup()
|
||||
func openPhotoSetup()
|
||||
}
|
||||
|
||||
public protocol QuickReplySetupScreenInitialData: AnyObject {
|
||||
|
@ -959,6 +959,7 @@ public protocol PeerInfoScreen: ViewController {
|
||||
func toggleStorySelection(ids: [Int32], isSelected: Bool)
|
||||
func togglePaneIsReordering(isReordering: Bool)
|
||||
func cancelItemSelection()
|
||||
func openAvatarSetup()
|
||||
}
|
||||
|
||||
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 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 cameraIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/CameraIcon"), color: .white)
|
||||
|
||||
public func avatarPlaceholderFont(size: CGFloat) -> UIFont {
|
||||
return Font.with(size: size, design: .round, weight: .bold)
|
||||
@ -86,7 +87,7 @@ public func calculateAvatarColors(context: AccountContext?, explicitColorIndex:
|
||||
}
|
||||
|
||||
let colors: [UIColor]
|
||||
if icon != .none {
|
||||
if icon != .none && icon != .cameraIcon {
|
||||
if case .deletedIcon = icon {
|
||||
colors = AvatarNode.grayscaleColors
|
||||
} else if case .phoneIcon = icon {
|
||||
@ -196,6 +197,7 @@ public enum AvatarNodeIcon: Equatable {
|
||||
case deletedIcon
|
||||
case phoneIcon
|
||||
case repostIcon
|
||||
case cameraIcon
|
||||
}
|
||||
|
||||
public enum AvatarNodeImageOverride: Equatable {
|
||||
@ -210,6 +212,7 @@ public enum AvatarNodeImageOverride: Equatable {
|
||||
case deletedIcon
|
||||
case phoneIcon
|
||||
case repostIcon
|
||||
case cameraIcon
|
||||
}
|
||||
|
||||
public enum AvatarNodeColorOverride {
|
||||
@ -540,6 +543,9 @@ public final class AvatarNode: ASDisplayNode {
|
||||
case .phoneIcon:
|
||||
representation = nil
|
||||
icon = .phoneIcon
|
||||
case .cameraIcon:
|
||||
representation = nil
|
||||
icon = .cameraIcon
|
||||
}
|
||||
} else if peer?.restrictionText(platform: "ios", contentSettings: contentSettings) == nil {
|
||||
representation = peer?.smallProfileImage
|
||||
@ -716,6 +722,9 @@ public final class AvatarNode: ASDisplayNode {
|
||||
case .phoneIcon:
|
||||
representation = nil
|
||||
icon = .phoneIcon
|
||||
case .cameraIcon:
|
||||
representation = nil
|
||||
icon = .cameraIcon
|
||||
}
|
||||
} else if peer?.restrictionText(platform: "ios", contentSettings: genericContext.currentContentSettings.with { $0 }) == nil {
|
||||
representation = peer?.smallProfileImage
|
||||
@ -959,6 +968,15 @@ public final class AvatarNode: ASDisplayNode {
|
||||
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))
|
||||
}
|
||||
} 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 {
|
||||
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.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 {
|
||||
context.setBlendMode(.normal)
|
||||
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.45).cgColor)
|
||||
|
@ -162,6 +162,7 @@ public final class BrowserBookmarksScreen: ViewController {
|
||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||
}, playMessageEffect: { _ in
|
||||
}, editMessageFactCheck: { _ in
|
||||
}, sendGift: { _ in
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
|
@ -214,7 +214,8 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
||||
theme: PresentationTheme,
|
||||
mode: HorizontalPeerItemMode,
|
||||
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.strings = strings
|
||||
self.themeAndStringsPromise = Promise((self.theme, self.strings))
|
||||
@ -225,6 +226,7 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
||||
self.isPeerSelected = isPeerSelected
|
||||
|
||||
self.listView = ListView()
|
||||
self.listView.preloadPages = false
|
||||
self.listView.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
self.listView.accessibilityPageScrolledString = { row, count in
|
||||
return strings.VoiceOver_ScrollStatus(row, count).string
|
||||
@ -340,11 +342,6 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
||||
)
|
||||
|
||||
strongSelf.enqueueTransition(transition)
|
||||
|
||||
if !strongSelf.didSetReady {
|
||||
strongSelf.ready.set(.single(true))
|
||||
strongSelf.didSetReady = true
|
||||
}
|
||||
}
|
||||
}))
|
||||
if case .actionSheet = mode {
|
||||
@ -371,7 +368,20 @@ public final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
||||
} else if transition.animated {
|
||||
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
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -354,6 +354,9 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
|
||||
itemNode.listNode.openWebApp = { [weak self] amount in
|
||||
self?.openWebApp?(amount)
|
||||
}
|
||||
itemNode.listNode.openPhotoSetup = { [weak self] in
|
||||
self?.openPhotoSetup?()
|
||||
}
|
||||
|
||||
self.currentItemStateValue.set(itemNode.listNode.state |> map { state in
|
||||
let filterId: Int32?
|
||||
@ -421,6 +424,7 @@ public final class ChatListContainerNode: ASDisplayNode, ASGestureRecognizerDele
|
||||
var openStories: ((ChatListNode.OpenStoriesSubject, ASDisplayNode?) -> Void)?
|
||||
var openStarsTopup: ((Int64?) -> Void)?
|
||||
var openWebApp: ((TelegramUser) -> Void)?
|
||||
var openPhotoSetup: (() -> Void)?
|
||||
var addedVisibleChatsWithPeerIds: (([EnginePeer.Id]) -> Void)?
|
||||
var didBeginSelectingChats: (() -> Void)?
|
||||
var canExpandHiddenItems: (() -> Bool)?
|
||||
|
@ -115,7 +115,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
presentationData: ChatListPresentationData,
|
||||
filter: ChatListNodePeersFilter,
|
||||
key: ChatListSearchPaneKey,
|
||||
peerSelected: @escaping (EnginePeer, Int64?, Bool) -> Void,
|
||||
peerSelected: @escaping (EnginePeer, Int64?, Bool, OpenPeerAction) -> Void,
|
||||
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
|
||||
peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?,
|
||||
clearRecentlySearchedPeers: @escaping () -> Void,
|
||||
@ -130,7 +130,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
switch self {
|
||||
case let .topPeers(peers, theme, strings):
|
||||
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
|
||||
if let peerContextAction = peerContextAction {
|
||||
peerContextAction(peer, .recentPeers, node, gesture, location)
|
||||
@ -287,21 +287,28 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
}
|
||||
|
||||
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(
|
||||
title: presentationData.strings.ChatList_Search_Open,
|
||||
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(
|
||||
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, dateTimeFormat: presentationData.dateTimeFormat),
|
||||
sortOrder: nameSortOrder,
|
||||
displayOrder: nameDisplayOrder,
|
||||
context: context,
|
||||
peerMode: .generalSearch(isSavedMessages: false),
|
||||
peerMode: peerMode,
|
||||
peer: .peer(peer: primaryPeer, chatPeer: chatPeer),
|
||||
status: status,
|
||||
badge: badge,
|
||||
@ -315,7 +322,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
alwaysShowLastSeparator: key == .apps,
|
||||
action: { _ in
|
||||
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
|
||||
@ -1067,6 +1074,12 @@ public struct ChatListSearchContainerTransition {
|
||||
}
|
||||
}
|
||||
|
||||
enum OpenPeerAction {
|
||||
case generic
|
||||
case info
|
||||
case openApp
|
||||
}
|
||||
|
||||
private func chatListSearchContainerPreparedRecentTransition(
|
||||
from fromEntries: [ChatListRecentEntry],
|
||||
to toEntries: [ChatListRecentEntry],
|
||||
@ -1075,7 +1088,7 @@ private func chatListSearchContainerPreparedRecentTransition(
|
||||
presentationData: ChatListPresentationData,
|
||||
filter: ChatListNodePeersFilter,
|
||||
key: ChatListSearchPaneKey,
|
||||
peerSelected: @escaping (EnginePeer, Int64?, Bool) -> Void,
|
||||
peerSelected: @escaping (EnginePeer, Int64?, Bool, OpenPeerAction) -> Void,
|
||||
disabledPeerSelected: @escaping (EnginePeer, Int64?, ChatListDisabledPeerReason) -> Void,
|
||||
peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?,
|
||||
clearRecentlySearchedPeers: @escaping () -> Void,
|
||||
@ -1468,6 +1481,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
self.selectedMessagesPromise.set(.single(self.selectedMessages))
|
||||
|
||||
self.recentListNode = ListView()
|
||||
self.recentListNode.preloadPages = false
|
||||
self.recentListNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||
self.recentListNode.accessibilityPageScrolledString = { row, count in
|
||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
||||
@ -3014,6 +3028,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
})
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -3848,36 +3863,39 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
} else if case .apps = key {
|
||||
if let navigationController = self.navigationController {
|
||||
if isRecommended {
|
||||
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)
|
||||
}
|
||||
} else if case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), let parentController = self.parentController {
|
||||
self.context.sharedContext.openWebApp(
|
||||
context: self.context,
|
||||
parentController: parentController,
|
||||
updatedPresentationData: nil,
|
||||
botPeer: peer,
|
||||
chatPeer: nil,
|
||||
threadId: nil,
|
||||
buttonText: "",
|
||||
url: "",
|
||||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: true,
|
||||
payload: nil
|
||||
)
|
||||
} else {
|
||||
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) {
|
||||
navigationController.pushViewController(peerInfoScreen)
|
||||
}
|
||||
case .openApp:
|
||||
if let parentController = self.parentController {
|
||||
self.context.sharedContext.openWebApp(
|
||||
context: self.context,
|
||||
parentController: parentController,
|
||||
updatedPresentationData: nil,
|
||||
botPeer: peer,
|
||||
chatPeer: nil,
|
||||
threadId: nil,
|
||||
buttonText: "",
|
||||
url: "",
|
||||
simple: true,
|
||||
source: .generic,
|
||||
skipTermsOfService: true,
|
||||
payload: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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(
|
||||
context: self.context,
|
||||
parentController: parentController,
|
||||
@ -4581,6 +4599,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
strongSelf.ready.set(.single(true))
|
||||
}
|
||||
strongSelf.didSetReady = true
|
||||
if !strongSelf.recentListNode.preloadPages {
|
||||
Queue.mainQueue().after(0.5) {
|
||||
strongSelf.recentListNode.preloadPages = true
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.emptyRecentAnimationNode?.isHidden = !transition.isEmpty
|
||||
strongSelf.emptyRecentTitleNode?.isHidden = !transition.isEmpty
|
||||
@ -4907,6 +4930,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
})
|
||||
var isInlineMode = false
|
||||
if case .topics = key {
|
||||
|
@ -160,6 +160,7 @@ public final class ChatListShimmerNode: ASDisplayNode {
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
})
|
||||
interaction.isInlineMode = isInlineMode
|
||||
|
||||
|
@ -113,6 +113,7 @@ public final class ChatListNodeInteraction {
|
||||
let dismissNotice: (ChatListNotice) -> Void
|
||||
let editPeer: (ChatListItem) -> Void
|
||||
let openWebApp: (TelegramUser) -> Void
|
||||
let openPhotoSetup: () -> Void
|
||||
|
||||
public var searchTextHighightState: String?
|
||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||
@ -169,7 +170,8 @@ public final class ChatListNodeInteraction {
|
||||
openStarsTopup: @escaping (Int64?) -> Void,
|
||||
dismissNotice: @escaping (ChatListNotice) -> Void,
|
||||
editPeer: @escaping (ChatListItem) -> Void,
|
||||
openWebApp: @escaping (TelegramUser) -> Void
|
||||
openWebApp: @escaping (TelegramUser) -> Void,
|
||||
openPhotoSetup: @escaping () -> Void
|
||||
) {
|
||||
self.activateSearch = activateSearch
|
||||
self.peerSelected = peerSelected
|
||||
@ -214,6 +216,7 @@ public final class ChatListNodeInteraction {
|
||||
self.dismissNotice = dismissNotice
|
||||
self.editPeer = editPeer
|
||||
self.openWebApp = openWebApp
|
||||
self.openPhotoSetup = openPhotoSetup
|
||||
}
|
||||
}
|
||||
|
||||
@ -519,10 +522,13 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if canManage {
|
||||
} 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 {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
@ -759,6 +765,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
break
|
||||
case let .starsSubscriptionLowBalance(amount, _):
|
||||
nodeInteraction?.openStarsTopup(amount.value)
|
||||
case .setupPhoto:
|
||||
nodeInteraction?.openPhotoSetup()
|
||||
}
|
||||
case .hide:
|
||||
nodeInteraction?.dismissNotice(notice)
|
||||
@ -1103,6 +1111,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
break
|
||||
case let .starsSubscriptionLowBalance(amount, _):
|
||||
nodeInteraction?.openStarsTopup(amount.value)
|
||||
case .setupPhoto:
|
||||
nodeInteraction?.openPhotoSetup()
|
||||
}
|
||||
case .hide:
|
||||
nodeInteraction?.dismissNotice(notice)
|
||||
@ -1224,6 +1234,7 @@ public final class ChatListNode: ListView {
|
||||
public var openPremiumManagement: (() -> Void)?
|
||||
public var openStarsTopup: ((Int64?) -> Void)?
|
||||
public var openWebApp: ((TelegramUser) -> Void)?
|
||||
public var openPhotoSetup: (() -> Void)?
|
||||
|
||||
private var theme: PresentationTheme
|
||||
|
||||
@ -1876,6 +1887,11 @@ public final class ChatListNode: ListView {
|
||||
return
|
||||
}
|
||||
self.openWebApp?(user)
|
||||
}, openPhotoSetup: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.openPhotoSetup?()
|
||||
})
|
||||
nodeInteraction.isInlineMode = isInlineMode
|
||||
|
||||
@ -1970,11 +1986,16 @@ public final class ChatListNode: ListView {
|
||||
context.engine.notices.getServerDismissedSuggestions(),
|
||||
twoStepData,
|
||||
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,
|
||||
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 {
|
||||
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)))
|
||||
return .single(nil)
|
||||
}
|
||||
} else if suggestions.contains(.setupPhoto), let accountPeer, accountPeer.smallProfileImage == nil {
|
||||
return .single(.setupPhoto(accountPeer))
|
||||
} else if suggestions.contains(.gracePremium) {
|
||||
return .single(.premiumGrace)
|
||||
} else if suggestions.contains(.setupBirthday) && birthday == nil {
|
||||
@ -2413,10 +2436,13 @@ public final class ChatListNode: ListView {
|
||||
default:
|
||||
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 {
|
||||
} else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -91,6 +91,7 @@ public enum ChatListNotice: Equatable {
|
||||
case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int)
|
||||
case premiumGrace
|
||||
case starsSubscriptionLowBalance(amount: StarsAmount, peers: [EnginePeer])
|
||||
case setupPhoto(EnginePeer)
|
||||
}
|
||||
|
||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
@ -605,6 +606,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
|
||||
|
||||
var result: [ChatListNodeEntry] = []
|
||||
|
||||
var hasContacts = false
|
||||
if !view.hasEarlier {
|
||||
var existingPeerIds = Set<EnginePeer.Id>()
|
||||
for item in view.items {
|
||||
@ -620,8 +622,9 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
|
||||
peer: contact.peer,
|
||||
presence: contact.presence
|
||||
)))
|
||||
hasContacts = true
|
||||
}
|
||||
if !contacts.isEmpty {
|
||||
if hasContacts {
|
||||
result.append(.SectionHeader(presentationData: state.presentationData, displayHide: !view.items.isEmpty))
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import AccountContext
|
||||
import MergedAvatarsNode
|
||||
import TextNodeWithEntities
|
||||
import TextFormat
|
||||
import AvatarNode
|
||||
|
||||
class ChatListNoticeItem: ListViewItem {
|
||||
enum Action {
|
||||
@ -94,6 +95,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
|
||||
private let arrowNode: ASImageNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
private var avatarNode: AvatarNode?
|
||||
private var avatarsNode: MergedAvatarsNode?
|
||||
|
||||
private var closeButton: HighlightableButtonNode?
|
||||
@ -175,6 +177,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let titleString: NSAttributedString
|
||||
let textString: NSAttributedString
|
||||
var avatarPeer: EnginePeer?
|
||||
var avatarPeers: [EnginePeer] = []
|
||||
|
||||
var okButtonLayout: (TextNodeLayout, () -> TextNode)?
|
||||
@ -285,12 +288,20 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
titleString = attributedTitle
|
||||
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
|
||||
if !avatarPeers.isEmpty {
|
||||
let avatarsWidth = 30.0 + CGFloat(avatarPeers.count - 1) * 16.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))
|
||||
@ -349,6 +360,25 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
|
||||
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 {
|
||||
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 {
|
||||
if let peer = state.renderedPeer?.peer {
|
||||
if canSendMessagesToPeer(peer) {
|
||||
let canBypassRestrictions = canBypassRestrictions(chatPresentationInterfaceState: state)
|
||||
if canSendMessagesToPeer(peer, ignoreDefault: canBypassRestrictions) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
@ -68,6 +68,7 @@ public enum ContactsPeerItemPeerMode: Equatable {
|
||||
case generalSearch(isSavedMessages: Bool)
|
||||
case peer
|
||||
case memberList
|
||||
case app(isPopular: Bool)
|
||||
}
|
||||
|
||||
public enum ContactsPeerItemBadgeType {
|
||||
@ -722,7 +723,15 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let titleFont = Font.regular(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 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
|
||||
let verticalInset: CGFloat = statusAttributedString == nil ? 13.0 : 6.0
|
||||
var verticalInset: CGFloat = statusAttributedString == nil ? 13.0 : 6.0
|
||||
if case .app = item.peerMode {
|
||||
verticalInset += 2.0
|
||||
}
|
||||
|
||||
let statusHeightComponent: CGFloat
|
||||
if statusAttributedString == nil {
|
||||
@ -1058,7 +1069,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let titleFrame: CGRect
|
||||
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 {
|
||||
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 {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
|
||||
var displayDimensions = CGSize(width: 60.0, height: 60.0)
|
||||
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
|
||||
} else {
|
||||
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):
|
||||
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)
|
||||
|
||||
|
@ -97,7 +97,11 @@ final class NavigationTransitionCoordinator {
|
||||
case .Push:
|
||||
self.container.addSubnode(topNode)
|
||||
case .Pop:
|
||||
self.container.insertSubnode(bottomNode, belowSubnode: topNode)
|
||||
if topNode.supernode == self.container {
|
||||
self.container.insertSubnode(bottomNode, belowSubnode: topNode)
|
||||
} else {
|
||||
self.container.addSubnode(topNode)
|
||||
}
|
||||
}
|
||||
|
||||
if !self.isFlat {
|
||||
|
@ -477,6 +477,10 @@ public struct MessageFlags: OptionSet {
|
||||
rawValue |= MessageFlags.IsForumTopic.rawValue
|
||||
}
|
||||
|
||||
if flags.contains(StoreMessageFlags.ReactionsArePossible) {
|
||||
rawValue |= MessageFlags.ReactionsArePossible.rawValue
|
||||
}
|
||||
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
@ -490,6 +494,7 @@ public struct MessageFlags: OptionSet {
|
||||
public static let CountedAsIncoming = MessageFlags(rawValue: 256)
|
||||
public static let CopyProtected = MessageFlags(rawValue: 512)
|
||||
public static let IsForumTopic = MessageFlags(rawValue: 1024)
|
||||
public static let ReactionsArePossible = MessageFlags(rawValue: 2048)
|
||||
|
||||
public static let IsIncomingMask = MessageFlags([.Incoming, .CountedAsIncoming])
|
||||
}
|
||||
@ -843,6 +848,10 @@ public struct StoreMessageFlags: OptionSet {
|
||||
rawValue |= StoreMessageFlags.IsForumTopic.rawValue
|
||||
}
|
||||
|
||||
if flags.contains(.ReactionsArePossible) {
|
||||
rawValue |= StoreMessageFlags.ReactionsArePossible.rawValue
|
||||
}
|
||||
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
@ -856,6 +865,7 @@ public struct StoreMessageFlags: OptionSet {
|
||||
public static let CountedAsIncoming = StoreMessageFlags(rawValue: 256)
|
||||
public static let CopyProtected = StoreMessageFlags(rawValue: 512)
|
||||
public static let IsForumTopic = StoreMessageFlags(rawValue: 1024)
|
||||
public static let ReactionsArePossible = StoreMessageFlags(rawValue: 2048)
|
||||
|
||||
public static let IsIncomingMask = StoreMessageFlags([.Incoming, .CountedAsIncoming])
|
||||
}
|
||||
|
@ -231,6 +231,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, ASScrollView
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ 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)
|
||||
|
@ -380,6 +380,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
})
|
||||
|
||||
func makeChatListItem(
|
||||
|
@ -537,7 +537,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) }
|
||||
dict[-1808510398] = { return Api.Message.parse_message($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[-988359047] = { return Api.MessageAction.parse_messageActionBotAllowed($0) }
|
||||
dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) }
|
||||
|
@ -62,7 +62,7 @@ public extension Api {
|
||||
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 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) {
|
||||
switch self {
|
||||
@ -115,9 +115,9 @@ public extension Api {
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {peerId!.serialize(buffer, true)}
|
||||
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 {
|
||||
buffer.appendInt32(721967202)
|
||||
buffer.appendInt32(-741178048)
|
||||
}
|
||||
serializeInt32(flags, 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)}
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
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)}
|
||||
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)])
|
||||
case .messageEmpty(let flags, let id, let peerId):
|
||||
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):
|
||||
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)])
|
||||
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), ("reactions", reactions as Any), ("ttlPeriod", ttlPeriod as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,8 +301,12 @@ public extension Api {
|
||||
if let signature = reader.readInt32() {
|
||||
_7 = Api.parse(reader, signature: signature) as? Api.MessageAction
|
||||
}
|
||||
var _8: Int32?
|
||||
if Int(_1!) & Int(1 << 25) != 0 {_8 = reader.readInt32() }
|
||||
var _8: Api.MessageReactions?
|
||||
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 _c2 = _2 != 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 _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = (Int(_1!) & Int(1 << 25) == 0) || _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.Message.messageService(flags: _1!, id: _2!, fromId: _3, peerId: _4!, replyTo: _5, date: _6!, action: _7!, ttlPeriod: _8)
|
||||
let _c8 = (Int(_1!) & Int(1 << 20) == 0) || _8 != nil
|
||||
let _c9 = (Int(_1!) & Int(1 << 25) == 0) || _9 != nil
|
||||
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 {
|
||||
return nil
|
||||
|
@ -135,7 +135,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case let .messageService(_, _, _, chatPeerId, _, _, _, _):
|
||||
case let .messageService(_, _, _, chatPeerId, _, _, _, _, _):
|
||||
return chatPeerId.peerId
|
||||
}
|
||||
}
|
||||
@ -216,7 +216,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
|
||||
return result
|
||||
case .messageEmpty:
|
||||
return []
|
||||
case let .messageService(_, _, fromId, chatPeerId, _, _, action, _):
|
||||
case let .messageService(_, _, fromId, chatPeerId, _, _, action, _, _):
|
||||
let peerId: PeerId = chatPeerId.peerId
|
||||
var result = [peerId]
|
||||
|
||||
@ -295,7 +295,7 @@ func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: Refere
|
||||
}
|
||||
case .messageEmpty:
|
||||
break
|
||||
case let .messageService(_, id, _, chatPeerId, replyHeader, _, _, _):
|
||||
case let .messageService(_, id, _, chatPeerId, replyHeader, _, _, _, _):
|
||||
if let replyHeader = replyHeader {
|
||||
switch replyHeader {
|
||||
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)
|
||||
case .messageEmpty:
|
||||
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 authorId: PeerId? = fromId?.peerId ?? chatPeerId.peerId
|
||||
|
||||
@ -1074,6 +1074,10 @@ extension StoreMessage {
|
||||
if (flags & (1 << 17)) != 0 {
|
||||
attributes.append(ContentRequiresValidationMessageAttribute())
|
||||
}
|
||||
|
||||
if let reactions = reactions {
|
||||
attributes.append(ReactionsMessageAttribute(apiReactions: reactions))
|
||||
}
|
||||
|
||||
var storeFlags = StoreMessageFlags()
|
||||
if (flags & 2) == 0 {
|
||||
@ -1120,6 +1124,10 @@ extension StoreMessage {
|
||||
if (flags & (1 << 27)) != 0 {
|
||||
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)
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ public enum TelegramChannelPermission {
|
||||
}
|
||||
|
||||
public extension TelegramChannel {
|
||||
func hasPermission(_ permission: TelegramChannelPermission) -> Bool {
|
||||
func hasPermission(_ permission: TelegramChannelPermission, ignoreDefault: Bool = false) -> Bool {
|
||||
if self.flags.contains(.isCreator) {
|
||||
if case .canBeAnonymous = permission {
|
||||
if let adminRights = self.adminRights {
|
||||
@ -50,7 +50,7 @@ public extension TelegramChannel {
|
||||
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendText) {
|
||||
return false
|
||||
}
|
||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendText) {
|
||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendText) && !ignoreDefault {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -69,7 +69,7 @@ public extension TelegramChannel {
|
||||
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendPhotos) {
|
||||
return false
|
||||
}
|
||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendPhotos) {
|
||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendPhotos) && !ignoreDefault {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -88,7 +88,7 @@ public extension TelegramChannel {
|
||||
if let bannedRights = self.bannedRights, bannedRights.flags.contains(.banSendVideos) {
|
||||
return false
|
||||
}
|
||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendVideos) {
|
||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banSendVideos) && !ignoreDefault {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -121,7 +121,7 @@ public extension TelegramChannel {
|
||||
if let bannedRights = self.bannedRights, bannedRights.flags.intersection(flags) == flags {
|
||||
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 true
|
||||
|
@ -108,7 +108,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
||||
updatedTimestamp = date
|
||||
case .messageEmpty:
|
||||
break
|
||||
case let .messageService(_, _, _, _, _, date, _, _):
|
||||
case let .messageService(_, _, _, _, _, date, _, _, _):
|
||||
updatedTimestamp = date
|
||||
}
|
||||
} else {
|
||||
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
||||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 195
|
||||
return 196
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
@ -108,7 +108,7 @@ extension Api.Message {
|
||||
return id
|
||||
case let .messageEmpty(_, id, _):
|
||||
return id
|
||||
case let .messageService(_, id, _, _, _, _, _, _):
|
||||
case let .messageService(_, id, _, _, _, _, _, _, _):
|
||||
return id
|
||||
}
|
||||
}
|
||||
@ -128,7 +128,7 @@ extension Api.Message {
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case let .messageService(_, id, _, chatPeerId, _, _, _, _):
|
||||
case let .messageService(_, id, _, chatPeerId, _, _, _, _, _):
|
||||
let peerId: PeerId = chatPeerId.peerId
|
||||
return MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: id)
|
||||
}
|
||||
@ -141,7 +141,7 @@ extension Api.Message {
|
||||
return peerId
|
||||
case let .messageEmpty(_, _, peerId):
|
||||
return peerId?.peerId
|
||||
case let .messageService(_, _, _, chatPeerId, _, _, _, _):
|
||||
case let .messageService(_, _, _, chatPeerId, _, _, _, _, _):
|
||||
let peerId: PeerId = chatPeerId.peerId
|
||||
return peerId
|
||||
}
|
||||
@ -151,7 +151,7 @@ extension Api.Message {
|
||||
switch self {
|
||||
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return date
|
||||
case let .messageService(_, _, _, _, _, date, _, _):
|
||||
case let .messageService(_, _, _, _, _, date, _, _, _):
|
||||
return date
|
||||
case .messageEmpty:
|
||||
return nil
|
||||
|
@ -17,6 +17,7 @@ public enum ServerProvidedSuggestion: String {
|
||||
case todayBirthdays = "BIRTHDAY_CONTACTS_TODAY"
|
||||
case gracePremium = "PREMIUM_GRACE"
|
||||
case starsSubscriptionLowBalance = "STARS_SUBSCRIPTION_LOW_BALANCE"
|
||||
case setupPhoto = "USERPIC_SETUP"
|
||||
}
|
||||
|
||||
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 {
|
||||
return []
|
||||
}
|
||||
|
||||
return listItems.compactMap { item -> ServerProvidedSuggestion? in
|
||||
return ServerProvidedSuggestion(rawValue: item)
|
||||
}.filter { !dismissedSuggestions.contains($0) }
|
||||
|
@ -6,7 +6,7 @@ import Postbox
|
||||
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" {
|
||||
return false
|
||||
} 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 {
|
||||
return peer.embeddedState == .active
|
||||
} else if let peer = peer as? TelegramChannel {
|
||||
return peer.hasPermission(.sendSomething)
|
||||
return peer.hasPermission(.sendSomething, ignoreDefault: ignoreDefault)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -671,6 +671,8 @@ public final class ChatInlineSearchResultsListComponent: Component {
|
||||
editPeer: { _ in
|
||||
},
|
||||
openWebApp: { _ in
|
||||
},
|
||||
openPhotoSetup: {
|
||||
}
|
||||
)
|
||||
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)
|
||||
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, { [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 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 {
|
||||
let imageNode: TransformImageNode
|
||||
if let current = strongSelf.imageNode {
|
||||
@ -319,7 +320,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
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 {
|
||||
let leadingIconView: UIImageView
|
||||
|
@ -1301,7 +1301,8 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
associatedData: item.associatedData,
|
||||
accountPeer: item.associatedData.accountPeer,
|
||||
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
|
||||
constrainedWidth: maxReactionsWidth
|
||||
constrainedWidth: maxReactionsWidth,
|
||||
centerAligned: false
|
||||
))
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
reactionButtonsFinalize = buttonsLayout
|
||||
|
@ -208,6 +208,7 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
isAction = true
|
||||
if case .phoneCall = action.action {
|
||||
result.append((message, ChatMessageCallBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
} else if case .giftPremium = action.action {
|
||||
result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
} 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)))
|
||||
} else if case .joinedChannel = action.action {
|
||||
result.append((message, ChatMessageJoinedChannelBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
} else {
|
||||
switch action.action {
|
||||
case .photoUpdated:
|
||||
break
|
||||
default:
|
||||
needReactions = false
|
||||
}
|
||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
}
|
||||
needReactions = false
|
||||
} else if let _ = media as? TelegramMediaMap {
|
||||
result.append((message, ChatMessageMapBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
||||
} else if let _ = media as? TelegramMediaGame {
|
||||
@ -2700,6 +2707,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
var reactionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode))?
|
||||
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
|
||||
if hasInstantVideo {
|
||||
maximumNodeWidth = min(309, baseWidth - 84)
|
||||
@ -2715,7 +2730,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
associatedData: item.associatedData,
|
||||
accountPeer: item.associatedData.accountPeer,
|
||||
isIncoming: incoming,
|
||||
constrainedWidth: maximumNodeWidth
|
||||
constrainedWidth: maximumNodeWidth,
|
||||
centerAligned: centerAligned
|
||||
))
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
reactionButtonsFinalize = buttonsLayout
|
||||
|
@ -616,7 +616,8 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco
|
||||
associatedData: item.associatedData,
|
||||
accountPeer: item.associatedData.accountPeer,
|
||||
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
|
||||
constrainedWidth: maxReactionsWidth
|
||||
constrainedWidth: maxReactionsWidth,
|
||||
centerAligned: false
|
||||
))
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
reactionButtonsFinalize = buttonsLayout
|
||||
|
@ -285,7 +285,7 @@ public func canAddMessageReactions(message: Message) -> Bool {
|
||||
}
|
||||
for media in message.media {
|
||||
if let _ = media as? TelegramMediaAction {
|
||||
return false
|
||||
return message.flags.contains(.ReactionsArePossible)
|
||||
} else if let story = media as? TelegramMediaStory {
|
||||
if story.isMention {
|
||||
return false
|
||||
|
@ -30,6 +30,7 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
||||
public enum DisplayAlignment {
|
||||
case left
|
||||
case right
|
||||
case center
|
||||
}
|
||||
|
||||
private var bubbleBackgroundNode: WallpaperBubbleBackgroundNode?
|
||||
@ -224,27 +225,29 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
||||
constrainedWidth: constrainedWidth
|
||||
)
|
||||
|
||||
let itemSpacing: CGFloat = 6.0
|
||||
|
||||
var reactionButtonsSize = CGSize()
|
||||
var currentRowWidth: CGFloat = 0.0
|
||||
for item in reactionButtonsResult.items {
|
||||
if currentRowWidth + item.size.width > constrainedWidth {
|
||||
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
|
||||
if !reactionButtonsSize.height.isZero {
|
||||
reactionButtonsSize.height += 6.0
|
||||
reactionButtonsSize.height += itemSpacing
|
||||
}
|
||||
reactionButtonsSize.height += item.size.height
|
||||
currentRowWidth = 0.0
|
||||
}
|
||||
|
||||
if !currentRowWidth.isZero {
|
||||
currentRowWidth += 6.0
|
||||
currentRowWidth += itemSpacing
|
||||
}
|
||||
currentRowWidth += item.size.width
|
||||
}
|
||||
if !currentRowWidth.isZero && !reactionButtonsResult.items.isEmpty {
|
||||
reactionButtonsSize.width = max(reactionButtonsSize.width, currentRowWidth)
|
||||
if !reactionButtonsSize.height.isZero {
|
||||
reactionButtonsSize.height += 6.0
|
||||
reactionButtonsSize.height += itemSpacing
|
||||
}
|
||||
reactionButtonsSize.height += reactionButtonsResult.items[0].size.height
|
||||
}
|
||||
@ -296,11 +299,14 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
var reactionButtonPosition: CGPoint
|
||||
|
||||
switch alignment {
|
||||
case .left:
|
||||
reactionButtonPosition = CGPoint(x: -1.0, y: topInset)
|
||||
case .right:
|
||||
reactionButtonPosition = CGPoint(x: size.width + 1.0, y: topInset)
|
||||
case .center:
|
||||
reactionButtonPosition = CGPoint(x: 0.0, y: topInset)
|
||||
}
|
||||
|
||||
let reactionButtons = reactionButtonsResult.apply(
|
||||
@ -312,32 +318,8 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
|
||||
)
|
||||
|
||||
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 itemMaskView: UIView
|
||||
@ -396,6 +378,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] = []
|
||||
for (id, view) in strongSelf.backgroundMaskButtons {
|
||||
@ -629,6 +682,7 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
||||
public let accountPeer: EnginePeer?
|
||||
public let isIncoming: Bool
|
||||
public let constrainedWidth: CGFloat
|
||||
public let centerAligned: Bool
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
@ -641,7 +695,8 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
||||
associatedData: ChatMessageItemAssociatedData,
|
||||
accountPeer: EnginePeer?,
|
||||
isIncoming: Bool,
|
||||
constrainedWidth: CGFloat
|
||||
constrainedWidth: CGFloat,
|
||||
centerAligned: Bool
|
||||
) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
@ -654,6 +709,7 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
||||
self.accountPeer = accountPeer
|
||||
self.isIncoming = isIncoming
|
||||
self.constrainedWidth = constrainedWidth
|
||||
self.centerAligned = centerAligned
|
||||
}
|
||||
}
|
||||
|
||||
@ -682,6 +738,13 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
||||
return { arguments in
|
||||
let node = maybeNode ?? ChatMessageReactionButtonsNode()
|
||||
|
||||
let alignment: MessageReactionButtonsNode.DisplayAlignment
|
||||
if arguments.centerAligned {
|
||||
alignment = .center
|
||||
} else {
|
||||
alignment = arguments.isIncoming ? .left : .right
|
||||
}
|
||||
|
||||
let buttonsUpdate = node.buttonsNode.prepareUpdate(
|
||||
context: arguments.context,
|
||||
presentationData: arguments.presentationData,
|
||||
@ -692,7 +755,7 @@ public final class ChatMessageReactionButtonsNode: ASDisplayNode {
|
||||
accountPeer: arguments.accountPeer,
|
||||
message: arguments.message,
|
||||
associatedData: arguments.associatedData,
|
||||
alignment: arguments.isIncoming ? .left : .right,
|
||||
alignment: alignment,
|
||||
constrainedWidth: arguments.constrainedWidth,
|
||||
type: .freeform
|
||||
)
|
||||
|
@ -432,8 +432,8 @@ public final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
||||
buttons = [
|
||||
self.deleteButton,
|
||||
tagButton,
|
||||
self.forwardButton,
|
||||
self.shareButton
|
||||
self.shareButton,
|
||||
self.forwardButton
|
||||
]
|
||||
} else {
|
||||
buttons = [
|
||||
|
@ -863,7 +863,8 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
associatedData: item.associatedData,
|
||||
accountPeer: item.associatedData.accountPeer,
|
||||
isIncoming: item.message.effectivelyIncoming(item.context.account.peerId),
|
||||
constrainedWidth: maxReactionsWidth
|
||||
constrainedWidth: maxReactionsWidth,
|
||||
centerAligned: false
|
||||
))
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
reactionButtonsFinalize = buttonsLayout
|
||||
|
@ -631,6 +631,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||
}, playMessageEffect: { _ in
|
||||
}, editMessageFactCheck: { _ in
|
||||
}, sendGift: { _ in
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
|
@ -488,6 +488,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess
|
||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||
}, playMessageEffect: { _ in
|
||||
}, editMessageFactCheck: { _ in
|
||||
}, sendGift: { _ in
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
|
@ -267,6 +267,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
public let openAgeRestrictedMessageMedia: (Message, @escaping () -> Void) -> Void
|
||||
public let playMessageEffect: (Message) -> Void
|
||||
public let editMessageFactCheck: (MessageId) -> Void
|
||||
public let sendGift: (EnginePeer.Id) -> Void
|
||||
|
||||
public let requestMessageUpdate: (MessageId, Bool) -> Void
|
||||
public let cancelInteractiveKeyboardGestures: () -> Void
|
||||
@ -400,6 +401,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
openAgeRestrictedMessageMedia: @escaping (Message, @escaping () -> Void) -> Void,
|
||||
playMessageEffect: @escaping (Message) -> Void,
|
||||
editMessageFactCheck: @escaping (MessageId) -> Void,
|
||||
sendGift: @escaping (EnginePeer.Id) -> Void,
|
||||
requestMessageUpdate: @escaping (MessageId, Bool) -> Void,
|
||||
cancelInteractiveKeyboardGestures: @escaping () -> Void,
|
||||
dismissTextInput: @escaping () -> Void,
|
||||
@ -512,6 +514,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
self.openAgeRestrictedMessageMedia = openAgeRestrictedMessageMedia
|
||||
self.playMessageEffect = playMessageEffect
|
||||
self.editMessageFactCheck = editMessageFactCheck
|
||||
self.sendGift = sendGift
|
||||
|
||||
self.requestMessageUpdate = requestMessageUpdate
|
||||
self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures
|
||||
|
@ -406,7 +406,7 @@ final class GiftSetupScreenComponent: Component {
|
||||
purpose: .starGift(peerId: component.peerId, requiredStars: starGift.price),
|
||||
completion: { [weak starsContext] stars in
|
||||
starsContext?.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||
Queue.mainQueue().after(0.1) {
|
||||
Queue.mainQueue().after(2.0) {
|
||||
proceed()
|
||||
}
|
||||
}
|
||||
|
@ -186,6 +186,7 @@ public final class LoadingOverlayNode: ASDisplayNode {
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
})
|
||||
|
||||
let items = (0 ..< 1).map { _ -> ChatListItem in
|
||||
@ -542,6 +543,8 @@ private final class PeerInfoScreenPersonalChannelItemNode: PeerInfoScreenItemNod
|
||||
editPeer: { _ in
|
||||
},
|
||||
openWebApp: { _ in
|
||||
},
|
||||
openPhotoSetup: {
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -3621,6 +3621,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||
}, playMessageEffect: { _ in
|
||||
}, editMessageFactCheck: { _ in
|
||||
}, sendGift: { _ in
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
@ -9788,7 +9789,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
fileprivate func openAvatarForEditing(mode: PeerInfoAvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping (UIImage?) -> Void = { _ in }) {
|
||||
guard let peer = self.data?.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else {
|
||||
return
|
||||
@ -9825,7 +9826,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
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
|
||||
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))!
|
||||
mixin.stickersContext = LegacyPaintStickersContext(context: strongSelf.context)
|
||||
let _ = strongSelf.currentAvatarMixin.swap(mixin)
|
||||
mixin.requestSearchController = { [weak self] assetsController in
|
||||
mixin.requestSearchController = { [weak self, weak parentController] assetsController in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -9885,14 +9889,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
self?.updateProfilePhoto(result, mode: mode)
|
||||
}))
|
||||
controller.navigationPresentation = .modal
|
||||
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller)
|
||||
parentController?.push(controller)
|
||||
|
||||
if fromGallery {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -9913,27 +9917,33 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
let controller = AvatarEditorScreen(context: strongSelf.context, inputData: keyboardInputData.get(), peerType: peerType, markup: emojiMarkup)
|
||||
controller.imageCompletion = imageCompletion
|
||||
controller.videoCompletion = videoCompletion
|
||||
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller)
|
||||
parentController?.push(controller)
|
||||
isFromEditor = true
|
||||
|
||||
Queue.mainQueue().after(1.0) {
|
||||
if let rootController = strongSelf.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||
rootController.openSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextPhoto, doneTitle: confirmationAction, commit: {
|
||||
commit?()
|
||||
})
|
||||
(strongSelf.controller?.navigationController?.topViewController as? ViewController)?.presentInGlobalOverlay(controller)
|
||||
parentController?.presentInGlobalOverlay(controller)
|
||||
}
|
||||
}
|
||||
}
|
||||
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 {
|
||||
let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextVideo, doneTitle: confirmationAction, isDark: !isFromEditor, 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) {
|
||||
if !self.isNodeLoaded {
|
||||
self.loadDisplayNode()
|
||||
|
@ -209,6 +209,8 @@ final class GreetingMessageListItemComponent: Component {
|
||||
editPeer: { _ in
|
||||
},
|
||||
openWebApp: { _ in
|
||||
},
|
||||
openPhotoSetup: {
|
||||
}
|
||||
)
|
||||
self.chatListNodeInteraction = chatListNodeInteraction
|
||||
|
@ -230,6 +230,8 @@ final class QuickReplySetupScreenComponent: Component {
|
||||
}
|
||||
},
|
||||
openWebApp: { _ in
|
||||
},
|
||||
openPhotoSetup: {
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -874,6 +874,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, ASScrollViewDelegate
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ 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)
|
||||
|
||||
|
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 boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict {
|
||||
if !canBypassRestrictions(chatPresentationInterfaceState: strongSelf.presentationInterfaceState) {
|
||||
strongSelf.interfaceInteraction?.openBoostToUnrestrict()
|
||||
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 boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict {
|
||||
if !canBypassRestrictions(chatPresentationInterfaceState: strongSelf.presentationInterfaceState) {
|
||||
strongSelf.interfaceInteraction?.openBoostToUnrestrict()
|
||||
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 boostsToUnrestrict = strongSelf.presentationInterfaceState.boostsToUnrestrict, boostsToUnrestrict > 0, (strongSelf.presentationInterfaceState.appliedBoosts ?? 0) < boostsToUnrestrict {
|
||||
if !canBypassRestrictions(chatPresentationInterfaceState: strongSelf.presentationInterfaceState) {
|
||||
strongSelf.interfaceInteraction?.openBoostToUnrestrict()
|
||||
return false
|
||||
}
|
||||
@ -4554,6 +4554,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
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
|
||||
if let self {
|
||||
self.chatDisplayNode.historyNode.requestMessageUpdate(id, andScrollToItem: scroll)
|
||||
|
@ -339,7 +339,8 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS
|
||||
case .peer:
|
||||
if let channel = peer as? TelegramChannel {
|
||||
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 {
|
||||
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
|
||||
if case let .replyThread(replyThreadMessage) = chatPresentationInterfaceState.chatLocation {
|
||||
isReplyThreadHead = messages[0].id == replyThreadMessage.effectiveTopId
|
||||
@ -2220,8 +2238,15 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if let action = media as? TelegramMediaAction, case .phoneCall = action.action {
|
||||
optionsMap[id]!.insert(.rateCall)
|
||||
} else if let action = media as? TelegramMediaAction {
|
||||
switch action.action {
|
||||
case .phoneCall:
|
||||
optionsMap[id]!.insert(.rateCall)
|
||||
case .starGift:
|
||||
optionsMap[id]!.insert(.sendGift)
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if let story = media as? TelegramMediaStory {
|
||||
if let story = message.associatedStories[story.storyId], story.data.isEmpty {
|
||||
isShareProtected = true
|
||||
|
@ -9,16 +9,6 @@ import ChatBotStartInputPanelNode
|
||||
import ChatChannelSubscriberInputPanelNode
|
||||
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?) {
|
||||
if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
|
||||
return (nil, nil)
|
||||
|
@ -294,6 +294,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, ASScrollViewDe
|
||||
}, dismissNotice: { _ in
|
||||
}, editPeer: { _ in
|
||||
}, openWebApp: { _ in
|
||||
}, openPhotoSetup: {
|
||||
})
|
||||
interaction.searchTextHighightState = searchQuery
|
||||
self.interaction = interaction
|
||||
|
@ -179,6 +179,8 @@ private struct CommandChatInputContextPanelEntry: Comparable, Identifiable {
|
||||
editPeer: { _ in
|
||||
},
|
||||
openWebApp: { _ in
|
||||
},
|
||||
openPhotoSetup: {
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -180,6 +180,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||
}, playMessageEffect: { _ in
|
||||
}, editMessageFactCheck: { _ in
|
||||
}, sendGift: { _ in
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
|
@ -1796,6 +1796,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}, openAgeRestrictedMessageMedia: { _, _ in
|
||||
}, playMessageEffect: { _ in
|
||||
}, editMessageFactCheck: { _ in
|
||||
}, sendGift: { _ in
|
||||
}, requestMessageUpdate: { _, _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, dismissTextInput: {
|
||||
|
@ -744,6 +744,10 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
public func openBirthdaySetup() {
|
||||
self.accountSettingsController?.openBirthdaySetup()
|
||||
}
|
||||
|
||||
public func openPhotoSetup() {
|
||||
self.accountSettingsController?.openAvatarSetup()
|
||||
}
|
||||
}
|
||||
|
||||
//Xcode 16
|
||||
|
Loading…
x
Reference in New Issue
Block a user