mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
5d25b32ac4
commit
e3498e99a1
@ -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.";
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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] = []
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -123,6 +123,7 @@ enum ChatMessageBubbleContentTapAction {
|
||||
case openPollResults(Data)
|
||||
case copy(String)
|
||||
case largeEmoji(String, String?, TelegramMediaFile)
|
||||
case customEmoji(TelegramMediaFile)
|
||||
}
|
||||
|
||||
final class ChatMessageBubbleContentItem {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -146,6 +146,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
}, displayDiceTooltip: { _ in
|
||||
}, animateDiceSuccess: { _, _ in
|
||||
}, displayPremiumStickerTooltip: { _, _ in
|
||||
}, displayEmojiPackTooltip: { _, _ in
|
||||
}, openPeerContextMenu: { _, _, _, _, _ in
|
||||
}, openMessageReplies: { _, _, _ in
|
||||
}, openReplyThreadOriginalMessage: { _ in
|
||||
|
@ -139,6 +139,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}, displayDiceTooltip: { _ in
|
||||
}, animateDiceSuccess: { _, _ in
|
||||
}, displayPremiumStickerTooltip: { _, _ in
|
||||
}, displayEmojiPackTooltip: { _, _ in
|
||||
}, openPeerContextMenu: { _, _, _, _, _ in
|
||||
}, openMessageReplies: { _, _, _ in
|
||||
}, openReplyThreadOriginalMessage: { _ in
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -1333,6 +1333,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}, displayDiceTooltip: { _ in
|
||||
}, animateDiceSuccess: { _, _ in
|
||||
}, displayPremiumStickerTooltip: { _, _ in
|
||||
}, displayEmojiPackTooltip: { _, _ in
|
||||
}, openPeerContextMenu: { _, _, _, _, _ in
|
||||
}, openMessageReplies: { _, _, _ in
|
||||
}, openReplyThreadOriginalMessage: { _ in
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user