Various improvements

This commit is contained in:
Ilya Laktyushin 2022-11-23 14:47:30 +04:00
parent 5d25b32ac4
commit e3498e99a1
29 changed files with 313 additions and 222 deletions

View File

@ -8182,7 +8182,7 @@ Sorry for the inconvenience.";
"ChatList.SelectedTopics_any" = "%@ Topics Selected";
"ChatList.EmptyTopicsTitle" = "No topics here yet";
"ChatList.EmptyTopicsCreate" = "Create New Topic";
"ChatList.EmptyTopicsCreate" = "Create Topic";
"ChatList.EmptyTopicsShowAsMessages" = "Show as Messages";
"Message.AudioTranscription.SubscribeToPremium" = "Subscribe to **Telegram Premium** to convert voice to text.";
@ -8301,7 +8301,11 @@ Sorry for the inconvenience.";
"Notification.OverviewTopicUnhidden" = "%1$@ unhid %2$@ %3$@";
"CreateTopic.ShowGeneral" = "Show in Topics";
"CreateTopic.ShowGeneralInfo" = "If the 'General' topic is hidden, group members can pull down in the topic list to view it.";
"CreateTopic.ShowGeneralInfo" = "If the \"General\" topic is hidden, group members can pull down in the topic list to view it.";
"Conversation.ContextMenuReportFalsePositive" = "Report False Positive";
"Group.AdminLog.AntiSpamFalsePositiveReportedText" = "Telegram moderators will review your report. Thank you!";
"ChatList.EmptyTopicsDescription" = "Older messages from this group have been moved to \"General\".";
"Stickers.EmojiPackInfoText" = "This message contains **%@** emoji.";

View File

@ -776,7 +776,7 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
}
private func openCustomMute(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, baseController: ViewController) {
let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, peerId: peerId, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak baseController] value in
let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak baseController] value in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if value <= 0 {

View File

@ -1173,14 +1173,14 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
self.chatListDisplayNode.emptyListAction = { [weak self] in
self.chatListDisplayNode.emptyListAction = { [weak self] forumPeerId in
guard let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController else {
return
}
if let filter = strongSelf.chatListDisplayNode.containerNode.currentItemNode.chatListFilter {
strongSelf.push(chatListFilterPresetController(context: strongSelf.context, currentPreset: filter, updated: { _ in }))
} else {
if case let .forum(peerId) = strongSelf.location {
if let peerId = forumPeerId {
let context = strongSelf.context
let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create)
controller.navigationPresentation = .modal

View File

@ -333,68 +333,75 @@ private final class ChatListContainerItemNode: ASDisplayNode {
}
var needsShimmerNode = false
var shimmerNodeOffset: CGFloat = 0.0
var needsEmptyNode = false
var hasOnlyGeneralThread = false
var isLoading = false
switch isEmptyState {
case let .empty(isLoading, hasArchiveInfo):
case let .empty(isLoadingValue, hasArchiveInfo):
if hasArchiveInfo {
shimmerNodeOffset = 253.0
}
if isLoading {
if isLoadingValue {
needsShimmerNode = true
if let emptyNode = strongSelf.emptyNode {
strongSelf.emptyNode = nil
transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in
emptyNode?.removeFromSupernode()
})
}
needsEmptyNode = false
isLoading = isLoadingValue
} else {
if let currentNode = strongSelf.emptyNode {
currentNode.updateIsLoading(isLoading)
} else {
let subject: ChatListEmptyNode.Subject
if let filter = filter {
var showEdit = true
if case let .filter(_, _, _, data) = filter {
if data.excludeRead && data.includePeers.peers.isEmpty && data.includePeers.pinnedPeers.isEmpty {
showEdit = false
}
}
subject = .filter(showEdit: showEdit)
} else {
if case .forum = location {
subject = .forum
} else {
subject = .chats
}
}
let emptyNode = ChatListEmptyNode(context: context, subject: subject, isLoading: isLoading, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, action: {
self?.emptyAction(filter)
}, secondaryAction: {
self?.secondaryEmptyAction()
})
strongSelf.emptyNode = emptyNode
strongSelf.addSubnode(emptyNode)
if let (size, insets, _, _, _, _) = strongSelf.validLayout {
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
emptyNode.frame = emptyNodeFrame
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate)
}
emptyNode.alpha = 0.0
transition.updateAlpha(node: emptyNode, alpha: 1.0)
}
needsEmptyNode = true
}
if !isLoading {
if !isLoadingValue {
strongSelf.becameEmpty(filter)
}
case .notEmpty:
if let emptyNode = strongSelf.emptyNode {
strongSelf.emptyNode = nil
transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in
emptyNode?.removeFromSupernode()
})
}
case let .notEmpty(_, onlyGeneralThreadValue):
needsEmptyNode = onlyGeneralThreadValue
hasOnlyGeneralThread = onlyGeneralThreadValue
}
if needsEmptyNode {
if let currentNode = strongSelf.emptyNode {
currentNode.updateIsLoading(isLoading)
} else {
let subject: ChatListEmptyNode.Subject
if let filter = filter {
var showEdit = true
if case let .filter(_, _, _, data) = filter {
if data.excludeRead && data.includePeers.peers.isEmpty && data.includePeers.pinnedPeers.isEmpty {
showEdit = false
}
}
subject = .filter(showEdit: showEdit)
} else {
if case .forum = location {
subject = .forum(hasGeneral: hasOnlyGeneralThread)
} else {
subject = .chats
}
}
let emptyNode = ChatListEmptyNode(context: context, subject: subject, isLoading: isLoading, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, action: {
self?.emptyAction(filter)
}, secondaryAction: {
self?.secondaryEmptyAction()
})
strongSelf.emptyNode = emptyNode
strongSelf.addSubnode(emptyNode)
if let (size, insets, _, _, _, _) = strongSelf.validLayout {
let emptyNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
emptyNode.frame = emptyNodeFrame
emptyNode.updateLayout(size: emptyNodeFrame.size, transition: .immediate)
}
emptyNode.alpha = 0.0
transition.updateAlpha(node: emptyNode, alpha: 1.0)
}
} else if let emptyNode = strongSelf.emptyNode {
strongSelf.emptyNode = nil
transition.updateAlpha(node: emptyNode, alpha: 0.0, completion: { [weak emptyNode] _ in
emptyNode?.removeFromSupernode()
})
}
if needsShimmerNode {
strongSelf.shimmerNodeOffset = shimmerNodeOffset
if strongSelf.emptyShimmerEffectNode == nil {
@ -1191,7 +1198,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
var peerContextAction: ((EnginePeer, ChatListSearchContextActionSource, ASDisplayNode, ContextGesture?, CGPoint?) -> Void)?
var dismissSelfIfCompletedPresentation: (() -> Void)?
var isEmptyUpdated: ((Bool) -> Void)?
var emptyListAction: (() -> Void)?
var emptyListAction: ((EnginePeer.Id?) -> Void)?
var cancelEditing: (() -> Void)?
let debugListView = ListView()
@ -1243,11 +1250,11 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
strongSelf.dismissSelfIfCompletedPresentation?()
}
}
filterEmptyAction = { [weak self] filter in
filterEmptyAction = { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.emptyListAction?()
strongSelf.emptyListAction?(nil)
}
secondaryEmptyAction = { [weak self] in
@ -1629,7 +1636,12 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
func setInlineChatList(location: ChatListControllerLocation?) {
if let location = location {
if self.inlineStackContainerNode?.location != location {
let inlineStackContainerNode = ChatListContainerNode(context: self.context, location: location, previewing: false, controlsHistoryPreload: false, isInlineMode: true, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { _ in }, secondaryEmptyAction: {})
var forumPeerId: EnginePeer.Id?
if case let .forum(peerId) = location {
forumPeerId = peerId
}
let inlineStackContainerNode = ChatListContainerNode(context: self.context, location: location, previewing: false, controlsHistoryPreload: false, isInlineMode: true, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { [weak self] _ in self?.emptyListAction?(forumPeerId) }, secondaryEmptyAction: {})
inlineStackContainerNode.leftSeparatorLayer.isHidden = false

View File

@ -14,7 +14,7 @@ final class ChatListEmptyNode: ASDisplayNode {
enum Subject {
case chats
case filter(showEdit: Bool)
case forum
case forum(hasGeneral: Bool)
}
private let action: () -> Void
private let secondaryAction: () -> Void
@ -131,7 +131,6 @@ final class ChatListEmptyNode: ASDisplayNode {
let text: String
var descriptionText = ""
let buttonText: String
var secondaryButtonText = ""
switch self.subject {
case .chats:
text = strings.ChatList_EmptyChatList
@ -143,7 +142,7 @@ final class ChatListEmptyNode: ASDisplayNode {
case .forum:
text = strings.ChatList_EmptyTopicsTitle
buttonText = strings.ChatList_EmptyTopicsCreate
secondaryButtonText = strings.ChatList_EmptyTopicsShowAsMessages
descriptionText = strings.ChatList_EmptyTopicsDescription
}
let string = NSMutableAttributedString(string: text, font: Font.medium(17.0), textColor: theme.list.itemPrimaryTextColor)
let descriptionString = NSAttributedString(string: descriptionText, font: Font.regular(14.0), textColor: theme.list.itemSecondaryTextColor)
@ -152,8 +151,6 @@ final class ChatListEmptyNode: ASDisplayNode {
self.descriptionNode.attributedText = descriptionString
self.buttonNode.title = buttonText
self.secondaryButtonNode.setAttributedTitle(NSAttributedString(string: secondaryButtonText, font: Font.regular(17.0), textColor: theme.list.itemAccentColor), for: .normal)
self.secondaryButtonNode.isHidden = secondaryButtonText.isEmpty
self.activityIndicator.type = .custom(theme.list.itemAccentColor, 22.0, 1.0, false)
@ -186,16 +183,21 @@ final class ChatListEmptyNode: ASDisplayNode {
let textSize = self.textNode.updateLayout(CGSize(width: size.width - 40.0, height: size.height))
let descriptionSize = self.descriptionNode.updateLayout(CGSize(width: size.width - 40.0, height: size.height))
let buttonSideInset: CGFloat = 16.0
let buttonWidth = size.width - buttonSideInset * 2.0
let buttonSideInset: CGFloat = 32.0
let buttonWidth = min(270.0, size.width - buttonSideInset * 2.0)
let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition)
let buttonSize = CGSize(width: buttonWidth, height: buttonHeight)
let secondaryButtonSize = self.secondaryButtonNode.measure(CGSize(width: buttonWidth, height: .greatestFiniteMagnitude))
var threshold: CGFloat = 0.0
if case .forum = self.subject {
threshold = 80.0
}
let contentHeight = self.animationSize.height + animationSpacing + textSize.height + buttonSize.height
var contentOffset: CGFloat = 0.0
if size.height < contentHeight {
if size.height < contentHeight + threshold {
contentOffset = -self.animationSize.height - animationSpacing + 44.0
transition.updateAlpha(node: self.animationNode, alpha: 0.0)
} else {
@ -224,7 +226,12 @@ final class ChatListEmptyNode: ASDisplayNode {
bottomInset += secondaryButtonSize.height + 23.0
}
let buttonFrame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: size.height - buttonHeight - bottomInset), size: buttonSize)
let buttonFrame: CGRect
if case .forum = self.subject {
buttonFrame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: descriptionFrame.maxY + 20.0), size: buttonSize)
} else {
buttonFrame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: size.height - buttonHeight - bottomInset), size: buttonSize)
}
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
}

View File

@ -759,7 +759,7 @@ public enum ChatListNodeScrollPosition {
}
public enum ChatListNodeEmptyState: Equatable {
case notEmpty(containsChats: Bool)
case notEmpty(containsChats: Bool, onlyGeneralThread: Bool)
case empty(isLoading: Bool, hasArchiveInfo: Bool)
}
@ -2244,16 +2244,30 @@ public final class ChatListNode: ListView {
isEmptyState = .empty(isLoading: isLoading, hasArchiveInfo: false)
} else {
var containsChats = false
var threadCount = 0
var hasGeneral = false
loop: for entry in transition.chatListView.filteredEntries {
switch entry {
case .GroupReferenceEntry, .HoleEntry, .PeerEntry:
containsChats = true
break loop
if case .forum = strongSelf.location {
if case let .PeerEntry(_, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry, let threadInfo {
if threadInfo.id == 1 {
hasGeneral = true
}
threadCount += 1
if threadCount > 1 {
break loop
}
}
} else {
break loop
}
case .ArchiveIntro, .HeaderEntry, .AdditionalCategory:
break
}
}
isEmptyState = .notEmpty(containsChats: containsChats)
isEmptyState = .notEmpty(containsChats: containsChats, onlyGeneralThread: hasGeneral && threadCount == 1)
}
var insertedPeerIds: [EnginePeer.Id] = []

View File

@ -334,6 +334,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
let enabled: Bool
let highlighted: Bool
public let selectable: Bool
let animateFirstAvatarTransition: Bool
public let sectionId: ItemListSectionId
let action: (() -> Void)?
let setPeerIdWithRevealedOptions: (EnginePeer.Id?, EnginePeer.Id?) -> Void
@ -350,7 +351,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
let displayDecorations: Bool
let disableInteractiveTransitionIfNecessary: Bool
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, threadInfo: EngineMessageHistoryThread.Info? = nil, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: EnginePeer.Presence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, highlighted: Bool = false, selectable: Bool, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, noCorners: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil, displayDecorations: Bool = true, disableInteractiveTransitionIfNecessary: Bool = false) {
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, threadInfo: EngineMessageHistoryThread.Info? = nil, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: EnginePeer.Presence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, highlighted: Bool = false, selectable: Bool, animateFirstAvatarTransition: Bool = true, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, noCorners: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil, displayDecorations: Bool = true, disableInteractiveTransitionIfNecessary: Bool = false) {
self.presentationData = presentationData
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
@ -370,6 +371,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
self.enabled = enabled
self.highlighted = highlighted
self.selectable = selectable
self.animateFirstAvatarTransition = animateFirstAvatarTransition
self.sectionId = sectionId
self.action = action
self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions
@ -1289,6 +1291,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
if item.peer.isDeleted {
overrideImage = .deletedIcon
}
strongSelf.avatarNode.imageNode.animateFirstTransition = item.animateFirstAvatarTransition
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad)
}
}

View File

@ -107,6 +107,7 @@ swift_library(
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
"//submodules/PersistentStringHash:PersistentStringHash",
"//submodules/TelegramUI/Components/NotificationPeerExceptionController",
"//submodules/TelegramUI/Components/ChatTimerScreen",
],
visibility = [
"//visibility:public",

View File

@ -30,7 +30,6 @@ public final class ChatTimerScreen: ViewController {
private var animatedIn = false
private let context: AccountContext
private let peerId: PeerId
private let style: ChatTimerScreenStyle
private let mode: ChatTimerScreenMode
private let currentTime: Int32?
@ -40,9 +39,8 @@ public final class ChatTimerScreen: ViewController {
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, style: ChatTimerScreenStyle, mode: ChatTimerScreenMode = .sendTimer, currentTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, style: ChatTimerScreenStyle, mode: ChatTimerScreenMode = .sendTimer, currentTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
self.context = context
self.peerId = peerId
self.style = style
self.mode = mode
self.currentTime = currentTime

View File

@ -1,112 +0,0 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 4.199997 1.907497 cm
1.000000 1.000000 1.000000 scn
0.000000 12.592503 m
0.000000 18.498425 l
0.000000 19.400753 0.000000 19.851917 0.142046 20.246223 c
0.267605 20.594761 0.472146 20.909500 0.739650 21.165794 c
1.042280 21.455738 1.454996 21.639168 2.280427 22.006027 c
7.680857 24.406218 l
8.829472 24.916712 9.403779 25.171961 10.000750 25.272770 c
10.529839 25.362116 11.070163 25.362116 11.599251 25.272770 c
12.196222 25.171961 12.770529 24.916714 13.919143 24.406218 c
19.319572 22.006027 l
20.145004 21.639168 20.557720 21.455738 20.860350 21.165794 c
21.127855 20.909500 21.332396 20.594761 21.457954 20.246223 c
21.600000 19.851917 21.600000 19.400753 21.600000 18.498423 c
21.600000 12.592503 l
21.600000 4.965950 14.783872 1.126137 11.981524 -0.130262 c
11.981509 -0.130268 l
11.662810 -0.273155 11.503456 -0.344597 11.202699 -0.395788 c
10.999416 -0.430387 10.600584 -0.430387 10.397303 -0.395788 c
10.096541 -0.344597 9.937186 -0.273151 9.618476 -0.130262 c
6.816126 1.126139 0.000000 4.965952 0.000000 12.592503 c
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 7.199997 8.199517 cm
0.274510 0.294118 0.298039 scn
0.989925 6.863103 m
4.855387 8.547224 7.432969 9.657499 8.722668 10.193930 c
12.405018 11.725546 13.170178 11.991605 13.668900 12.000390 c
13.778588 12.002322 14.023846 11.975138 14.182714 11.846229 c
14.316857 11.737380 14.353765 11.590341 14.371427 11.487141 c
14.389089 11.383940 14.411081 11.148846 14.393599 10.965151 c
14.194051 8.868484 13.330610 3.780426 12.891341 1.432123 c
12.705469 0.438469 12.339483 0.105302 11.985166 0.072697 c
11.215152 0.001839 10.630439 0.581574 9.884643 1.070453 c
8.717619 1.835451 8.058326 2.311666 6.925527 3.058163 c
5.616383 3.920870 6.465046 4.395029 7.211124 5.169937 c
7.406376 5.372734 10.799074 8.458654 10.864739 8.738596 c
10.872952 8.773607 10.880574 8.904113 10.803043 8.973024 c
10.725513 9.041937 10.611083 9.018372 10.528507 8.999630 c
10.411459 8.973064 8.547124 7.740808 4.935502 5.302861 c
4.406317 4.939481 3.926997 4.762432 3.497544 4.771710 c
3.024105 4.781938 2.113399 5.039400 1.436383 5.259471 c
0.605995 5.529397 -0.053981 5.672108 0.003489 6.130527 c
0.033422 6.369299 0.362234 6.613492 0.989925 6.863103 c
h
f*
n
Q
endstream
endobj
3 0 obj
2275
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002365 00000 n
0000002388 00000 n
0000002561 00000 n
0000002635 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2694
%%EOF

View File

@ -1,12 +0,0 @@
{
"images" : [
{
"filename" : "AntiSpamTooltip.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

File diff suppressed because one or more lines are too long

View File

@ -171,7 +171,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
break
case .ignore:
return .fail
case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji:
case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji:
return .waitForSingleTap
}
}

View File

@ -3590,6 +3590,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}, displayPremiumStickerTooltip: { [weak self] file, message in
self?.displayPremiumStickerTooltip(file: file, message: message)
}, displayEmojiPackTooltip: { [weak self] file, message in
self?.displayEmojiPackTooltip(file: file, message: message)
}, openPeerContextMenu: { [weak self] peer, messageId, node, rect, gesture in
guard let strongSelf = self else {
return
@ -13581,6 +13583,56 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
private func displayEmojiPackTooltip(file: TelegramMediaFile, message: Message) {
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
guard !premiumConfiguration.isPremiumDisabled else {
return
}
var currentOverlayController: UndoOverlayController?
self.window?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
currentOverlayController = controller
}
})
self.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
currentOverlayController = controller
}
return true
})
if let currentOverlayController = currentOverlayController {
if case .sticker = currentOverlayController.content {
return
}
currentOverlayController.dismissWithCommitAction()
}
var stickerPackReference: StickerPackReference?
for attribute in file.attributes {
if case let .CustomEmoji(_, _, packReference) = attribute {
stickerPackReference = packReference
break
}
}
if let stickerPackReference = stickerPackReference {
let _ = (self.context.engine.stickers.loadedStickerPack(reference: stickerPackReference, forceActualized: false)
|> deliverOnMainQueue).start(next: { [weak self] stickerPack in
if let strongSelf = self, case let .result(info, _, _) = stickerPack {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, title: nil, text: strongSelf.presentationData.strings.Stickers_EmojiPackInfoText(info.title).string, undoText: strongSelf.presentationData.strings.Stickers_PremiumPackView, customAction: nil), elevatedLayout: false, action: { [weak self] action in
if let strongSelf = self, action == .undo {
strongSelf.presentEmojiList(references: [stickerPackReference])
}
return false
}), in: .current)
}
})
}
}
private func displayDiceTooltip(dice: TelegramMediaDice) {
guard let _ = dice.value else {
return
@ -17171,10 +17223,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
private func presentTimerPicker(style: ChatTimerScreenStyle = .default, selectedTime: Int32? = nil, dismissByTapOutside: Bool = true, completion: @escaping (Int32) -> Void) {
guard case let .peer(peerId) = self.chatLocation else {
return
}
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peerId, style: style, currentTime: selectedTime, dismissByTapOutside: dismissByTapOutside, completion: { time in
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, style: style, currentTime: selectedTime, dismissByTapOutside: dismissByTapOutside, completion: { time in
completion(time)
})
self.chatDisplayNode.dismissInput()
@ -17231,7 +17280,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, peerId: peer.id, style: .default, mode: .autoremove, currentTime: self.presentationInterfaceState.autoremoveTimeout, dismissByTapOutside: true, completion: { [weak self] value in
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, style: .default, mode: .autoremove, currentTime: self.presentationInterfaceState.autoremoveTimeout, dismissByTapOutside: true, completion: { [weak self] value in
guard let strongSelf = self else {
return
}

View File

@ -134,6 +134,7 @@ public final class ChatControllerInteraction {
let displayDiceTooltip: (TelegramMediaDice) -> Void
let animateDiceSuccess: (Bool, Bool) -> Void
let displayPremiumStickerTooltip: (TelegramMediaFile, Message) -> Void
let displayEmojiPackTooltip: (TelegramMediaFile, Message) -> Void
let openPeerContextMenu: (Peer, MessageId?, ASDisplayNode, CGRect, ContextGesture?) -> Void
let openMessageReplies: (MessageId, Bool, Bool) -> Void
let openReplyThreadOriginalMessage: (Message) -> Void
@ -243,6 +244,7 @@ public final class ChatControllerInteraction {
displayDiceTooltip: @escaping (TelegramMediaDice) -> Void,
animateDiceSuccess: @escaping (Bool, Bool) -> Void,
displayPremiumStickerTooltip: @escaping (TelegramMediaFile, Message) -> Void,
displayEmojiPackTooltip: @escaping (TelegramMediaFile, Message) -> Void,
openPeerContextMenu: @escaping (Peer, MessageId?, ASDisplayNode, CGRect, ContextGesture?) -> Void,
openMessageReplies: @escaping (MessageId, Bool, Bool) -> Void,
openReplyThreadOriginalMessage: @escaping (Message) -> Void,
@ -335,6 +337,7 @@ public final class ChatControllerInteraction {
self.displayDiceTooltip = displayDiceTooltip
self.animateDiceSuccess = animateDiceSuccess
self.displayPremiumStickerTooltip = displayPremiumStickerTooltip
self.displayEmojiPackTooltip = displayEmojiPackTooltip
self.openPeerContextMenu = openPeerContextMenu
self.openMessageReplies = openMessageReplies
self.openReplyThreadOriginalMessage = openReplyThreadOriginalMessage

View File

@ -2126,7 +2126,17 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
if let item = self.item, self.imageNode.frame.contains(location) {
if let item = self.item, let emojiString = self.emojiString, emojiString.emojis.count > 1 {
if let (_, attributes) = self.textNode.textNode.attributesAtPoint(self.view.convert(location, to: self.textNode.textNode.view)) {
for (_, attribute) in attributes {
if let attribute = attribute as? ChatTextInputTextCustomEmojiAttribute, let file = attribute.file {
return .optionalAction({
item.controllerInteraction.displayEmojiPackTooltip(file, item.message)
})
}
}
}
} else if let item = self.item, self.imageNode.frame.contains(location) {
let emojiTapAction: (Bool) -> InternalBubbleTapAction? = { shouldPlay in
let beatingHearts: [UInt32] = [0x2764, 0x1F90E, 0x1F9E1, 0x1F499, 0x1F49A, 0x1F49C, 0x1F49B, 0x1F5A4, 0x1F90D]
let heart = 0x2764

View File

@ -123,6 +123,7 @@ enum ChatMessageBubbleContentTapAction {
case openPollResults(Data)
case copy(String)
case largeEmoji(String, String?, TelegramMediaFile)
case customEmoji(TelegramMediaFile)
}
final class ChatMessageBubbleContentItem {

View File

@ -964,7 +964,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
break
case .ignore:
return .fail
case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji:
case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .theme, .call, .openMessage, .timecode, .bankCard, .tooltip, .openPollResults, .copy, .largeEmoji, .customEmoji:
return .waitForSingleTap
}
}
@ -3742,6 +3742,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
item.controllerInteraction.openLargeEmojiInfo(emoji, fitz, file)
})
}
case let .customEmoji(file):
if let item = self.item {
return .optionalAction({
item.controllerInteraction.displayEmojiPackTooltip(file, item.message)
})
}
}
}
return nil
@ -3826,6 +3832,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
break
case .largeEmoji:
break
case .customEmoji:
break
}
}
if let tapMessage = tapMessage {

View File

@ -580,6 +580,8 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
return .bankCard(bankCard)
} else if let pre = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Pre)] as? String {
return .copy(pre)
} else if let emoji = attributes[NSAttributedString.Key(rawValue: ChatTextInputAttributes.customEmoji.rawValue)] as? ChatTextInputTextCustomEmojiAttribute, let file = emoji.file {
return .customEmoji(file)
} else {
if let item = self.item, item.message.text.count == 1, !item.presentationData.largeEmoji {
let (emoji, fitz) = item.message.text.basicEmoji

View File

@ -535,6 +535,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _, _ in
}, displayPremiumStickerTooltip: { _, _ in
}, displayEmojiPackTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in
@ -789,7 +790,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
if peer.id == antiSpamBotConfiguration.antiSpamBotId {
self.dismissAllTooltips()
self.presentController(UndoOverlayController(presentationData: self.presentationData, content: .image(image: UIImage(bundleImageName: "Chat/AntiSpamTooltipIcon")!, title: self.presentationData.strings.Group_AdminLog_AntiSpamTitle, text: self.presentationData.strings.Group_AdminLog_AntiSpamText, undo: false), elevatedLayout: true, action: { [weak self] action in
self.presentController(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_antispam", scale: 0.066, colors: [:], title: self.presentationData.strings.Group_AdminLog_AntiSpamTitle, text: self.presentationData.strings.Group_AdminLog_AntiSpamText, customUndoText: nil), elevatedLayout: true, action: { [weak self] action in
if let strongSelf = self {
if case .info = action {
let _ = strongSelf.getNavigationController()?.popViewController(animated: true)
@ -923,8 +924,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|> deliverOnMainQueue).start(), forKey: message.id)
Queue.mainQueue().after(0.2, {
let content: UndoOverlayContent = .image(image: UIImage(bundleImageName: "Chat/AntiSpamTooltipIcon")!, title: nil, text: strongSelf.presentationData.strings.Group_AdminLog_AntiSpamFalsePositiveReportedText, undo: false)
strongSelf.presentController(UndoOverlayController(presentationData: strongSelf.presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), .current, nil)
strongSelf.presentController(UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_antispam", scale: 0.066, colors: [:], title: nil, text: strongSelf.presentationData.strings.Group_AdminLog_AntiSpamFalsePositiveReportedText, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), .current, nil)
})
}
})), at: 0

View File

@ -529,7 +529,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
if let peer = self.entry.peers[self.entry.event.peerId] {
peers[peer.id] = peer
}
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(bitPattern: self.entry.stableId)), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
let message = Message(stableId: self.entry.stableId, stableVersion: 0, id: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: message.id.id), globallyUniqueId: self.entry.event.id, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
return ChatMessageItem(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
}
case .participantJoin, .participantLeave:

View File

@ -146,6 +146,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _, _ in
}, displayPremiumStickerTooltip: { _, _ in
}, displayEmojiPackTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in

View File

@ -139,6 +139,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _, _ in
}, displayPremiumStickerTooltip: { _, _ in
}, displayEmojiPackTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in

View File

@ -23,6 +23,7 @@ final class PeerInfoScreenMemberItem: PeerInfoScreenItem {
let enclosingPeer: Peer?
let member: PeerInfoMember
let badge: String?
let isAccount: Bool
let action: ((PeerInfoScreenMemberItemAction) -> Void)?
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
@ -32,6 +33,7 @@ final class PeerInfoScreenMemberItem: PeerInfoScreenItem {
enclosingPeer: Peer?,
member: PeerInfoMember,
badge: String? = nil,
isAccount: Bool,
action: ((PeerInfoScreenMemberItemAction) -> Void)?,
contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil
) {
@ -40,6 +42,7 @@ final class PeerInfoScreenMemberItem: PeerInfoScreenItem {
self.enclosingPeer = enclosingPeer
self.member = member
self.badge = badge
self.isAccount = isAccount
self.action = action
self.contextAction = contextAction
}
@ -188,7 +191,7 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode {
itemText = .presence
}
let peerItem = ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: item.context, peer: EnginePeer(item.member.peer), height: itemHeight, presence: item.member.presence.flatMap(EnginePeer.Presence.init), text: itemText, label: itemLabel, editing: ItemListPeerItemEditing(editable: !options.isEmpty, editing: false, revealed: nil), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: false, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
let peerItem = ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: item.context, peer: EnginePeer(item.member.peer), height: itemHeight, presence: item.member.presence.flatMap(EnginePeer.Presence.init), text: itemText, label: itemLabel, editing: ItemListPeerItemEditing(editable: !options.isEmpty, editing: false, revealed: nil), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: false, animateFirstAvatarTransition: !item.isAccount, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
}, removePeer: { _ in

View File

@ -322,6 +322,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
}
var tapped: (() -> Void)?
var emojiTapped: (() -> Void)?
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
private var isFirstAvatarLoading = true
@ -365,6 +366,12 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
self.tapped?()
}
}
@objc private func emojiTapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.emojiTapped?()
}
}
func updateTransitionFraction(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) {
if let videoNode = self.videoNode {
@ -409,6 +416,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
self.containerNode.isGestureEnabled = false
}
self.avatarNode.imageNode.animateFirstTransition = !isSettings
self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: self.isFirstAvatarLoading, displayDimensions: CGSize(width: avatarSize, height: avatarSize), storeUnrounded: true)
if let threadInfo = threadInfo {
@ -443,7 +451,9 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
containerSize: CGSize(width: avatarSize, height: avatarSize)
)
if let iconComponentView = iconView.view {
iconComponentView.isUserInteractionEnabled = true
if iconComponentView.superview == nil {
iconComponentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.emojiTapGesture(_:))))
self.avatarNode.view.superview?.addSubview(iconComponentView)
}
iconComponentView.frame = CGRect(origin: CGPoint(), size: CGSize(width: avatarSize, height: avatarSize))
@ -988,6 +998,8 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
} else {
if let result = self.avatarContainerNode.avatarNode.view.hitTest(self.view.convert(point, to: self.avatarContainerNode.avatarNode.view), with: event) {
return result
} else if let result = self.avatarContainerNode.iconView?.view?.hitTest(self.view.convert(point, to: self.avatarContainerNode.iconView?.view), with: event) {
return result
}
}
@ -2108,6 +2120,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
var displayAvatarContextMenu: ((ASDisplayNode, ContextGesture?) -> Void)?
var displayCopyContextMenu: ((ASDisplayNode, Bool, Bool) -> Void)?
var displayEmojiPackTooltip: (() -> Void)?
var displayPremiumIntro: ((UIView, PeerEmojiStatus?, Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError>, Bool) -> Void)?
@ -2241,6 +2254,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.avatarListNode.avatarContainerNode.contextAction = { [weak self] node, gesture in
self?.displayAvatarContextMenu?(node, gesture)
}
self.avatarListNode.avatarContainerNode.emojiTapped = { [weak self] in
self?.displayEmojiPackTooltip?()
}
self.editingContentNode.avatarNode.tapped = { [weak self] confirm in
self?.initiateAvatarExpansion(gallery: true, first: true)

View File

@ -81,6 +81,7 @@ import ForumCreateTopicScreen
import NotificationExceptionsScreen
import ChatTimerScreen
import NotificationPeerExceptionController
import StickerPackPreviewUI
protocol PeerInfoScreenItem: AnyObject {
var id: AnyHashable { get }
@ -705,7 +706,7 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
if !settings.accountsAndPeers.isEmpty {
for (peerAccountContext, peer, badgeCount) in settings.accountsAndPeers {
let member: PeerInfoMember = .account(peer: RenderedPeer(peer: peer._asPeer()))
items[.accounts]!.append(PeerInfoScreenMemberItem(id: member.id, context: context.sharedContext.makeTempAccountContext(account: peerAccountContext.account), enclosingPeer: nil, member: member, badge: badgeCount > 0 ? "\(compactNumericCountString(Int(badgeCount), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))" : nil, action: { action in
items[.accounts]!.append(PeerInfoScreenMemberItem(id: member.id, context: context.sharedContext.makeTempAccountContext(account: peerAccountContext.account), enclosingPeer: nil, member: member, badge: badgeCount > 0 ? "\(compactNumericCountString(Int(badgeCount), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))" : nil, isAccount: true, action: { action in
switch action {
case .open:
interaction.switchToAccount(peerAccountContext.account.id)
@ -1297,7 +1298,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
for member in memberList {
let isAccountPeer = member.id == context.account.peerId
items[.peerMembers]!.append(PeerInfoScreenMemberItem(id: member.id, context: context, enclosingPeer: peer, member: member, action: isAccountPeer ? nil : { action in
items[.peerMembers]!.append(PeerInfoScreenMemberItem(id: member.id, context: context, enclosingPeer: peer, member: member, isAccount: false, action: isAccountPeer ? nil : { action in
switch action {
case .open:
interaction.openPeerInfo(member.peer, true)
@ -2600,6 +2601,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _, _ in
}, displayPremiumStickerTooltip: { _, _ in
}, displayEmojiPackTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in
@ -3445,6 +3447,40 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
strongSelf.controller?.presentInGlobalOverlay(contextController)
}
self.headerNode.displayEmojiPackTooltip = { [weak self] in
guard let strongSelf = self, let threadData = strongSelf.data?.threadData else {
return
}
if let icon = threadData.info.icon, icon != 0 {
let _ = (strongSelf.context.engine.stickers.resolveInlineStickers(fileIds: [icon])
|> deliverOnMainQueue).start(next: { [weak self] files in
if let file = files.first?.value {
var stickerPackReference: StickerPackReference?
for attribute in file.attributes {
if case let .CustomEmoji(_, _, packReference) = attribute {
stickerPackReference = packReference
break
}
}
if let stickerPackReference = stickerPackReference {
let _ = (strongSelf.context.engine.stickers.loadedStickerPack(reference: stickerPackReference, forceActualized: false)
|> deliverOnMainQueue).start(next: { [weak self] stickerPack in
if let strongSelf = self, case let .result(info, _, _) = stickerPack {
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, title: nil, text: strongSelf.presentationData.strings.Stickers_EmojiPackInfoText(info.title).string, undoText: strongSelf.presentationData.strings.Stickers_PremiumPackView, customAction: nil), elevatedLayout: false, action: { [weak self] action in
if let strongSelf = self, action == .undo {
strongSelf.presentEmojiList(packReference: stickerPackReference)
}
return false
}), in: .current)
}
})
}
}
})
}
}
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peerId.namespace) {
self.displayAsPeersPromise.set(context.engine.calls.cachedGroupCallDisplayAsAvailablePeers(peerId: peerId))
}
@ -4987,7 +5023,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
private func openAutoremove(currentValue: Int32?) {
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, style: .default, mode: .autoremove, currentTime: currentValue, dismissByTapOutside: true, completion: { [weak self] value in
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, style: .default, mode: .autoremove, currentTime: currentValue, dismissByTapOutside: true, completion: { [weak self] value in
guard let strongSelf = self else {
return
}
@ -5016,7 +5052,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
private func openCustomMute() {
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak self] value in
let controller = ChatTimerScreen(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak self] value in
guard let strongSelf = self else {
return
}
@ -7862,6 +7898,38 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
func presentEmojiList(packReference: StickerPackReference) {
guard let peerController = self.controller else {
return
}
let presentationData = self.presentationData
let navigationController = peerController.navigationController as? NavigationController
let controller = StickerPackScreen(context: self.context, updatedPresentationData: peerController.updatedPresentationData, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: navigationController, sendEmoji: nil, actionPerformed: { [weak self] actions in
guard let strongSelf = self else {
return
}
let context = strongSelf.context
if let (info, items, action) = actions.first {
let isEmoji = info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks
switch action {
case .add:
strongSelf.controller?.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedTitle : presentationData.strings.StickerPackActionInfo_AddedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string : presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: false, animateInAsReplacement: false, action: { _ in
return true
}))
case let .remove(positionInList):
strongSelf.controller?.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedText(info.title).string : presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if case .undo = action {
let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start()
}
return true
}))
}
}
})
peerController.present(controller, in: .window(.root))
}
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
@ -8231,8 +8299,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
}
private var hasQrButton = false
fileprivate func updateNavigation(transition: ContainedViewLayoutTransition, additive: Bool) {
let offsetY = self.scrollNode.view.contentOffset.y
var transition = transition
if self.isSettings, !(self.controller?.movingInHierarchy == true) {
let bottomOffsetY = max(0.0, self.scrollNode.view.contentSize.height + min(83.0, self.scrollNode.view.contentInset.bottom) - offsetY - self.scrollNode.frame.height)
@ -8294,14 +8364,24 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
transition.updateFrame(node: self.headerNode.navigationButtonContainer, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.statusBarHeight ?? 0.0), size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight)))
self.headerNode.navigationButtonContainer.isWhite = self.headerNode.isAvatarExpanded
var leftNavigationButtons: [PeerInfoHeaderNavigationButtonSpec] = []
var rightNavigationButtons: [PeerInfoHeaderNavigationButtonSpec] = []
if self.state.isEditing {
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .done, isForExpandedView: false))
} else {
if self.isSettings {
leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false))
var hasQrButton = false
if self.data?.globalSettings?.privacySettings?.phoneDiscoveryEnabled == true {
leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false))
hasQrButton = true
}
if hasQrButton != self.hasQrButton {
self.hasQrButton = hasQrButton
if !transition.isAnimated {
transition = .animated(duration: 0.2, curve: .easeInOut)
}
}
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
} else if peerInfoCanEdit(peer: self.data?.peer, chatLocation: self.chatLocation, threadData: self.data?.threadData, cachedData: self.data?.cachedData, isContact: self.data?.isContact) {

View File

@ -1333,6 +1333,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _, _ in
}, displayPremiumStickerTooltip: { _, _ in
}, displayEmojiPackTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in

View File

@ -371,7 +371,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
let emoticons = keywords.flatMap { $0.emoticons }
for emoji in emoticons {
signals.append(context.engine.stickers.searchStickers(query: emoji.basicEmoji.0)
|> take(1)
// |> take(1)
|> map { (emoji, $0) })
}
return signals

View File

@ -464,7 +464,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) {
decisionHandler(.prompt)
}
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alertController = textAlertController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, title: nil, text: message, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
completionHandler()