mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
Various improvements
This commit is contained in:
parent
6e42f96c19
commit
acf905a098
@ -12065,8 +12065,8 @@ Sorry for the inconvenience.";
|
||||
"Login.EnterPhrasePlaceholder" = "Enter Phrase from SMS";
|
||||
|
||||
"Login.EnterPhraseHint" = "You can copy and paste the phrase here.";
|
||||
"Login.BackToWord" = "Back to entering word";
|
||||
"Login.BackToPhrase" = "Back to entering phrase";
|
||||
"Login.BackToWord" = "Back to entering the word";
|
||||
"Login.BackToPhrase" = "Back to entering the phrase";
|
||||
|
||||
"Login.WrongPhraseError" = "Incorrect, please try again.";
|
||||
"Login.Paste" = "Paste";
|
||||
@ -12095,3 +12095,10 @@ Sorry for the inconvenience.";
|
||||
"ChannelProfile.ToastAboutCopied" = "Copied to clipboard.";
|
||||
|
||||
"Conversation.ContextMenuBanFull" = "Ban";
|
||||
|
||||
"Channel.AdminLog.MessageManyDeleted" = "%1$@ deleted %2$@ from %3$@";
|
||||
"Channel.AdminLog.MessageManyDeletedMore" = "%1$@ deleted %2$@ from %3$@ %4$@";
|
||||
"Channel.AdminLog.MessageManyDeleted.Messages_1" = "%@ message";
|
||||
"Channel.AdminLog.MessageManyDeleted.Messages_any" = "%@ messages";
|
||||
"Channel.AdminLog.MessageManyDeleted.ShowAll" = "show all";
|
||||
"Channel.AdminLog.MessageManyDeleted.HideAll" = "hide all";
|
||||
|
@ -168,6 +168,10 @@ public final class AuthorizationSequenceCodeEntryController: ViewController {
|
||||
self.controllerNode.animateSuccess()
|
||||
}
|
||||
|
||||
public func selectIncorrectPart() {
|
||||
self.controllerNode.selectIncorrectPart()
|
||||
}
|
||||
|
||||
public func animateError(text: String) {
|
||||
self.hapticFeedback.error()
|
||||
self.controllerNode.animateError(text: text)
|
||||
|
@ -405,7 +405,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
|
||||
func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool, hasPreviousCode: Bool, previousIsPhrase: Bool) {
|
||||
self.codeType = codeType
|
||||
self.phoneNumber = number
|
||||
self.phoneNumber = number.replacingOccurrences(of: " ", with: "\u{00A0}").replacingOccurrences(of: "-", with: "\u{2011}")
|
||||
self.email = email
|
||||
self.hasPreviousCode = hasPreviousCode
|
||||
self.previousIsPhrase = previousIsPhrase
|
||||
@ -881,6 +881,21 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF
|
||||
}
|
||||
}
|
||||
|
||||
func selectIncorrectPart() {
|
||||
switch self.codeType {
|
||||
case .word:
|
||||
self.textField.textField.selectAll(nil)
|
||||
case let .phrase(startsWith):
|
||||
if let startsWith, let fromPosition = self.textField.textField.position(from: self.textField.textField.beginningOfDocument, offset: startsWith.count + 1) {
|
||||
self.textField.textField.selectedTextRange = self.textField.textField.textRange(from: fromPosition, to: self.textField.textField.endOfDocument)
|
||||
} else {
|
||||
self.textField.textField.selectAll(nil)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func animateError(text: String) {
|
||||
let errorOriginY: CGFloat
|
||||
let errorOriginOffset: CGFloat
|
||||
|
@ -476,7 +476,6 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
controller?.inProgress = false
|
||||
switch result {
|
||||
case let .signUp(data):
|
||||
if let (termsOfService, explicit) = termsOfService, explicit {
|
||||
@ -543,6 +542,7 @@ public final class AuthorizationSequenceController: NavigationController, ASAuth
|
||||
switch type {
|
||||
case .word, .phrase:
|
||||
text = strongSelf.presentationData.strings.Login_WrongPhraseError
|
||||
controller.selectIncorrectPart()
|
||||
default:
|
||||
text = strongSelf.presentationData.strings.Login_WrongCodeError
|
||||
}
|
||||
|
@ -684,7 +684,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
|
||||
tokenString = tokenString.replacingOccurrences(of: "/", with: "_")
|
||||
let urlString = "tg://login?token=\(tokenString)"
|
||||
let _ = (qrCode(string: urlString, color: .black, backgroundColor: .white, icon: .none)
|
||||
|> deliverOnMainQueue).startStrict(next: { _, generate in
|
||||
|> deliverOnMainQueue).startStandalone(next: { _, generate in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
@ -83,7 +83,11 @@ public final class ChatMessageLiveLocationTimerNode: ASDisplayNode {
|
||||
let intRemaining = Int32(remaining)
|
||||
if intRemaining > 60 * 60 {
|
||||
let hours = Int32(round(remaining / (60.0 * 60.0)))
|
||||
string = strings.Map_LiveLocationShortHour("\(hours)").string
|
||||
if hours > 99 {
|
||||
string = "99+"
|
||||
} else {
|
||||
string = strings.Map_LiveLocationShortHour("\(hours)").string
|
||||
}
|
||||
} else {
|
||||
let minutes = Int32(round(remaining / (60.0)))
|
||||
string = "\(minutes)"
|
||||
|
@ -321,7 +321,14 @@ final class LocationLiveListItemNode: ListViewItemNode {
|
||||
}
|
||||
|
||||
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
if currentTimestamp < item.message.timestamp + liveBroadcastingTimeout {
|
||||
let remainingTime: Int32
|
||||
if liveBroadcastingTimeout == liveLocationIndefinitePeriod {
|
||||
remainingTime = liveLocationIndefinitePeriod
|
||||
} else {
|
||||
remainingTime = max(0, item.message.timestamp + liveBroadcastingTimeout - currentTimestamp)
|
||||
}
|
||||
|
||||
if remainingTime > 0 {
|
||||
let timerNode: ChatMessageLiveLocationTimerNode
|
||||
if let current = strongSelf.timerNode {
|
||||
timerNode = current
|
||||
@ -331,7 +338,7 @@ final class LocationLiveListItemNode: ListViewItemNode {
|
||||
strongSelf.timerNode = timerNode
|
||||
}
|
||||
let timerSize = CGSize(width: 28.0, height: 28.0)
|
||||
timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: Double(item.message.timestamp), timeout: Double(liveBroadcastingTimeout), strings: item.presentationData.strings)
|
||||
timerNode.update(backgroundColor: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.4), foregroundColor: item.presentationData.theme.list.itemAccentColor, textColor: item.presentationData.theme.list.itemAccentColor, beginTimestamp: Double(item.message.timestamp), timeout: Int32(liveBroadcastingTimeout) == liveLocationIndefinitePeriod ? -1.0 : Double(liveBroadcastingTimeout), strings: item.presentationData.strings)
|
||||
timerNode.frame = CGRect(origin: CGPoint(x: contentSize.width - 16.0 - timerSize.width, y: 14.0), size: timerSize)
|
||||
} else if let timerNode = strongSelf.timerNode {
|
||||
strongSelf.timerNode = nil
|
||||
|
@ -838,8 +838,12 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
let diameter: CGFloat = self.buttonHeight - 22.0
|
||||
let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(buttonOffset + (buttonWidth - diameter) / 2.0), y: floorToScreenPixels((self.buttonHeight - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter))
|
||||
progressNode.frame = progressFrame
|
||||
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.theme.foregroundColor, diameter: diameter, lineWidth: 3.0)
|
||||
|
||||
|
||||
if !self.isEnabled, let disabledForegroundColor = self.theme.disabledForegroundColor {
|
||||
progressNode.image = generateIndefiniteActivityIndicatorImage(color: disabledForegroundColor, diameter: diameter, lineWidth: 3.0)
|
||||
} else {
|
||||
progressNode.image = generateIndefiniteActivityIndicatorImage(color: self.theme.foregroundColor, diameter: diameter, lineWidth: 3.0)
|
||||
}
|
||||
self.addSubnode(progressNode)
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ public class LocationBroadcastActionSheetItemNode: ActionSheetItemNode {
|
||||
|
||||
self.avatarNode.setPeer(context: item.context, theme: (item.context.sharedContext.currentPresentationData.with { $0 }).theme, peer: EnginePeer(item.peer))
|
||||
|
||||
self.timerNode.update(backgroundColor: self.theme.controlAccentColor.withAlphaComponent(0.4), foregroundColor: self.theme.controlAccentColor, textColor: self.theme.controlAccentColor, beginTimestamp: item.beginTimestamp, timeout: item.timeout, strings: item.strings)
|
||||
self.timerNode.update(backgroundColor: self.theme.controlAccentColor.withAlphaComponent(0.4), foregroundColor: self.theme.controlAccentColor, textColor: self.theme.controlAccentColor, beginTimestamp: item.beginTimestamp, timeout: Int32(item.timeout) == liveLocationIndefinitePeriod ? -1.0 : item.timeout, strings: item.strings)
|
||||
}
|
||||
|
||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
|
@ -186,7 +186,28 @@ func channelAdminLogEvents(accountPeerId: PeerId, postbox: Postbox, network: Net
|
||||
if let threadId = message.threadId, let threadInfo = transaction.getMessageHistoryThreadInfo(peerId: message.id.peerId, threadId: threadId) {
|
||||
associatedThreadInfo = postbox.seedConfiguration.decodeMessageThreadInfo(threadInfo.data)
|
||||
}
|
||||
return locallyRenderedMessage(message: message, peers: peers, associatedThreadInfo: associatedThreadInfo)
|
||||
var associatedMessages: SimpleDictionary<MessageId, Message> = SimpleDictionary()
|
||||
if let replyAttribute = message.attributes.first(where: { $0 is ReplyMessageAttribute }) as? ReplyMessageAttribute {
|
||||
var foundDeletedReplyMessage = false
|
||||
for event in apiEvents {
|
||||
switch event {
|
||||
case let .channelAdminLogEvent(_, _, _, apiAction):
|
||||
switch apiAction {
|
||||
case let .channelAdminLogEventActionDeleteMessage(apiMessage):
|
||||
if let messageId = apiMessage.id(namespace: Namespaces.Message.Cloud), messageId == replyAttribute.messageId, let message = StoreMessage(apiMessage: apiMessage, accountPeerId: accountPeerId, peerIsForum: peer.isForum), let replyMessage = locallyRenderedMessage(message: message, peers: peers, associatedThreadInfo: associatedThreadInfo) {
|
||||
associatedMessages[replyMessage.id] = replyMessage
|
||||
foundDeletedReplyMessage = true
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundDeletedReplyMessage, let replyMessage = transaction.getMessage(replyAttribute.messageId) {
|
||||
associatedMessages[replyMessage.id] = replyMessage
|
||||
}
|
||||
}
|
||||
return locallyRenderedMessage(message: message, peers: peers, associatedThreadInfo: associatedThreadInfo, associatedMessages: associatedMessages)
|
||||
}
|
||||
|
||||
for event in apiEvents {
|
||||
|
@ -211,7 +211,7 @@ func messagesIdsGroupedByPeerId(_ ids: [MessageAndThreadId]) -> [PeerAndThreadId
|
||||
return dict
|
||||
}
|
||||
|
||||
func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer], associatedThreadInfo: Message.AssociatedThreadInfo? = nil) -> Message? {
|
||||
func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer], associatedThreadInfo: Message.AssociatedThreadInfo? = nil, associatedMessages: SimpleDictionary<MessageId, Message> = SimpleDictionary()) -> Message? {
|
||||
guard case let .Id(id) = message.id else {
|
||||
return nil
|
||||
}
|
||||
@ -264,7 +264,7 @@ func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer], associ
|
||||
let second = UInt32(hashValue & 0xffffffff)
|
||||
let stableId = first &+ second
|
||||
|
||||
return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, customTags: [], forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: associatedThreadInfo, associatedStories: [:])
|
||||
return Message(stableId: stableId, stableVersion: 0, id: id, globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: message.threadId, timestamp: message.timestamp, flags: MessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, customTags: [], forwardInfo: forwardInfo, author: author, text: message.text, attributes: message.attributes, media: message.media, peers: messagePeers, associatedMessages: associatedMessages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: associatedThreadInfo, associatedStories: [:])
|
||||
}
|
||||
|
||||
func locallyRenderedMessage(message: StoreMessage, peers: AccumulatedPeers, associatedThreadInfo: Message.AssociatedThreadInfo? = nil) -> Message? {
|
||||
|
@ -27,6 +27,8 @@ private func attributedServiceMessageString(theme: ChatPresentationThemeData, st
|
||||
}
|
||||
|
||||
public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
public var expandHighlightingNode: LinkHighlightingNode?
|
||||
|
||||
public let labelNode: TextNodeWithEntities
|
||||
private var dustNode: InvisibleInkDustNode?
|
||||
public var backgroundNode: WallpaperBubbleBackgroundNode?
|
||||
@ -364,6 +366,25 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0)
|
||||
|
||||
|
||||
if var rect = strongSelf.labelNode.textNode.cachedLayout?.allAttributeRects(name: TelegramTextAttributes.Button).first?.1 {
|
||||
rect = rect.insetBy(dx: -2.0, dy: 2.0).offsetBy(dx: 0.0, dy: 1.0 - UIScreenPixel)
|
||||
let highlightNode: LinkHighlightingNode
|
||||
if let current = strongSelf.expandHighlightingNode {
|
||||
highlightNode = current
|
||||
} else {
|
||||
highlightNode = LinkHighlightingNode(color: UIColor(rgb: 0x000000, alpha: 0.1))
|
||||
highlightNode.outerRadius = 7.5
|
||||
strongSelf.insertSubnode(highlightNode, belowSubnode: strongSelf.labelNode.textNode)
|
||||
strongSelf.expandHighlightingNode = highlightNode
|
||||
}
|
||||
highlightNode.frame = strongSelf.labelNode.textNode.frame
|
||||
highlightNode.updateRects([rect])
|
||||
} else {
|
||||
strongSelf.expandHighlightingNode?.removeFromSupernode()
|
||||
strongSelf.expandHighlightingNode = nil
|
||||
}
|
||||
|
||||
if let (offset, image) = backgroundMaskImage {
|
||||
if item.context.sharedContext.energyUsageSettings.fullTranslucency {
|
||||
if strongSelf.backgroundNode == nil {
|
||||
|
@ -304,6 +304,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
case let .eventLogPreviousLink(previousMessage):
|
||||
result.append((previousMessage, ChatMessageEventLogPreviousLinkContentNode.self, ChatMessageEntryAttributes(), BubbleItemAttributes(isAttachment: false, neighborType: .text, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
case .eventLogGroupedMessages:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -2483,7 +2485,25 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
var maxContentWidth: CGFloat = headerSize.width
|
||||
|
||||
var actionButtonsFinalize: ((CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode))?
|
||||
if let replyMarkup = replyMarkup, !item.presentationData.isPreview {
|
||||
if let additionalContent = item.additionalContent, case let .eventLogGroupedMessages(messages, hasButton) = additionalContent, hasButton {
|
||||
let (minWidth, buttonsLayout) = actionButtonsLayout(
|
||||
item.context,
|
||||
item.presentationData.theme,
|
||||
item.presentationData.chatBubbleCorners,
|
||||
item.presentationData.strings,
|
||||
item.controllerInteraction.presentationContext.backgroundNode,
|
||||
ReplyMarkupMessageAttribute(
|
||||
rows: [
|
||||
ReplyMarkupRow(
|
||||
buttons: [ReplyMarkupButton(title: "Show \(messages.count - 1) More Messages", titleWhenForwarded: nil, action: .callback(requiresPassword: false, data: MemoryBuffer(data: Data())))]
|
||||
)
|
||||
],
|
||||
flags: [],
|
||||
placeholder: nil
|
||||
), item.message, maximumNodeWidth)
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
actionButtonsFinalize = buttonsLayout
|
||||
} else if let replyMarkup = replyMarkup, !item.presentationData.isPreview {
|
||||
let (minWidth, buttonsLayout) = actionButtonsLayout(item.context, item.presentationData.theme, item.presentationData.chatBubbleCorners, item.presentationData.strings, item.controllerInteraction.presentationContext.backgroundNode, replyMarkup, item.message, maximumNodeWidth)
|
||||
maxContentWidth = max(maxContentWidth, minWidth)
|
||||
actionButtonsFinalize = buttonsLayout
|
||||
@ -2966,6 +2986,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
return
|
||||
}
|
||||
|
||||
if let additionalContent = item.additionalContent, case .eventLogGroupedMessages = additionalContent {
|
||||
applyInfo.setInvertOffsetDirection()
|
||||
}
|
||||
|
||||
let themeUpdated = strongSelf.appliedItem?.presentationData.theme.theme !== item.presentationData.theme.theme
|
||||
let previousContextFrame = strongSelf.mainContainerNode.frame
|
||||
strongSelf.mainContainerNode.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||
|
@ -83,6 +83,7 @@ public enum ChatMessageItemAdditionalContent {
|
||||
case eventLogPreviousMessage(Message)
|
||||
case eventLogPreviousDescription(Message)
|
||||
case eventLogPreviousLink(Message)
|
||||
case eventLogGroupedMessages([Message], Bool)
|
||||
}
|
||||
|
||||
public enum ChatMessageMerge: Int32 {
|
||||
|
@ -17,6 +17,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/ChatControllerInteraction",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatHistoryEntry",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemView",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItem",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatNavigationButton",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatLoadingNode",
|
||||
|
@ -76,6 +76,13 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
|
||||
private let navigationActionDisposable = MetaDisposable()
|
||||
|
||||
private var expandedDeletedMessages = Set<EngineMessage.Id>() {
|
||||
didSet {
|
||||
self.expandedDeletedMessagesPromise.set(self.expandedDeletedMessages)
|
||||
}
|
||||
}
|
||||
private let expandedDeletedMessagesPromise = ValuePromise<Set<EngineMessage.Id>>(Set())
|
||||
|
||||
private var isLoading: Bool = false {
|
||||
didSet {
|
||||
if self.isLoading != oldValue {
|
||||
@ -88,6 +95,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
private let eventLogContext: ChannelAdminEventLogContext
|
||||
|
||||
private var enqueuedTransitions: [(ChatRecentActionsHistoryTransition, Bool)] = []
|
||||
private var searchResultsState: (String, [MessageIndex])?
|
||||
|
||||
private var historyDisposable: Disposable?
|
||||
private let resolvePeerByNameDisposable = MetaDisposable()
|
||||
@ -162,6 +170,16 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
return false
|
||||
}
|
||||
for entry in state.entries {
|
||||
if entry.entry.headerStableId == message.stableId {
|
||||
if case let .deleteMessage(message) = entry.entry.event.action {
|
||||
if strongSelf.expandedDeletedMessages.contains(message.id) {
|
||||
strongSelf.expandedDeletedMessages.remove(message.id)
|
||||
} else {
|
||||
strongSelf.expandedDeletedMessages.insert(message.id)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
if entry.entry.stableId == message.stableId {
|
||||
switch entry.entry.event.action {
|
||||
case let .changeStickerPack(_, new):
|
||||
@ -277,12 +295,28 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}, updateMessageReaction: { _, _, _, _ in
|
||||
}, activateMessagePinch: { _ in
|
||||
}, openMessageContextActions: { _, _, _, _ in
|
||||
}, navigateToMessage: { _, _, _ in }, navigateToMessageStandalone: { _ in
|
||||
}, navigateToMessage: { [weak self] fromId, toId, params in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
context.sharedContext.navigateToChat(accountId: self.context.account.id, peerId: toId.peerId, messageId: toId)
|
||||
}, navigateToMessageStandalone: { _ in
|
||||
}, navigateToThreadMessage: { [weak self] peerId, threadId, _ in
|
||||
if let context = self?.context, let navigationController = self?.getNavigationController() {
|
||||
let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, keepStack: .always).startStandalone()
|
||||
}
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url in
|
||||
}, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false
|
||||
}, requestMessageActionCallback: { [weak self] messageId, _, _, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.expandedDeletedMessages.contains(messageId) {
|
||||
self.expandedDeletedMessages.remove(messageId)
|
||||
} else {
|
||||
self.expandedDeletedMessages.insert(messageId)
|
||||
}
|
||||
}, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url in
|
||||
self?.openUrl(url.url)
|
||||
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
|
||||
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
|
||||
@ -616,15 +650,36 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
let previousView = Atomic<[ChatRecentActionsEntry]?>(value: nil)
|
||||
let previousExpandedDeletedMessages = Atomic<Set<EngineMessage.Id>>(value: Set())
|
||||
|
||||
let chatThemes = self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager)
|
||||
|
||||
let historyViewTransition = combineLatest(historyViewUpdate, self.chatPresentationDataPromise.get(), chatThemes)
|
||||
|> mapToQueue { update, chatPresentationData, chatThemes -> Signal<ChatRecentActionsHistoryTransition, NoError> in
|
||||
let processedView = chatRecentActionsEntries(entries: update.0, presentationData: chatPresentationData)
|
||||
let historyViewTransition = combineLatest(
|
||||
historyViewUpdate,
|
||||
self.chatPresentationDataPromise.get(),
|
||||
chatThemes,
|
||||
self.expandedDeletedMessagesPromise.get()
|
||||
)
|
||||
|> mapToQueue { [weak self] update, chatPresentationData, chatThemes, expandedDeletedMessages -> Signal<ChatRecentActionsHistoryTransition, NoError> in
|
||||
let processedView = chatRecentActionsEntries(entries: update.0, presentationData: chatPresentationData, expandedDeletedMessages: expandedDeletedMessages)
|
||||
let previous = previousView.swap(processedView)
|
||||
let previousExpandedDeletedMessages = previousExpandedDeletedMessages.swap(expandedDeletedMessages)
|
||||
|
||||
return .single(chatRecentActionsHistoryPreparedTransition(from: previous ?? [], to: processedView, type: update.2, canLoadEarlier: update.1, displayingResults: update.3, context: context, peer: peer, controllerInteraction: controllerInteraction, chatThemes: chatThemes))
|
||||
var updateType = update.2
|
||||
if previousExpandedDeletedMessages.count != expandedDeletedMessages.count {
|
||||
updateType = .generic
|
||||
}
|
||||
|
||||
var searchResultsState: (String, [MessageIndex])?
|
||||
if update.3, let query = self?.filter.query {
|
||||
searchResultsState = (query, processedView.compactMap { entry in
|
||||
return entry.entry.event.action.messageId.flatMap { MessageIndex(id: $0, timestamp: entry.entry.event.date) }
|
||||
})
|
||||
} else {
|
||||
searchResultsState = nil
|
||||
}
|
||||
|
||||
return .single(chatRecentActionsHistoryPreparedTransition(from: previous ?? [], to: processedView, type: updateType, canLoadEarlier: update.1, displayingResults: update.3, context: context, peer: peer, controllerInteraction: controllerInteraction, chatThemes: chatThemes, searchResultsState: searchResultsState))
|
||||
}
|
||||
|
||||
let appliedTransition = historyViewTransition |> deliverOnMainQueue |> mapToQueue { [weak self] transition -> Signal<Void, NoError> in
|
||||
@ -698,15 +753,20 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size, displayMode: .aspectFill, transition: transition)
|
||||
|
||||
let intrinsicPanelHeight: CGFloat = 47.0
|
||||
let panelHeight = intrinsicPanelHeight + cleanInsets.bottom
|
||||
transition.updateFrame(node: self.panelBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: panelHeight)))
|
||||
var panelHeight = intrinsicPanelHeight + cleanInsets.bottom
|
||||
var panelOffset: CGFloat = panelHeight
|
||||
if insets.bottom > cleanInsets.bottom {
|
||||
panelHeight = intrinsicPanelHeight
|
||||
panelOffset = insets.bottom + panelHeight
|
||||
}
|
||||
transition.updateFrame(node: self.panelBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelOffset), size: CGSize(width: layout.size.width, height: panelHeight)))
|
||||
self.panelBackgroundNode.update(size: self.panelBackgroundNode.bounds.size, transition: transition)
|
||||
transition.updateFrame(node: self.panelSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.panelSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - panelOffset), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||
|
||||
let infoButtonSize = CGSize(width: 56.0, height: intrinsicPanelHeight)
|
||||
transition.updateFrame(node: self.panelButtonNode, frame: CGRect(origin: CGPoint(x: insets.left + infoButtonSize.width, y: layout.size.height - panelHeight), size: CGSize(width: layout.size.width - insets.left - insets.right - infoButtonSize.width * 2.0, height: intrinsicPanelHeight)))
|
||||
transition.updateFrame(node: self.panelButtonNode, frame: CGRect(origin: CGPoint(x: insets.left + infoButtonSize.width, y: layout.size.height - panelOffset), size: CGSize(width: layout.size.width - insets.left - insets.right - infoButtonSize.width * 2.0, height: intrinsicPanelHeight)))
|
||||
|
||||
transition.updateFrame(node: self.panelInfoButtonNode, frame: CGRect(origin: CGPoint(x: layout.size.width - insets.right - infoButtonSize.width, y: layout.size.height - panelHeight), size: infoButtonSize))
|
||||
transition.updateFrame(node: self.panelInfoButtonNode, frame: CGRect(origin: CGPoint(x: layout.size.width - insets.right - infoButtonSize.width, y: layout.size.height - panelOffset), size: infoButtonSize))
|
||||
|
||||
self.visibleAreaInset = UIEdgeInsets(top: 0.0, left: 0.0, bottom: panelHeight, right: 0.0)
|
||||
|
||||
@ -714,14 +774,14 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
transition.updatePosition(node: self.listNode, position: CGRect(origin: CGPoint(), size: layout.size).center)
|
||||
|
||||
transition.updateFrame(node: self.loadingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
loadingNode.updateLayout(size: layout.size, insets: insets, transition: transition)
|
||||
self.loadingNode.updateLayout(size: layout.size, insets: insets, transition: transition)
|
||||
|
||||
let emptyFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - navigationBarHeight - panelHeight))
|
||||
transition.updateFrame(node: self.emptyNode, frame: emptyFrame)
|
||||
self.emptyNode.update(rect: emptyFrame, within: layout.size)
|
||||
self.emptyNode.updateLayout(presentationData: self.chatPresentationData, backgroundNode: self.backgroundNode, size: emptyFrame.size, transition: transition)
|
||||
|
||||
let contentBottomInset: CGFloat = panelHeight + 4.0
|
||||
let contentBottomInset: CGFloat = panelOffset + 4.0
|
||||
let listInsets = UIEdgeInsets(top: contentBottomInset, left: layout.safeInsets.right, bottom: insets.top, right: layout.safeInsets.left)
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
@ -763,6 +823,9 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
let displayingResults = transition.displayingResults
|
||||
let isEmpty = transition.isEmpty
|
||||
let displayEmptyNode = isEmpty && displayingResults
|
||||
|
||||
self.searchResultsState = transition.searchResultsState
|
||||
|
||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: ChatRecentActionsListOpaqueState(entries: transition.filteredEntries, canLoadEarlier: transition.canLoadEarlier), completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
if displayEmptyNode != strongSelf.listNode.isHidden {
|
||||
@ -805,6 +868,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
isEmpty = true
|
||||
}
|
||||
strongSelf.isEmptyUpdated(isEmpty)
|
||||
|
||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@ -834,6 +899,29 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
func updateSearchQuery(_ query: String) {
|
||||
self.filter = self.filter.withQuery(query.isEmpty ? nil : query)
|
||||
self.eventLogContext.setFilter(self.filter)
|
||||
|
||||
self.updateItemNodesSearchTextHighlightStates()
|
||||
}
|
||||
|
||||
func updateItemNodesSearchTextHighlightStates() {
|
||||
var searchString: String?
|
||||
var resultsMessageIndices: [MessageIndex]? = nil
|
||||
if let (query, indices) = self.searchResultsState {
|
||||
searchString = query
|
||||
resultsMessageIndices = indices
|
||||
}
|
||||
if searchString != self.controllerInteraction?.searchTextHighightState?.0 || resultsMessageIndices != self.controllerInteraction?.searchTextHighightState?.1 {
|
||||
var searchTextHighightState: (String, [MessageIndex])?
|
||||
if let searchString = searchString, let resultsMessageIndices = resultsMessageIndices {
|
||||
searchTextHighightState = (searchString, resultsMessageIndices)
|
||||
}
|
||||
self.controllerInteraction?.searchTextHighightState = searchTextHighightState
|
||||
self.listNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
itemNode.updateSearchTextHighlightState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateFilter(events: AdminLogEventsFlags, adminPeerIds: [PeerId]?) {
|
||||
@ -1294,3 +1382,20 @@ final class ChatMessageContextLocationContentSource: ContextLocationContentSourc
|
||||
return ContextControllerLocationViewInfo(location: self.location, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
||||
extension AdminLogEventAction {
|
||||
var messageId: MessageId? {
|
||||
switch self {
|
||||
case let .editMessage(_, new):
|
||||
return new.id
|
||||
case let .deleteMessage(message):
|
||||
return message.id
|
||||
case let .pollStopped(message):
|
||||
return message.id
|
||||
case let .sendMessage(message):
|
||||
return message.id
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import MergeLists
|
||||
import AccountContext
|
||||
import ChatControllerInteraction
|
||||
import ChatHistoryEntry
|
||||
import ChatMessageItem
|
||||
import ChatMessageItemImpl
|
||||
import TextFormat
|
||||
|
||||
@ -95,6 +96,8 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
let id: ChatRecentActionsEntryId
|
||||
let presentationData: ChatPresentationData
|
||||
let entry: ChannelAdminEventLogEntry
|
||||
let subEntries: [ChannelAdminEventLogEntry]
|
||||
let isExpanded: Bool?
|
||||
|
||||
static func ==(lhs: ChatRecentActionsEntry, rhs: ChatRecentActionsEntry) -> Bool {
|
||||
if lhs.id != rhs.id {
|
||||
@ -106,6 +109,12 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
if lhs.entry != rhs.entry {
|
||||
return false
|
||||
}
|
||||
if lhs.subEntries != rhs.subEntries {
|
||||
return false
|
||||
}
|
||||
if lhs.isExpanded != rhs.isExpanded {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -524,13 +533,48 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
|
||||
var text: String = ""
|
||||
var entities: [MessageTextEntity] = []
|
||||
|
||||
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageDeleted(author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""), generateEntities: { index in
|
||||
if index == 0, let author = author {
|
||||
return [.TextMention(peerId: author.id)]
|
||||
|
||||
let authorName = author.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""
|
||||
|
||||
if !self.subEntries.isEmpty {
|
||||
var peers: [EnginePeer] = []
|
||||
var existingPeerIds = Set<EnginePeer.Id>()
|
||||
for entry in self.subEntries {
|
||||
if case let .deleteMessage(message) = entry.event.action, let author = message.author {
|
||||
guard !existingPeerIds.contains(author.id) else {
|
||||
continue
|
||||
}
|
||||
peers.append(EnginePeer(author))
|
||||
existingPeerIds.insert(author.id)
|
||||
}
|
||||
}
|
||||
return []
|
||||
}, to: &text, entities: &entities)
|
||||
let peerNames = peers.map { $0.compactDisplayTitle }.joined(separator: ", ")
|
||||
let messagesString = self.presentationData.strings.Channel_AdminLog_MessageManyDeleted_Messages(Int32(self.subEntries.count))
|
||||
|
||||
let fullText: PresentationStrings.FormattedString
|
||||
if let isExpanded = self.isExpanded {
|
||||
let moreText = (isExpanded ? self.presentationData.strings.Channel_AdminLog_MessageManyDeleted_HideAll : self.presentationData.strings.Channel_AdminLog_MessageManyDeleted_ShowAll).replacingOccurrences(of: " ", with: "\u{00A0}")
|
||||
fullText = self.presentationData.strings.Channel_AdminLog_MessageManyDeletedMore(authorName, messagesString, peerNames, moreText)
|
||||
} else {
|
||||
fullText = self.presentationData.strings.Channel_AdminLog_MessageManyDeleted(authorName, messagesString, peerNames)
|
||||
}
|
||||
|
||||
appendAttributedText(text: fullText, generateEntities: { index in
|
||||
if index == 0, let author = author {
|
||||
return [.TextMention(peerId: author.id)]
|
||||
} else if index == 3 {
|
||||
return [.Custom(type: ApplicationSpecificEntityType.Button)]
|
||||
}
|
||||
return []
|
||||
}, to: &text, entities: &entities)
|
||||
} else {
|
||||
appendAttributedText(text: self.presentationData.strings.Channel_AdminLog_MessageDeleted(authorName), generateEntities: { index in
|
||||
if index == 0, let author = author {
|
||||
return [.TextMention(peerId: author.id)]
|
||||
}
|
||||
return []
|
||||
}, to: &text, entities: &entities)
|
||||
}
|
||||
|
||||
let action = TelegramMediaActionType.customText(text: text, entities: entities, additionalAttributes: nil)
|
||||
|
||||
@ -567,8 +611,24 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
if let peer = self.entry.peers[message.id.peerId] {
|
||||
peers[peer.id] = peer
|
||||
}
|
||||
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: message.threadId, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: message.associatedThreadInfo, associatedStories: [:])
|
||||
return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil))
|
||||
|
||||
var additionalContent: ChatMessageItemAdditionalContent?
|
||||
if !self.subEntries.isEmpty {
|
||||
var messages: [Message] = []
|
||||
for entry in self.subEntries {
|
||||
if case let .deleteMessage(message) = entry.event.action {
|
||||
messages.append(message)
|
||||
}
|
||||
}
|
||||
var hasButton = false
|
||||
if let isExpanded = self.isExpanded, !isExpanded {
|
||||
hasButton = true
|
||||
}
|
||||
additionalContent = .eventLogGroupedMessages(messages, hasButton)
|
||||
}
|
||||
|
||||
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: message.threadId, timestamp: self.entry.event.date, flags: [.Incoming], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: message.effectiveAuthor, text: message.text, attributes: attributes, media: message.media, peers: peers, associatedMessages: message.associatedMessages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: message.associatedThreadInfo, associatedStories: [:])
|
||||
return ChatMessageItemImpl(presentationData: self.presentationData, context: context, chatLocation: .peer(id: peer.id), associatedData: ChatMessageItemAssociatedData(automaticDownloadPeerType: .channel, automaticDownloadPeerId: nil, automaticDownloadNetworkType: .cellular, isRecentActions: true, availableReactions: nil, savedMessageTags: nil, defaultReaction: nil, isPremium: false, accountPeer: nil), controllerInteraction: controllerInteraction, content: .message(message: message, read: true, selection: .none, attributes: ChatMessageEntryAttributes(), location: nil), additionalContent: additionalContent)
|
||||
}
|
||||
case .participantJoin, .participantLeave:
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
@ -2184,16 +2244,53 @@ struct ChatRecentActionsEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
func chatRecentActionsEntries(entries: [ChannelAdminEventLogEntry], presentationData: ChatPresentationData) -> [ChatRecentActionsEntry] {
|
||||
func chatRecentActionsEntries(entries: [ChannelAdminEventLogEntry], presentationData: ChatPresentationData, expandedDeletedMessages: Set<EngineMessage.Id>) -> [ChatRecentActionsEntry] {
|
||||
var result: [ChatRecentActionsEntry] = []
|
||||
for entry in entries.reversed() {
|
||||
result.append(ChatRecentActionsEntry(id: ChatRecentActionsEntryId(eventId: entry.event.id, contentIndex: .content), presentationData: presentationData, entry: entry))
|
||||
if eventNeedsHeader(entry.event) {
|
||||
result.append(ChatRecentActionsEntry(id: ChatRecentActionsEntryId(eventId: entry.event.id, contentIndex: .header), presentationData: presentationData, entry: entry))
|
||||
var deleteMessageEntries: [ChannelAdminEventLogEntry] = []
|
||||
|
||||
func appendCurrentDeleteEntries() {
|
||||
if !deleteMessageEntries.isEmpty, let lastEntry = deleteMessageEntries.last, let lastMessageId = lastEntry.event.action.messageId {
|
||||
let isExpandable = deleteMessageEntries.count > 10
|
||||
let isExpanded = expandedDeletedMessages.contains(lastMessageId) || !isExpandable
|
||||
let isGroup = deleteMessageEntries.count > 1
|
||||
|
||||
for i in 0 ..< deleteMessageEntries.count {
|
||||
let entry = deleteMessageEntries[i]
|
||||
let isLast = i == deleteMessageEntries.count - 1
|
||||
if isExpanded || isLast {
|
||||
result.append(ChatRecentActionsEntry(id: ChatRecentActionsEntryId(eventId: entry.event.id, contentIndex: .content), presentationData: presentationData, entry: entry, subEntries: isGroup && isExpandable ? deleteMessageEntries : [], isExpanded: isExpandable && isLast ? isExpanded : nil))
|
||||
}
|
||||
}
|
||||
|
||||
result.append(ChatRecentActionsEntry(id: ChatRecentActionsEntryId(eventId: lastEntry.event.id, contentIndex: .header), presentationData: presentationData, entry: lastEntry, subEntries: isGroup ? deleteMessageEntries : [], isExpanded: isExpandable ? isExpanded : nil))
|
||||
|
||||
deleteMessageEntries = []
|
||||
}
|
||||
}
|
||||
|
||||
assert(result == result.sorted().reversed())
|
||||
for entry in entries.reversed() {
|
||||
let currentDeleteMessageEvent = deleteMessageEntries.first?.event
|
||||
var skipAppending = false
|
||||
if case .deleteMessage = entry.event.action {
|
||||
if currentDeleteMessageEvent == nil || (currentDeleteMessageEvent!.peerId == entry.event.peerId && abs(currentDeleteMessageEvent!.date - entry.event.date) < 5) {
|
||||
} else {
|
||||
appendCurrentDeleteEntries()
|
||||
}
|
||||
deleteMessageEntries.append(entry)
|
||||
skipAppending = true
|
||||
}
|
||||
if !skipAppending {
|
||||
appendCurrentDeleteEntries()
|
||||
|
||||
result.append(ChatRecentActionsEntry(id: ChatRecentActionsEntryId(eventId: entry.event.id, contentIndex: .content), presentationData: presentationData, entry: entry, subEntries: [], isExpanded: nil))
|
||||
if eventNeedsHeader(entry.event) {
|
||||
result.append(ChatRecentActionsEntry(id: ChatRecentActionsEntryId(eventId: entry.event.id, contentIndex: .header), presentationData: presentationData, entry: entry, subEntries: [], isExpanded: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
appendCurrentDeleteEntries()
|
||||
|
||||
// assert(result == result.sorted().reversed())
|
||||
return result
|
||||
}
|
||||
|
||||
@ -2205,17 +2302,18 @@ struct ChatRecentActionsHistoryTransition {
|
||||
let updates: [ListViewUpdateItem]
|
||||
let canLoadEarlier: Bool
|
||||
let displayingResults: Bool
|
||||
let searchResultsState: (String, [MessageIndex])?
|
||||
let isEmpty: Bool
|
||||
}
|
||||
|
||||
func chatRecentActionsHistoryPreparedTransition(from fromEntries: [ChatRecentActionsEntry], to toEntries: [ChatRecentActionsEntry], type: ChannelAdminEventLogUpdateType, canLoadEarlier: Bool, displayingResults: Bool, context: AccountContext, peer: Peer, controllerInteraction: ChatControllerInteraction, chatThemes: [TelegramTheme]) -> ChatRecentActionsHistoryTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
func chatRecentActionsHistoryPreparedTransition(from fromEntries: [ChatRecentActionsEntry], to toEntries: [ChatRecentActionsEntry], type: ChannelAdminEventLogUpdateType, canLoadEarlier: Bool, displayingResults: Bool, context: AccountContext, peer: Peer, controllerInteraction: ChatControllerInteraction, chatThemes: [TelegramTheme], searchResultsState: (String, [MessageIndex])?) -> ChatRecentActionsHistoryTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdatesReversed(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, peer: peer, controllerInteraction: controllerInteraction, chatThemes: chatThemes), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, peer: peer, controllerInteraction: controllerInteraction, chatThemes: chatThemes), directionHint: nil) }
|
||||
|
||||
return ChatRecentActionsHistoryTransition(filteredEntries: toEntries, type: type, deletions: deletions, insertions: insertions, updates: updates, canLoadEarlier: canLoadEarlier, displayingResults: displayingResults, isEmpty: toEntries.isEmpty)
|
||||
return ChatRecentActionsHistoryTransition(filteredEntries: toEntries, type: type, deletions: deletions, insertions: insertions, updates: updates, canLoadEarlier: canLoadEarlier, displayingResults: displayingResults, searchResultsState: searchResultsState, isEmpty: toEntries.isEmpty)
|
||||
}
|
||||
|
||||
private extension ExportedInvitation {
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Contact List/AddBirthdayIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Contact List/AddBirthdayIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "addbirthday_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
353
submodules/TelegramUI/Images.xcassets/Contact List/AddBirthdayIcon.imageset/addbirthday_30.pdf
vendored
Normal file
353
submodules/TelegramUI/Images.xcassets/Contact List/AddBirthdayIcon.imageset/addbirthday_30.pdf
vendored
Normal file
@ -0,0 +1,353 @@
|
||||
%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 7.000000 9.588997 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
12.205765 10.441348 m
|
||||
11.856519 10.554989 11.481276 10.363993 11.367635 10.014748 c
|
||||
11.253994 9.665502 11.444990 9.290258 11.794235 9.176618 c
|
||||
12.205765 10.441348 l
|
||||
h
|
||||
4.205764 9.176618 m
|
||||
4.555010 9.290258 4.746006 9.665502 4.632365 10.014748 c
|
||||
4.518725 10.363993 4.143481 10.554989 3.794235 10.441348 c
|
||||
4.205764 9.176618 l
|
||||
h
|
||||
15.335000 5.911003 m
|
||||
15.335000 5.015311 14.682563 4.068969 13.330831 3.308620 c
|
||||
11.999943 2.559996 10.116560 2.076003 8.000000 2.076003 c
|
||||
8.000000 0.746003 l
|
||||
10.301718 0.746003 12.418336 1.269370 13.982878 2.149425 c
|
||||
15.526576 3.017755 16.665001 4.321413 16.665001 5.911003 c
|
||||
15.335000 5.911003 l
|
||||
h
|
||||
8.000000 2.076003 m
|
||||
5.883440 2.076003 4.000058 2.559996 2.669169 3.308620 c
|
||||
1.317437 4.068969 0.665000 5.015311 0.665000 5.911003 c
|
||||
-0.665000 5.911003 l
|
||||
-0.665000 4.321413 0.473424 3.017755 2.017122 2.149425 c
|
||||
3.581664 1.269370 5.698282 0.746003 8.000000 0.746003 c
|
||||
8.000000 2.076003 l
|
||||
h
|
||||
11.794235 9.176618 m
|
||||
12.922145 8.809608 13.826619 8.294772 14.437608 7.711730 c
|
||||
15.046136 7.131035 15.335000 6.514472 15.335000 5.911003 c
|
||||
16.665001 5.911003 l
|
||||
16.665001 6.973168 16.149467 7.916560 15.355797 8.673929 c
|
||||
14.564587 9.428950 13.469061 10.030286 12.205765 10.441348 c
|
||||
11.794235 9.176618 l
|
||||
h
|
||||
0.665000 5.911003 m
|
||||
0.665000 6.514472 0.953865 7.131035 1.562393 7.711730 c
|
||||
2.173381 8.294772 3.077855 8.809608 4.205764 9.176618 c
|
||||
3.794235 10.441348 l
|
||||
2.530939 10.030286 1.435413 9.428950 0.644203 8.673929 c
|
||||
-0.149467 7.916560 -0.665000 6.973168 -0.665000 5.911003 c
|
||||
0.665000 5.911003 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 15.000000 4.613129 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
7.000000 5.386871 m
|
||||
7.648594 5.240073 l
|
||||
7.651151 5.251367 l
|
||||
7.653312 5.262742 l
|
||||
7.000000 5.386871 l
|
||||
h
|
||||
0.000000 2.051871 m
|
||||
-0.367269 2.051871 -0.665000 1.754141 -0.665000 1.386871 c
|
||||
-0.665000 1.019602 -0.367269 0.721871 0.000000 0.721871 c
|
||||
0.000000 2.051871 l
|
||||
h
|
||||
8.603312 10.262742 m
|
||||
8.671866 10.623556 8.434943 10.971629 8.074129 11.040184 c
|
||||
7.713314 11.108738 7.365242 10.871815 7.296687 10.511001 c
|
||||
8.603312 10.262742 l
|
||||
h
|
||||
6.351405 5.533669 m
|
||||
5.950424 3.762025 3.718182 2.051871 0.000000 2.051871 c
|
||||
0.000000 0.721871 l
|
||||
4.013805 0.721871 7.049575 2.593439 7.648594 5.240073 c
|
||||
6.351405 5.533669 l
|
||||
h
|
||||
7.653312 5.262742 m
|
||||
8.603312 10.262742 l
|
||||
7.296687 10.511001 l
|
||||
6.346687 5.511001 l
|
||||
7.653312 5.262742 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 13.500000 13.669998 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 7.330002 m
|
||||
-0.470226 7.800228 l
|
||||
-0.594938 7.675516 -0.665000 7.506371 -0.665000 7.330002 c
|
||||
0.000000 7.330002 l
|
||||
h
|
||||
3.000000 7.330002 m
|
||||
3.665000 7.330002 l
|
||||
3.665000 7.506371 3.594938 7.675516 3.470226 7.800228 c
|
||||
3.000000 7.330002 l
|
||||
h
|
||||
3.000000 1.830002 m
|
||||
3.470226 1.359776 l
|
||||
3.594938 1.484488 3.665000 1.653633 3.665000 1.830002 c
|
||||
3.000000 1.830002 l
|
||||
h
|
||||
0.000000 1.830002 m
|
||||
-0.665000 1.830002 l
|
||||
-0.665000 1.653633 -0.594938 1.484488 -0.470226 1.359776 c
|
||||
0.000000 1.830002 l
|
||||
h
|
||||
2.335000 7.330002 m
|
||||
2.335000 1.830002 l
|
||||
3.665000 1.830002 l
|
||||
3.665000 7.330002 l
|
||||
2.335000 7.330002 l
|
||||
h
|
||||
0.665000 1.830002 m
|
||||
0.665000 7.330002 l
|
||||
-0.665000 7.330002 l
|
||||
-0.665000 1.830002 l
|
||||
0.665000 1.830002 l
|
||||
h
|
||||
3.000000 1.830002 m
|
||||
2.529774 2.300228 2.530002 2.300456 2.530228 2.300682 c
|
||||
2.530301 2.300755 2.530526 2.300979 2.530672 2.301124 c
|
||||
2.530965 2.301416 2.531251 2.301700 2.531530 2.301977 c
|
||||
2.532089 2.302531 2.532620 2.303057 2.533125 2.303553 c
|
||||
2.534134 2.304547 2.535036 2.305427 2.535831 2.306197 c
|
||||
2.537421 2.307737 2.538584 2.308839 2.539320 2.309529 c
|
||||
2.540790 2.310907 2.540567 2.310652 2.538657 2.308981 c
|
||||
2.534820 2.305623 2.524336 2.296691 2.507250 2.283877 c
|
||||
2.472987 2.258180 2.413031 2.217510 2.327603 2.174796 c
|
||||
2.158924 2.090456 1.885112 1.995002 1.500000 1.995002 c
|
||||
1.500000 0.665002 l
|
||||
2.114888 0.665002 2.591076 0.819547 2.922397 0.985208 c
|
||||
3.086969 1.067494 3.214513 1.151824 3.305250 1.219877 c
|
||||
3.350664 1.253938 3.387055 1.284068 3.414468 1.308054 c
|
||||
3.428183 1.320055 3.439679 1.330542 3.448961 1.339244 c
|
||||
3.453604 1.343596 3.457696 1.347506 3.461239 1.350938 c
|
||||
3.463011 1.352654 3.464645 1.354251 3.466143 1.355726 c
|
||||
3.466892 1.356462 3.467606 1.357169 3.468287 1.357844 c
|
||||
3.468627 1.358182 3.468959 1.358512 3.469282 1.358834 c
|
||||
3.469444 1.358994 3.469680 1.359230 3.469760 1.359311 c
|
||||
3.469994 1.359544 3.470226 1.359776 3.000000 1.830002 c
|
||||
h
|
||||
1.500000 1.995002 m
|
||||
1.114888 1.995002 0.841076 2.090456 0.672397 2.174796 c
|
||||
0.586969 2.217510 0.527013 2.258180 0.492750 2.283877 c
|
||||
0.475664 2.296691 0.465180 2.305623 0.461343 2.308981 c
|
||||
0.459433 2.310652 0.459210 2.310907 0.460680 2.309529 c
|
||||
0.461416 2.308839 0.462579 2.307737 0.464169 2.306197 c
|
||||
0.464964 2.305427 0.465866 2.304547 0.466875 2.303553 c
|
||||
0.467380 2.303057 0.467912 2.302531 0.468470 2.301977 c
|
||||
0.468749 2.301700 0.469035 2.301416 0.469328 2.301124 c
|
||||
0.469474 2.300979 0.469699 2.300755 0.469772 2.300682 c
|
||||
0.469998 2.300456 0.470226 2.300228 0.000000 1.830002 c
|
||||
-0.470226 1.359776 -0.469994 1.359544 -0.469760 1.359311 c
|
||||
-0.469680 1.359230 -0.469444 1.358994 -0.469282 1.358834 c
|
||||
-0.468959 1.358512 -0.468627 1.358182 -0.468287 1.357844 c
|
||||
-0.467606 1.357169 -0.466892 1.356462 -0.466143 1.355726 c
|
||||
-0.464645 1.354251 -0.463011 1.352654 -0.461239 1.350938 c
|
||||
-0.457696 1.347506 -0.453604 1.343596 -0.448961 1.339244 c
|
||||
-0.439679 1.330542 -0.428183 1.320055 -0.414468 1.308054 c
|
||||
-0.387055 1.284068 -0.350664 1.253938 -0.305250 1.219877 c
|
||||
-0.214513 1.151824 -0.086969 1.067494 0.077603 0.985208 c
|
||||
0.408924 0.819547 0.885112 0.665002 1.500000 0.665002 c
|
||||
1.500000 1.995002 l
|
||||
h
|
||||
0.000000 7.330002 m
|
||||
0.470226 6.859776 0.469998 6.859548 0.469772 6.859322 c
|
||||
0.469699 6.859249 0.469474 6.859025 0.469328 6.858879 c
|
||||
0.469035 6.858588 0.468749 6.858304 0.468470 6.858027 c
|
||||
0.467912 6.857472 0.467380 6.856947 0.466875 6.856450 c
|
||||
0.465866 6.855457 0.464964 6.854577 0.464169 6.853806 c
|
||||
0.462579 6.852266 0.461416 6.851165 0.460680 6.850475 c
|
||||
0.459210 6.849096 0.459433 6.849351 0.461343 6.851023 c
|
||||
0.465180 6.854381 0.475664 6.863313 0.492750 6.876127 c
|
||||
0.527013 6.901824 0.586969 6.942494 0.672397 6.985208 c
|
||||
0.841076 7.069547 1.114888 7.165002 1.500000 7.165002 c
|
||||
1.500000 8.495002 l
|
||||
0.885112 8.495002 0.408924 8.340457 0.077603 8.174796 c
|
||||
-0.086969 8.092510 -0.214513 8.008180 -0.305250 7.940127 c
|
||||
-0.350664 7.906066 -0.387055 7.875936 -0.414468 7.851950 c
|
||||
-0.428183 7.839949 -0.439679 7.829462 -0.448961 7.820759 c
|
||||
-0.453604 7.816407 -0.457696 7.812498 -0.461239 7.809066 c
|
||||
-0.463011 7.807350 -0.464645 7.805752 -0.466143 7.804278 c
|
||||
-0.466892 7.803541 -0.467606 7.802835 -0.468287 7.802159 c
|
||||
-0.468627 7.801822 -0.468959 7.801492 -0.469282 7.801170 c
|
||||
-0.469444 7.801009 -0.469680 7.800774 -0.469760 7.800693 c
|
||||
-0.469994 7.800459 -0.470226 7.800228 0.000000 7.330002 c
|
||||
h
|
||||
1.500000 7.165002 m
|
||||
1.885112 7.165002 2.158924 7.069547 2.327603 6.985208 c
|
||||
2.413031 6.942494 2.472987 6.901824 2.507250 6.876127 c
|
||||
2.524336 6.863313 2.534820 6.854381 2.538657 6.851023 c
|
||||
2.540567 6.849351 2.540790 6.849096 2.539320 6.850475 c
|
||||
2.538584 6.851165 2.537421 6.852266 2.535831 6.853806 c
|
||||
2.535036 6.854577 2.534134 6.855457 2.533125 6.856450 c
|
||||
2.532620 6.856947 2.532089 6.857472 2.531530 6.858027 c
|
||||
2.531251 6.858304 2.530965 6.858588 2.530672 6.858879 c
|
||||
2.530526 6.859025 2.530301 6.859249 2.530228 6.859322 c
|
||||
2.530002 6.859548 2.529774 6.859776 3.000000 7.330002 c
|
||||
3.470226 7.800228 3.469994 7.800459 3.469760 7.800693 c
|
||||
3.469680 7.800774 3.469444 7.801009 3.469282 7.801170 c
|
||||
3.468959 7.801492 3.468627 7.801822 3.468287 7.802159 c
|
||||
3.467606 7.802835 3.466892 7.803541 3.466143 7.804278 c
|
||||
3.464645 7.805752 3.463011 7.807350 3.461239 7.809066 c
|
||||
3.457696 7.812498 3.453604 7.816407 3.448961 7.820759 c
|
||||
3.439679 7.829462 3.428183 7.839949 3.414468 7.851950 c
|
||||
3.387055 7.875936 3.350664 7.906066 3.305250 7.940127 c
|
||||
3.214513 8.008180 3.086969 8.092510 2.922397 8.174796 c
|
||||
2.591076 8.340457 2.114888 8.495002 1.500000 8.495002 c
|
||||
1.500000 7.165002 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 13.500000 20.169998 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
2.335000 2.830002 m
|
||||
2.335000 2.368844 1.961158 1.995002 1.500000 1.995002 c
|
||||
1.500000 0.665002 l
|
||||
2.695697 0.665002 3.665000 1.634305 3.665000 2.830002 c
|
||||
2.335000 2.830002 l
|
||||
h
|
||||
1.500000 1.995002 m
|
||||
1.038842 1.995002 0.665000 2.368844 0.665000 2.830002 c
|
||||
-0.665000 2.830002 l
|
||||
-0.665000 1.634305 0.304303 0.665002 1.500000 0.665002 c
|
||||
1.500000 1.995002 l
|
||||
h
|
||||
0.665000 2.830002 m
|
||||
0.665000 3.110801 0.793223 3.630285 1.029203 4.083600 c
|
||||
1.144165 4.304442 1.265095 4.470649 1.372974 4.572635 c
|
||||
1.483884 4.677485 1.525731 4.665002 1.500000 4.665002 c
|
||||
1.500000 5.995002 l
|
||||
1.060055 5.995002 0.707296 5.773571 0.459291 5.539114 c
|
||||
0.208255 5.301792 0.005961 4.998329 -0.150523 4.697724 c
|
||||
-0.457436 4.108146 -0.665000 3.377629 -0.665000 2.830002 c
|
||||
0.665000 2.830002 l
|
||||
h
|
||||
1.500000 4.665002 m
|
||||
1.474269 4.665002 1.516116 4.677485 1.627026 4.572635 c
|
||||
1.734905 4.470649 1.855835 4.304442 1.970797 4.083600 c
|
||||
2.206777 3.630285 2.335000 3.110801 2.335000 2.830002 c
|
||||
3.665000 2.830002 l
|
||||
3.665000 3.377629 3.457436 4.108146 3.150523 4.697724 c
|
||||
2.994039 4.998329 2.791745 5.301792 2.540709 5.539114 c
|
||||
2.292704 5.773571 1.939945 5.995002 1.500000 5.995002 c
|
||||
1.500000 4.665002 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
0.000000 1.000000 -1.000000 0.000000 6.330002 6.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.665000 1.330002 m
|
||||
0.665000 1.697271 0.367269 1.995002 0.000000 1.995002 c
|
||||
-0.367269 1.995002 -0.665000 1.697271 -0.665000 1.330002 c
|
||||
0.665000 1.330002 l
|
||||
h
|
||||
-0.665000 -5.669998 m
|
||||
-0.665000 -6.037268 -0.367269 -6.334998 0.000000 -6.334998 c
|
||||
0.367269 -6.334998 0.665000 -6.037268 0.665000 -5.669998 c
|
||||
-0.665000 -5.669998 l
|
||||
h
|
||||
-0.665000 1.330002 m
|
||||
-0.665000 -5.669998 l
|
||||
0.665000 -5.669998 l
|
||||
0.665000 1.330002 l
|
||||
-0.665000 1.330002 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 -0.000000 0.000000 1.000000 8.500000 1.169998 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.665000 8.330002 m
|
||||
0.665000 8.697271 0.367269 8.995002 0.000000 8.995002 c
|
||||
-0.367269 8.995002 -0.665000 8.697271 -0.665000 8.330002 c
|
||||
0.665000 8.330002 l
|
||||
h
|
||||
-0.665000 1.330002 m
|
||||
-0.665000 0.962732 -0.367269 0.665002 0.000000 0.665002 c
|
||||
0.367269 0.665002 0.665000 0.962732 0.665000 1.330002 c
|
||||
-0.665000 1.330002 l
|
||||
h
|
||||
-0.665000 8.330002 m
|
||||
-0.665000 1.330002 l
|
||||
0.665000 1.330002 l
|
||||
0.665000 8.330002 l
|
||||
-0.665000 8.330002 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
10208
|
||||
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
|
||||
0000010298 00000 n
|
||||
0000010322 00000 n
|
||||
0000010495 00000 n
|
||||
0000010569 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
10628
|
||||
%%EOF
|
@ -63,6 +63,7 @@ import BusinessIntroSetupScreen
|
||||
import TelegramNotices
|
||||
import BotSettingsScreen
|
||||
import CameraScreen
|
||||
import BirthdayPickerScreen
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -2151,7 +2152,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
|
||||
let limit: Int32 = 10
|
||||
var reachedLimitImpl: ((Int32) -> Void)?
|
||||
|
||||
var presentBirthdayPickerImpl: (() -> Void)?
|
||||
let mode: ContactMultiselectionControllerMode
|
||||
var currentBirthdays: [EnginePeer.Id: TelegramBirthday]?
|
||||
if case let .chatList(birthdays) = source, let birthdays, !birthdays.isEmpty {
|
||||
@ -2164,6 +2165,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mode = .premiumGifting(birthdays: nil, selectToday: false)
|
||||
}
|
||||
|
||||
var contactOptions: [ContactListAdditionalOption] = []
|
||||
if currentBirthdays != nil || "".isEmpty {
|
||||
contactOptions = [ContactListAdditionalOption(
|
||||
title: "Add Your Birthday",
|
||||
icon: .generic(UIImage(bundleImageName: "Contact List/AddBirthdayIcon")!),
|
||||
action: {
|
||||
presentBirthdayPickerImpl?()
|
||||
},
|
||||
clearHighlightAutomatically: true
|
||||
)]
|
||||
}
|
||||
|
||||
var openProfileImpl: ((EnginePeer) -> Void)?
|
||||
var sendMessageImpl: ((EnginePeer) -> Void)?
|
||||
|
||||
@ -2171,7 +2184,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
ContactMultiselectionControllerParams(
|
||||
context: context,
|
||||
mode: mode,
|
||||
options: [],
|
||||
options: contactOptions,
|
||||
isPeerEnabled: { peer in
|
||||
if case let .user(user) = peer, user.botInfo == nil && !peer.isService && !user.flags.contains(.isSupport) {
|
||||
return true
|
||||
@ -2275,6 +2288,33 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
}
|
||||
|
||||
presentBirthdayPickerImpl = { [weak controller] in
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
let _ = context.engine.notices.dismissServerProvidedSuggestion(suggestion: .setupBirthday).startStandalone()
|
||||
|
||||
let settingsPromise: Promise<AccountPrivacySettings?>
|
||||
if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface, let current = rootController.getPrivacySettings() {
|
||||
settingsPromise = current
|
||||
} else {
|
||||
settingsPromise = Promise()
|
||||
settingsPromise.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init)))
|
||||
}
|
||||
let birthdayController = BirthdayPickerScreen(context: context, settings: settingsPromise.get(), openSettings: {
|
||||
context.sharedContext.makeBirthdayPrivacyController(context: context, settings: settingsPromise, openedFromBirthdayScreen: true, present: { [weak controller] c in
|
||||
controller?.push(c)
|
||||
})
|
||||
}, completion: { [weak controller] value in
|
||||
let _ = context.engine.accountData.updateBirthday(birthday: value).startStandalone()
|
||||
|
||||
controller?.present(UndoOverlayController(presentationData: presentationData, content: .actionSucceeded(title: nil, text: presentationData.strings.Birthday_Added, cancel: nil, destructive: false), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
}), in: .current)
|
||||
})
|
||||
controller.push(birthdayController)
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
|
@ -80,6 +80,7 @@ public struct ChatTextFontAttributes: OptionSet, Hashable, Sequence {
|
||||
public static let italic = ChatTextFontAttributes(rawValue: 1 << 1)
|
||||
public static let monospace = ChatTextFontAttributes(rawValue: 1 << 2)
|
||||
public static let blockQuote = ChatTextFontAttributes(rawValue: 1 << 3)
|
||||
public static let smaller = ChatTextFontAttributes(rawValue: 1 << 4)
|
||||
|
||||
public func makeIterator() -> AnyIterator<ChatTextFontAttributes> {
|
||||
var index = 0
|
||||
|
@ -59,6 +59,7 @@ private let validTimecodePreviousSet: CharacterSet = {
|
||||
|
||||
public struct ApplicationSpecificEntityType {
|
||||
public static let Timecode: Int32 = 1
|
||||
public static let Button: Int32 = 2
|
||||
}
|
||||
|
||||
private enum CurrentEntityType {
|
||||
|
@ -253,6 +253,9 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
||||
if let time = parseTimecodeString(text) {
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Timecode), value: TelegramTimecode(time: time, text: text), range: range)
|
||||
}
|
||||
} else if type == ApplicationSpecificEntityType.Button {
|
||||
string.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Button), value: true as NSNumber, range: range)
|
||||
addFontAttributes(range, .smaller)
|
||||
}
|
||||
case let .CustomEmoji(_, fileId):
|
||||
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
|
||||
@ -284,6 +287,8 @@ public func stringWithAppliedEntities(_ text: String, entities: [MessageTextEnti
|
||||
font = italicFont
|
||||
} else if fontAttributes == [.monospace] {
|
||||
font = fixedFont
|
||||
} else if fontAttributes == [.smaller] {
|
||||
font = baseFont.withSize(floor(baseFont.pointSize * 0.9))
|
||||
} else {
|
||||
font = baseFont
|
||||
}
|
||||
|
@ -43,4 +43,5 @@ public struct TelegramTextAttributes {
|
||||
public static let Pre = "TelegramPre"
|
||||
public static let Spoiler = "TelegramSpoiler"
|
||||
public static let Code = "TelegramCode"
|
||||
public static let Button = "TelegramButton"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user