From cfe7f43e0dfe6a46c010b5882050322ff67671fb Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 14 Apr 2023 18:59:33 +0400 Subject: [PATCH] Various improvements --- .../Sources/NotificationService.swift | 58 ++++++++- .../Telegram-iOS/en.lproj/Localizable.strings | 3 + .../Sources/ChatListController.swift | 30 +++-- .../Sources/ChatListControllerNode.swift | 112 ++++++++++++++++-- .../Sources/Node/ChatListNode.swift | 2 +- ...tControllerExtractedPresentationNode.swift | 3 + .../Sources/StickerPickerScreen.swift | 9 +- submodules/Postbox/Sources/ChatListView.swift | 1 + .../Postbox/Sources/ChatListViewState.swift | 31 +++++ .../Postbox/Sources/SeedConfiguration.swift | 5 +- .../Sources/ReactionContextNode.swift | 3 +- .../InstalledStickerPacksController.swift | 4 +- submodules/TelegramCore/BUILD | 1 + .../Sources/ApiUtils/TelegramChannel.swift | 3 +- .../Sources/ApiUtils/TelegramGroup.swift | 3 +- .../PendingMessages/EnqueueMessage.swift | 20 +++- ...yncCore_StandaloneAccountTransaction.swift | 13 ++ .../Sources/MessageContentKind.swift | 4 +- .../Sources/ActionPanelComponent.swift | 21 +++- .../Sources/AvatarEditorScreen.swift | 6 +- .../Sources/ChatEntityKeyboardInputNode.swift | 44 +++++-- .../EmojiStatusSelectionComponent.swift | 3 +- .../Sources/EmojiPagerContentComponent.swift | 45 +++++-- .../Sources/EmojiSearchContent.swift | 3 +- .../Sources/ForumCreateTopicScreen.swift | 3 +- .../TelegramUI/Sources/AccountContext.swift | 12 +- .../TelegramUI/Sources/AppDelegate.swift | 10 +- .../Sources/ChatControllerNode.swift | 6 +- .../TelegramUI/Sources/ChatLoadingNode.swift | 9 +- .../Sources/ChatMessageBubbleItemNode.swift | 7 +- .../Sources/ChatMessageReplyInfoNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 37 +++--- .../Sources/PeerSelectionControllerNode.swift | 2 +- 33 files changed, 430 insertions(+), 85 deletions(-) diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 0a067efc1c..ed0b9eec2c 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -918,6 +918,7 @@ private final class NotificationServiceHandler { case logout case poll(peerId: PeerId, content: NotificationContent, messageId: MessageId?) case deleteMessage([MessageId]) + case readReactions([MessageId]) case readMessage(MessageId) case call(CallData) } @@ -948,6 +949,20 @@ private final class NotificationServiceHandler { action = .deleteMessage(messagesDeleted) } } + case "READ_REACTION": + if let peerId { + if let messageId = messageId { + action = .readReactions([MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageId)]) + } else if let messageIds = payloadJson["messages"] as? String { + var messages: [MessageId] = [] + for messageId in messageIds.split(separator: ",") { + if let messageIdValue = Int32(messageId) { + messages.append(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageIdValue)) + } + } + action = .readReactions(messages) + } + } case "READ_HISTORY": if let peerId = peerId { if let messageIdString = payloadJson["max_id"] as? String { @@ -1055,13 +1070,15 @@ private final class NotificationServiceHandler { } if let category = aps["category"] as? String { + if let _ = aps["r"] { + content.category = "r" + } if peerId.isGroupOrChannel && ["r", "m"].contains(category) { content.category = "g\(category)" } else { content.category = category } - let _ = messageId /*if (peerId != 0 && messageId != 0 && parsedAttachment != nil && attachmentData != nil) { @@ -1588,6 +1605,45 @@ private final class NotificationServiceHandler { } }) }) + case let .readReactions(ids): + Logger.shared.log("NotificationService \(episode)", "Will read reactions \(ids)") + UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in + var removeIdentifiers: [String] = [] + for notification in notifications { + if notification.request.content.categoryIdentifier != "r" { + continue + } + if let peerIdString = notification.request.content.userInfo["peerId"] as? String, let peerIdValue = Int64(peerIdString), let messageIdString = notification.request.content.userInfo["msg_id"] as? String, let messageIdValue = Int32(messageIdString) { + for id in ids { + if PeerId(peerIdValue) == id.peerId && messageIdValue == id.id { + removeIdentifiers.append(notification.request.identifier) + } + } + } + } + + let completeRemoval: () -> Void = { + guard let strongSelf = self else { + return + } + var content = NotificationContent(isLockedMessage: nil) + Logger.shared.log("NotificationService \(episode)", "Updating content to \(content)") + + updateCurrentContent(content) + + completed() + } + + if !removeIdentifiers.isEmpty { + Logger.shared.log("NotificationService \(episode)", "Will try to remove \(removeIdentifiers.count) notifications") + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: removeIdentifiers) + queue.after(1.0, { + completeRemoval() + }) + } else { + completeRemoval() + } + }) case let .readMessage(id): Logger.shared.log("NotificationService \(episode)", "Will read message \(id)") let _ = (stateManager.postbox.transaction { transaction -> Void in diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 32f52ea744..512728e03d 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7541,6 +7541,7 @@ Sorry for the inconvenience."; "PeerInfo.AutoDeleteSettingOther" = "Other..."; "PeerInfo.AutoDeleteDisable" = "Disable"; "PeerInfo.AutoDeleteInfo" = "Automatically delete messages sent in this chat after a certain period of time."; +"PeerInfo.ChannelAutoDeleteInfo" = "Automatically delete messages sent in this channel after a certain period of time."; "PeerInfo.ClearMessages" = "Clear Messages"; "PeerInfo.ClearConfirmationUser" = "Are you sure you want to delete all messages with %@?"; @@ -9339,3 +9340,5 @@ Sorry for the inconvenience."; "ChatList.EmptyListTooltip" = "Send a message or\nstart a group here."; "Username.BotTitle" = "Public Links"; + +"Notification.LockScreenReactionPlaceholder" = "Reaction"; diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 73613c7845..94832b4542 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -3561,17 +3561,29 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController } } case let .forum(peerId): - self.joinForumDisposable.set((self.context.peerChannelMemberCategoriesContextsManager.join(engine: context.engine, peerId: peerId, hash: nil) - |> afterDisposed { [weak self] in - Queue.mainQueue().async { - if let strongSelf = self { - let _ = strongSelf - /*strongSelf.activityIndicator.isHidden = true - strongSelf.activityIndicator.stopAnimating() - strongSelf.isJoining = false*/ + let presentationData = self.presentationData + let progressSignal = Signal { [weak self] subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) + self?.present(controller, in: .window(.root)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() } } - }).start(error: { [weak self] error in + } + |> runOn(Queue.mainQueue()) + |> delay(0.8, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + let signal: Signal = self.context.peerChannelMemberCategoriesContextsManager.join(engine: self.context.engine, peerId: peerId, hash: nil) + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + + self.joinForumDisposable.set((signal + |> deliverOnMainQueue).start(error: { [weak self] error in guard let strongSelf = self else { return } diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index e05badfeb4..7169a163e8 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -329,6 +329,8 @@ private final class ChatListContainerItemNode: ASDisplayNode { } private let context: AccountContext + private weak var controller: ChatListControllerImpl? + private let location: ChatListControllerLocation private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer private var presentationData: PresentationData @@ -348,13 +350,18 @@ private final class ChatListContainerItemNode: ASDisplayNode { private var pollFilterUpdatesDisposable: Disposable? private var chatFilterUpdatesDisposable: Disposable? + private var peerDataDisposable: Disposable? private var chatFolderUpdates: ChatFolderUpdates? + private var canReportPeer: Bool = false + private(set) var validLayout: (size: CGSize, insets: UIEdgeInsets, visualNavigationHeight: CGFloat, originalNavigationHeight: CGFloat, inlineNavigationLocation: ChatListControllerLocation?, inlineNavigationTransitionFraction: CGFloat)? - init(context: AccountContext, location: ChatListControllerLocation, filter: ChatListFilter?, chatListMode: ChatListNodeMode, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { + init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, filter: ChatListFilter?, chatListMode: ChatListNodeMode, previewing: Bool, isInlineMode: Bool, controlsHistoryPreload: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, becameEmpty: @escaping (ChatListFilter?) -> Void, emptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { self.context = context + self.controller = controller + self.location = location self.animationCache = animationCache self.animationRenderer = animationRenderer self.presentationData = presentationData @@ -504,11 +511,33 @@ private final class ChatListContainerItemNode: ASDisplayNode { } }) } + + if case let .forum(peerId) = location { + self.peerDataDisposable = (context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.StatusSettings(id: peerId) + ) + |> deliverOnMainQueue).start(next: { [weak self] statusSettings in + guard let self else { + return + } + var canReportPeer = false + if let statusSettings, statusSettings.flags.contains(.canReport) { + canReportPeer = true + } + if self.canReportPeer != canReportPeer { + self.canReportPeer = canReportPeer + if let (size, insets, visualNavigationHeight, originalNavigationHeight, inlineNavigationLocation, inlineNavigationTransitionFraction) = self.validLayout { + self.updateLayout(size: size, insets: insets, visualNavigationHeight: visualNavigationHeight, originalNavigationHeight: originalNavigationHeight, inlineNavigationLocation: inlineNavigationLocation, inlineNavigationTransitionFraction: inlineNavigationTransitionFraction, transition: .animated(duration: 0.4, curve: .spring)) + } + } + }) + } } deinit { self.pollFilterUpdatesDisposable?.dispose() self.chatFilterUpdatesDisposable?.dispose() + self.peerDataDisposable?.dispose() } private func layoutEmptyShimmerEffectNode(node: ChatListShimmerNode, size: CGSize, insets: UIEdgeInsets, verticalOffset: CGFloat, transition: ContainedViewLayoutTransition) { @@ -580,6 +609,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { component: AnyComponent(ActionPanelComponent( theme: self.presentationData.theme, title: title, + color: .accent, action: { [weak self] in guard let self, let chatFolderUpdates = self.chatFolderUpdates else { return @@ -603,6 +633,72 @@ private final class ChatListContainerItemNode: ASDisplayNode { } } + topPanel.size = CGSize(width: size.width, height: topPanelHeight) + listInsets.top += topPanelHeight + additionalTopInset += topPanelHeight + } else if self.canReportPeer { + let topPanel: TopPanelItem + var topPanelTransition = Transition(transition) + if let current = self.topPanel { + topPanel = current + } else { + topPanelTransition = .immediate + topPanel = TopPanelItem() + self.topPanel = topPanel + } + + let title: String = self.presentationData.strings.Conversation_ReportSpamAndLeave + + let topPanelHeight: CGFloat = 44.0 + + let _ = topPanel.view.update( + transition: topPanelTransition, + component: AnyComponent(ActionPanelComponent( + theme: self.presentationData.theme, + title: title, + color: .destructive, + action: { [weak self] in + guard let self, case let .forum(peerId) = self.location else { + return + } + + let actionSheet = ActionSheetController(presentationData: self.presentationData) + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: self.presentationData.strings.Conversation_ReportSpamGroupConfirmation), + ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ReportSpamAndLeave, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + + if let self { + self.controller?.setInlineChatList(location: nil) + let _ = self.context.engine.peers.removePeerChat(peerId: peerId, reportChatSpam: true).start() + } + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + self.listNode.present?(actionSheet) + }, + dismissAction: { [weak self] in + guard let self, case let .forum(peerId) = self.location else { + return + } + let _ = self.context.engine.peers.dismissPeerStatusOptions(peerId: peerId).start() + } + )), + environment: {}, + containerSize: CGSize(width: size.width, height: topPanelHeight) + ) + if let topPanelView = topPanel.view.view { + if topPanelView.superview == nil { + self.view.addSubview(topPanelView) + } + } + topPanel.size = CGSize(width: size.width, height: topPanelHeight) listInsets.top += topPanelHeight additionalTopInset += topPanelHeight @@ -635,6 +731,7 @@ private final class ChatListContainerItemNode: ASDisplayNode { public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate { private let context: AccountContext + private weak var controller: ChatListControllerImpl? let location: ChatListControllerLocation private let chatListMode: ChatListNodeMode private let previewing: Bool @@ -847,8 +944,9 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele var didBeginSelectingChats: (() -> Void)? public var displayFilterLimit: (() -> Void)? - public init(context: AccountContext, location: ChatListControllerLocation, chatListMode: ChatListNodeMode = .chatList(appendContacts: true), previewing: Bool, controlsHistoryPreload: Bool, isInlineMode: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { + public init(context: AccountContext, controller: ChatListControllerImpl?, location: ChatListControllerLocation, chatListMode: ChatListNodeMode = .chatList(appendContacts: true), previewing: Bool, controlsHistoryPreload: Bool, isInlineMode: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { self.context = context + self.controller = controller self.location = location self.chatListMode = chatListMode self.previewing = previewing @@ -872,7 +970,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele self.backgroundColor = presentationData.theme.chatList.backgroundColor - let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: nil, chatListMode: chatListMode, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + let itemNode = ChatListContainerItemNode(context: self.context, controller: self.controller, location: self.location, filter: nil, chatListMode: chatListMode, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -1170,7 +1268,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele itemNode.emptyNode?.restartAnimation() completion?() } else if self.pendingItemNode == nil { - let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[index].filter, chatListMode: self.chatListMode, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + let itemNode = ChatListContainerItemNode(context: self.context, controller: self.controller, location: self.location, filter: self.availableFilters[index].filter, chatListMode: self.chatListMode, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -1288,7 +1386,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele validNodeIds.append(id) if self.itemNodes[id] == nil && self.enableAdjacentFilterLoading && !self.disableItemNodeOperationsWhileAnimating { - let itemNode = ChatListContainerItemNode(context: self.context, location: self.location, filter: self.availableFilters[i].filter, chatListMode: self.chatListMode, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in + let itemNode = ChatListContainerItemNode(context: self.context, controller: self.controller, location: self.location, filter: self.availableFilters[i].filter, chatListMode: self.chatListMode, previewing: self.previewing, isInlineMode: self.isInlineMode, controlsHistoryPreload: self.controlsHistoryPreload, presentationData: self.presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, becameEmpty: { [weak self] filter in self?.filterBecameEmpty(filter) }, emptyAction: { [weak self] filter in self?.filterEmptyAction(filter) @@ -1421,7 +1519,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { var filterBecameEmpty: ((ChatListFilter?) -> Void)? var filterEmptyAction: ((ChatListFilter?) -> Void)? var secondaryEmptyAction: (() -> Void)? - self.mainContainerNode = ChatListContainerNode(context: context, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, isInlineMode: false, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in + self.mainContainerNode = ChatListContainerNode(context: context, controller: controller, location: location, previewing: previewing, controlsHistoryPreload: controlsHistoryPreload, isInlineMode: false, presentationData: presentationData, animationCache: animationCache, animationRenderer: animationRenderer, filterBecameEmpty: { filter in filterBecameEmpty?(filter) }, filterEmptyAction: { filter in filterEmptyAction?(filter) @@ -1849,7 +1947,7 @@ final class ChatListControllerNode: ASDisplayNode, UIGestureRecognizerDelegate { 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: {}) + let inlineStackContainerNode = ChatListContainerNode(context: self.context, controller: self.controller, 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: {}) return inlineStackContainerNode } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 6cdfc5f62c..fd358cff29 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -1775,7 +1775,7 @@ public final class ChatListNode: ListView { })*/ let contacts: Signal<[ChatListContactPeer], NoError> - if case .chatList(groupId: .root) = location, chatListFilter == nil { + if case .chatList(groupId: .root) = location, chatListFilter == nil, case .chatList = mode { contacts = ApplicationSpecificNotice.displayChatListContacts(accountManager: context.sharedContext.accountManager) |> distinctUntilChanged |> mapToSignal { value -> Signal<[ChatListContactPeer], NoError> in diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 099518966b..6a31d7403d 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -269,6 +269,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo super.init() + self.view.addSubview(self.scroller) + self.scroller.isHidden = true + self.addSubnode(self.backgroundNode) self.addSubnode(self.clippingNode) self.clippingNode.addSubnode(self.scrollNode) diff --git a/submodules/DrawingUI/Sources/StickerPickerScreen.swift b/submodules/DrawingUI/Sources/StickerPickerScreen.swift index 7e5720995d..85e9b74f03 100644 --- a/submodules/DrawingUI/Sources/StickerPickerScreen.swift +++ b/submodules/DrawingUI/Sources/StickerPickerScreen.swift @@ -377,7 +377,8 @@ class StickerPickerScreen: ViewController { externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: false, - hideBackground: true + hideBackground: true, + stateContext: nil ) content.masks?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( @@ -455,7 +456,8 @@ class StickerPickerScreen: ViewController { externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: false, - hideBackground: true + hideBackground: true, + stateContext: nil ) var stickerPeekBehavior: EmojiContentPeekBehaviorImpl? @@ -580,7 +582,8 @@ class StickerPickerScreen: ViewController { externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: false, - hideBackground: true + hideBackground: true, + stateContext: nil ) if let (layout, navigationHeight) = self.currentLayout { diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index f6e82fa722..56a37baaaa 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -677,6 +677,7 @@ final class MutableChatListView { switch entry { case let .IntermediateMessageEntry(index, messageIndex): var renderedMessages: [Message] = [] + if let messageIndex = messageIndex { if let messageGroup = postbox.messageHistoryTable.getMessageGroup(at: messageIndex, limit: 10) { renderedMessages.append(contentsOf: messageGroup.compactMap(postbox.renderIntermediateMessage)) diff --git a/submodules/Postbox/Sources/ChatListViewState.swift b/submodules/Postbox/Sources/ChatListViewState.swift index 20eca6a4a3..1551b9a486 100644 --- a/submodules/Postbox/Sources/ChatListViewState.swift +++ b/submodules/Postbox/Sources/ChatListViewState.swift @@ -1599,6 +1599,37 @@ struct ChatListViewState { } } + var needsMigrationMerge = false + for message in renderedMessages { + if postbox.seedConfiguration.isPeerUpgradeMessage(message) { + if renderedMessages.count == 1 { + needsMigrationMerge = true + } + } + } + + if needsMigrationMerge, let associatedMessageId = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId)?.associatedHistoryMessageId { + let innerMessages = postbox.messageHistoryTable.fetch( + peerId: associatedMessageId.peerId, + namespace: associatedMessageId.namespace, + tag: nil, + threadId: nil, + from: .absoluteUpperBound().withPeerId(associatedMessageId.peerId).withNamespace(associatedMessageId.namespace), + includeFrom: true, + to: .absoluteLowerBound().withPeerId(associatedMessageId.peerId).withNamespace(associatedMessageId.namespace), + ignoreMessagesInTimestampRange: nil, + limit: 2 + ) + for innerMessage in innerMessages { + let message = postbox.renderIntermediateMessage(innerMessage) + if !postbox.seedConfiguration.isPeerUpgradeMessage(message) { + renderedMessages.removeAll() + renderedMessages.append(message) + break + } + } + } + var autoremoveTimeout: Int32? if let cachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId) { autoremoveTimeout = postbox.seedConfiguration.decodeAutoremoveTimeout(cachedData) diff --git a/submodules/Postbox/Sources/SeedConfiguration.swift b/submodules/Postbox/Sources/SeedConfiguration.swift index f39902dd77..704eb78956 100644 --- a/submodules/Postbox/Sources/SeedConfiguration.swift +++ b/submodules/Postbox/Sources/SeedConfiguration.swift @@ -76,6 +76,7 @@ public final class SeedConfiguration { public let mergeMessageAttributes: ([MessageAttribute], inout [MessageAttribute]) -> Void public let decodeMessageThreadInfo: (CodableEntry) -> Message.AssociatedThreadInfo? public let decodeAutoremoveTimeout: (CachedPeerData) -> Int32? + public let isPeerUpgradeMessage: (Message) -> Bool public init( globalMessageIdsPeerIdNamespaces: Set, @@ -101,7 +102,8 @@ public final class SeedConfiguration { defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings, mergeMessageAttributes: @escaping ([MessageAttribute], inout [MessageAttribute]) -> Void, decodeMessageThreadInfo: @escaping (CodableEntry) -> Message.AssociatedThreadInfo?, - decodeAutoremoveTimeout: @escaping (CachedPeerData) -> Int32? + decodeAutoremoveTimeout: @escaping (CachedPeerData) -> Int32?, + isPeerUpgradeMessage: @escaping (Message) -> Bool ) { self.globalMessageIdsPeerIdNamespaces = globalMessageIdsPeerIdNamespaces self.initializeChatListWithHole = initializeChatListWithHole @@ -123,5 +125,6 @@ public final class SeedConfiguration { self.mergeMessageAttributes = mergeMessageAttributes self.decodeMessageThreadInfo = decodeMessageThreadInfo self.decodeAutoremoveTimeout = decodeAutoremoveTimeout + self.isPeerUpgradeMessage = isPeerUpgradeMessage } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index d2ff3e60cc..1ea0d48d3b 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -1583,7 +1583,8 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { ), externalExpansionView: self.view, useOpaqueTheme: false, - hideBackground: false + hideBackground: false, + stateContext: nil ) } diff --git a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift index cfcecf8841..3df985500b 100644 --- a/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift +++ b/submodules/SettingsUI/Sources/Stickers/InstalledStickerPacksController.swift @@ -519,7 +519,9 @@ private func installedStickerPacksControllerEntries(context: AccountContext, pre if let archived = archived, !archived.isEmpty { entries.append(.archived(presentationData.theme, presentationData.strings.StickerPacksSettings_ArchivedPacks, Int32(archived.count), archived)) } - entries.append(.emoji(presentationData.theme, presentationData.strings.StickerPacksSettings_Emoji, emojiCount)) + if emojiCount != 0 { + entries.append(.emoji(presentationData.theme, presentationData.strings.StickerPacksSettings_Emoji, emojiCount)) + } if let quickReaction = quickReaction, let availableReactions = availableReactions { entries.append(.quickReaction(presentationData.strings.Settings_QuickReactionSetup_NavigationTitle, quickReaction, availableReactions)) } diff --git a/submodules/TelegramCore/BUILD b/submodules/TelegramCore/BUILD index 5a883c9356..980b3eff65 100644 --- a/submodules/TelegramCore/BUILD +++ b/submodules/TelegramCore/BUILD @@ -48,6 +48,7 @@ swift_library( "//submodules/ManagedFile:ManagedFile", "//submodules/Utils/RangeSet:RangeSet", "//submodules/Utils/DarwinDirStat", + "//submodules/Emoji", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramChannel.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramChannel.swift index 90d653e163..5fbc467e5a 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramChannel.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramChannel.swift @@ -111,7 +111,8 @@ public extension TelegramChannel { .banSendStickers, .banSendPolls, .banSendFiles, - .banSendInline + .banSendInline, + .banSendMusic ] if let bannedRights = self.bannedRights, bannedRights.flags.intersection(flags) == flags { diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramGroup.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramGroup.swift index d0a85c23cd..2408b3f899 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramGroup.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramGroup.swift @@ -30,7 +30,8 @@ public extension TelegramGroup { .banSendStickers, .banSendPolls, .banSendFiles, - .banSendInline + .banSendInline, + .banSendMusic ] if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.intersection(flags) == flags { return false diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift index 176b04e711..4330fb5ab9 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift @@ -2,6 +2,7 @@ import Foundation import Postbox import TelegramApi import SwiftSignalKit +import Emoji public enum EnqueueMessageGrouping { case none @@ -287,18 +288,25 @@ public func resendMessages(account: Account, messageIds: [MessageId]) -> Signal< var filteredAttributes: [MessageAttribute] = [] var replyToMessageId: MessageId? var bubbleUpEmojiOrStickersets: [ItemCollectionId] = [] + var forwardSource: MessageId? inner: for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute { replyToMessageId = attribute.messageId } else if let attribute = attribute as? OutgoingMessageInfoAttribute { bubbleUpEmojiOrStickersets = attribute.bubbleUpEmojiOrStickersets continue inner + } else if let attribute = attribute as? ForwardSourceInfoAttribute { + forwardSource = attribute.messageId } else { filteredAttributes.append(attribute) } } - - messages.append(.message(text: message.text, attributes: filteredAttributes, inlineStickers: [:], mediaReference: message.media.first.flatMap(AnyMediaReference.standalone), replyToMessageId: replyToMessageId, localGroupingKey: message.groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)) + + if let forwardSource { + messages.append(.forward(source: forwardSource, threadId: nil, grouping: .auto, attributes: filteredAttributes, correlationId: nil)) + } else { + messages.append(.message(text: message.text, attributes: filteredAttributes, inlineStickers: [:], mediaReference: message.media.first.flatMap(AnyMediaReference.standalone), replyToMessageId: replyToMessageId, localGroupingKey: message.groupingKey, correlationId: nil, bubbleUpEmojiOrStickersets: bubbleUpEmojiOrStickersets)) + } } } let _ = enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: messages.map { (false, $0) }) @@ -399,6 +407,14 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, transaction.storeMediaIfNotPresent(media: file) } + for emoji in text.emojis { + if emoji.isSingleEmoji { + if !emojiItems.contains(where: { $0.content == .text(emoji) }) { + emojiItems.append(RecentEmojiItem(.text(emoji))) + } + } + } + var peerAutoremoveTimeout: Int32? if let peer = peer as? TelegramSecretChat { var isAction = false diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift index e050fbcbcd..d8195e20b6 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_StandaloneAccountTransaction.swift @@ -149,6 +149,19 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { } } return nil + }, + isPeerUpgradeMessage: { message in + for media in message.media { + if let action = media as? TelegramMediaAction { + switch action.action { + case .groupMigratedToChannel, .channelMigratedFromGroup: + return true + default: + break + } + } + } + return false } ) }() diff --git a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift index 990b07d8b0..e306922d14 100644 --- a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift +++ b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift @@ -414,11 +414,13 @@ public func foldLineBreaks(_ text: String) -> String { public func foldLineBreaks(_ text: NSAttributedString) -> NSAttributedString { let remainingString = NSMutableAttributedString(attributedString: text) + var lines: [NSAttributedString] = [] while true { if let range = remainingString.string.range(of: "\n") { let mappedRange = NSRange(range, in: remainingString.string) - lines.append(remainingString.attributedSubstring(from: NSRange(location: 0, length: mappedRange.upperBound - 1))) + let restString = remainingString.attributedSubstring(from: NSRange(location: 0, length: mappedRange.upperBound - 1)) + lines.append(restString) remainingString.replaceCharacters(in: NSRange(location: 0, length: mappedRange.upperBound), with: "") } else { if lines.isEmpty { diff --git a/submodules/TelegramUI/Components/ActionPanelComponent/Sources/ActionPanelComponent.swift b/submodules/TelegramUI/Components/ActionPanelComponent/Sources/ActionPanelComponent.swift index e827d85d98..2e946f37db 100644 --- a/submodules/TelegramUI/Components/ActionPanelComponent/Sources/ActionPanelComponent.swift +++ b/submodules/TelegramUI/Components/ActionPanelComponent/Sources/ActionPanelComponent.swift @@ -7,19 +7,27 @@ import ComponentDisplayAdapters import AppBundle public final class ActionPanelComponent: Component { + public enum Color { + case accent + case destructive + } + public let theme: PresentationTheme public let title: String + public let color: Color public let action: () -> Void public let dismissAction: () -> Void public init( theme: PresentationTheme, title: String, + color: Color, action: @escaping () -> Void, dismissAction: @escaping () -> Void ) { self.theme = theme self.title = title + self.color = color self.action = action self.dismissAction = dismissAction } @@ -31,6 +39,9 @@ public final class ActionPanelComponent: Component { if lhs.title != rhs.title { return false } + if lhs.color != rhs.color { + return false + } return true } @@ -136,9 +147,17 @@ public final class ActionPanelComponent: Component { let rightInset: CGFloat = 44.0 + let resolvedColor: UIColor + switch component.color { + case .accent: + resolvedColor = component.theme.rootController.navigationBar.accentTextColor + case .destructive: + resolvedColor = component.theme.list.itemDestructiveColor + } + let titleSize = self.title.update( transition: .immediate, - component: AnyComponent(Text(text: component.title, font: Font.regular(17.0), color: component.theme.rootController.navigationBar.accentTextColor)), + component: AnyComponent(Text(text: component.title, font: Font.regular(17.0), color: resolvedColor)), environment: {}, containerSize: CGSize(width: availableSize.width - rightInset, height: availableSize.height) ) diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 3d57ab5d24..fd58e86a12 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -686,7 +686,8 @@ final class AvatarEditorScreenComponent: Component { externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: true, - hideBackground: true + hideBackground: true, + stateContext: nil ) data.stickers?.inputInteractionHolder.inputInteraction = EmojiPagerContentComponent.InputInteraction( @@ -813,7 +814,8 @@ final class AvatarEditorScreenComponent: Component { externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: true, - hideBackground: true + hideBackground: true, + stateContext: nil ) self.state?.updated(transition: .immediate) diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 0e4f25ab67..3ddeb72b0b 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -87,6 +87,13 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } } + public final class StateContext { + let emojiState = EmojiPagerContentComponent.StateContext() + + public init() { + } + } + public static func hasPremium(context: AccountContext, chatPeerId: EnginePeer.Id?, premiumIfSavedMessages: Bool) -> Signal { let hasPremium: Signal if premiumIfSavedMessages, let chatPeerId = chatPeerId, chatPeerId == context.account.peerId { @@ -237,6 +244,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } private let context: AccountContext + private let stateContext: StateContext? private let entityKeyboardView: ComponentHostView private let defaultToEmojiTab: Bool @@ -581,11 +589,12 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { |> distinctUntilChanged } - public init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, opaqueTopPanelBackground: Bool = false, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPeerId: PeerId?) { + public init(context: AccountContext, currentInputData: InputData, updatedInputData: Signal, defaultToEmojiTab: Bool, opaqueTopPanelBackground: Bool = false, controllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?, chatPeerId: PeerId?, stateContext: StateContext?) { self.context = context self.currentInputData = currentInputData self.defaultToEmojiTab = defaultToEmojiTab self.opaqueTopPanelBackground = opaqueTopPanelBackground + self.stateContext = stateContext self.controllerInteraction = controllerInteraction @@ -1217,7 +1226,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: false, - hideBackground: false + hideBackground: false, + stateContext: self.stateContext?.emojiState ) self.stickerInputInteraction = EmojiPagerContentComponent.InputInteraction( @@ -1510,7 +1520,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: false, - hideBackground: false + hideBackground: false, + stateContext: nil ) self.inputDataDisposable = (combineLatest(queue: .mainQueue(), @@ -1950,8 +1961,25 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { var updatedGroups: [EmojiPagerContentComponent.ItemGroup] = [] var staticIsFirst = false - if let first = itemGroups.first, first.groupId == AnyHashable("static") { - staticIsFirst = true + let topStaticGroups: [String] = [ + "static", + "recent", + "featuredTop" + ] + for group in itemGroups { + var found = false + for topStaticGroup in topStaticGroups { + if group.groupId == AnyHashable(topStaticGroup) { + if group.groupId == AnyHashable("static") { + staticIsFirst = true + } + found = true + break + } + } + if !found { + break + } } for group in itemGroups { @@ -2379,7 +2407,8 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: false, - hideBackground: hideBackground + hideBackground: hideBackground, + stateContext: nil ) let semaphore = DispatchSemaphore(value: 0) @@ -2411,7 +2440,8 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi opaqueTopPanelBackground: true, controllerInteraction: nil, interfaceInteraction: nil, - chatPeerId: nil + chatPeerId: nil, + stateContext: nil ) self.inputNode = inputNode inputNode.clipContentToTopPanel = true diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index ef73e6e853..68f6e7504c 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -674,7 +674,8 @@ public final class EmojiStatusSelectionController: ViewController { externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: true, - hideBackground: false + hideBackground: false, + stateContext: nil ) strongSelf.refreshLayout(transition: .immediate) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 0a9b72b465..560d832e04 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -2244,6 +2244,13 @@ public final class EmojiPagerContentComponent: Component { } } + public final class StateContext { + var scrollPosition: CGFloat = 0.0 + + public init() { + } + } + public final class SynchronousLoadBehavior { public let isDisabled: Bool @@ -2313,6 +2320,7 @@ public final class EmojiPagerContentComponent: Component { public let useOpaqueTheme: Bool public let hideBackground: Bool public let scrollingStickersGridPromise = ValuePromise(false) + public let stateContext: StateContext? public init( performItemAction: @escaping (AnyHashable, Item, UIView, CGRect, CALayer, Bool) -> Void, @@ -2337,7 +2345,8 @@ public final class EmojiPagerContentComponent: Component { externalBackground: ExternalBackground?, externalExpansionView: UIView?, useOpaqueTheme: Bool, - hideBackground: Bool + hideBackground: Bool, + stateContext: StateContext? ) { self.performItemAction = performItemAction self.deleteBackwards = deleteBackwards @@ -2362,6 +2371,7 @@ public final class EmojiPagerContentComponent: Component { self.externalExpansionView = externalExpansionView self.useOpaqueTheme = useOpaqueTheme self.hideBackground = hideBackground + self.stateContext = stateContext } } @@ -5209,6 +5219,10 @@ public final class EmojiPagerContentComponent: Component { self.updateVisibleItems(transition: .immediate, attemptSynchronousLoads: false, previousItemPositions: nil, updatedItemPositions: nil) self.updateScrollingOffset(isReset: false, transition: .immediate) + + if let stateContext = self.component?.inputInteractionHolder.inputInteraction?.stateContext { + stateContext.scrollPosition = scrollView.bounds.minY + } } public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { @@ -6493,9 +6507,13 @@ public final class EmojiPagerContentComponent: Component { let previousSize = self.scrollView.bounds.size var resetScrolling = false + var isFirstUpdate = false if self.scrollView.bounds.isEmpty && component.displaySearchWithPlaceholder != nil { resetScrolling = true } + if previousComponent == nil { + isFirstUpdate = true + } if previousComponent?.itemContentUniqueId != component.itemContentUniqueId { resetScrolling = true } @@ -6601,7 +6619,11 @@ public final class EmojiPagerContentComponent: Component { } if resetScrolling { - self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: scrollSize) + var resetScrollY: CGFloat = 0.0 + if isFirstUpdate, let stateContext = component.inputInteractionHolder.inputInteraction?.stateContext { + resetScrollY = stateContext.scrollPosition + } + self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: resetScrollY), size: scrollSize) } self.ignoreScrolling = false @@ -7072,10 +7094,15 @@ public final class EmojiPagerContentComponent: Component { var itemGroups: [ItemGroup] = [] var itemGroupIndexById: [AnyHashable: Int] = [:] - let appendUnicodeEmoji = { + let maybeAppendUnicodeEmoji = { + let groupId: AnyHashable = "static" + + if itemGroupIndexById[groupId] != nil { + return + } + if areUnicodeEmojiEnabled { for (subgroupId, list) in staticEmojiMapping { - let groupId: AnyHashable = "static" for emojiString in list { let resultItem = EmojiPagerContentComponent.Item( animationData: nil, @@ -7097,10 +7124,6 @@ public final class EmojiPagerContentComponent: Component { } } - if !hasPremium { - appendUnicodeEmoji() - } - var installedCollectionIds = Set() for (id, _, _) in view.collectionInfos { installedCollectionIds.insert(id) @@ -7744,6 +7767,10 @@ public final class EmojiPagerContentComponent: Component { } } + if !hasPremium { + maybeAppendUnicodeEmoji() + } + if areCustomEmojiEnabled { for entry in view.entries { guard let item = entry.item as? StickerPackItem else { @@ -7889,7 +7916,7 @@ public final class EmojiPagerContentComponent: Component { } if hasPremium { - appendUnicodeEmoji() + maybeAppendUnicodeEmoji() } var displaySearchWithPlaceholder: String? diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift index 255699b257..bd542f9918 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift @@ -401,7 +401,8 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: true, - hideBackground: false + hideBackground: false, + stateContext: nil ) self.dataDisposable = ( diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 7b92eacbbc..a36e244c2f 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -1013,7 +1013,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { externalBackground: nil, externalExpansionView: nil, useOpaqueTheme: true, - hideBackground: false + hideBackground: false, + stateContext: nil ) } } diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index fb828fb0a3..9db9edbbb1 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -328,11 +328,13 @@ public final class AccountContextImpl: AccountContext { }) self.currentCountriesConfiguration = Atomic(value: CountriesConfiguration(countries: loadCountryCodes())) - let currentCountriesConfiguration = self.currentCountriesConfiguration - self.countriesConfigurationDisposable = (self.engine.localization.getCountriesList(accountManager: sharedContext.accountManager, langCode: nil) - |> deliverOnMainQueue).start(next: { value in - let _ = currentCountriesConfiguration.swap(CountriesConfiguration(countries: value)) - }) + if !temp { + let currentCountriesConfiguration = self.currentCountriesConfiguration + self.countriesConfigurationDisposable = (self.engine.localization.getCountriesList(accountManager: sharedContext.accountManager, langCode: nil) + |> deliverOnMainQueue).start(next: { value in + let _ = currentCountriesConfiguration.swap(CountriesConfiguration(countries: value)) + }) + } let queue = Queue() self.deviceSpecificContactImportContexts = QueueLocalObject(queue: queue, generate: { diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 78a5260652..27044decc3 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -2399,7 +2399,7 @@ private func extractAccountManagerState(records: AccountRecordsView deliverOnMainQueue).start(next: { displayNames in - self.registerForNotifications(replyString: presentationData.strings.Notification_Reply, messagePlaceholderString: presentationData.strings.Conversation_InputTextPlaceholder, hiddenContentString: presentationData.strings.Watch_MessageView_Title, includeNames: displayNames, authorize: authorize, completion: completion) + self.registerForNotifications(replyString: presentationData.strings.Notification_Reply, messagePlaceholderString: presentationData.strings.Conversation_InputTextPlaceholder, hiddenContentString: presentationData.strings.Watch_MessageView_Title, hiddenReactionContentString: presentationData.strings.Notification_LockScreenReactionPlaceholder, includeNames: displayNames, authorize: authorize, completion: completion) }) } - private func registerForNotifications(replyString: String, messagePlaceholderString: String, hiddenContentString: String, includeNames: Bool, authorize: Bool = true, completion: @escaping (Bool) -> Void = { _ in }) { + private func registerForNotifications(replyString: String, messagePlaceholderString: String, hiddenContentString: String, hiddenReactionContentString: String, includeNames: Bool, authorize: Bool = true, completion: @escaping (Bool) -> Void = { _ in }) { let notificationCenter = UNUserNotificationCenter.current() Logger.shared.log("App \(self.episodeId)", "register for notifications: get settings (authorize: \(authorize))") notificationCenter.getNotificationSettings(completionHandler: { settings in @@ -2525,6 +2525,7 @@ private func extractAccountManagerState(records: AccountRecordsView() private var didInitializeInputMediaNodeDataPromise: Bool = false private var inputMediaNodeDataDisposable: Disposable? + private var inputMediaNodeStateContext = ChatEntityKeyboardInputNode.StateContext() let navigateButtons: ChatHistoryNavigationButtons @@ -227,7 +228,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { private var isLoadingValue: Bool = false private var isLoadingEarlier: Bool = false private func updateIsLoading(isLoading: Bool, earlier: Bool, animated: Bool) { - let useLoadingPlaceholder = self.chatLocation.peerId?.namespace != Namespaces.Peer.CloudUser + let useLoadingPlaceholder = "".isEmpty let updated = isLoading != self.isLoadingValue || (isLoading && earlier && !self.isLoadingEarlier) @@ -2640,7 +2641,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { defaultToEmojiTab: !self.chatPresentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty || self.chatPresentationInterfaceState.interfaceState.forwardMessageIds != nil || self.openStickersBeginWithEmoji, controllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction, - chatPeerId: peerId + chatPeerId: peerId, + stateContext: self.inputMediaNodeStateContext ) self.openStickersBeginWithEmoji = false diff --git a/submodules/TelegramUI/Sources/ChatLoadingNode.swift b/submodules/TelegramUI/Sources/ChatLoadingNode.swift index bb015b4a8e..2bbea87b3a 100644 --- a/submodules/TelegramUI/Sources/ChatLoadingNode.swift +++ b/submodules/TelegramUI/Sources/ChatLoadingNode.swift @@ -360,7 +360,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { let messageContainer = self.messageContainers[k] let messageSize = messageContainer.frame.size - messageContainer.update(size: size, hasAvatar: self.chatType != .channel, rect: CGRect(origin: CGPoint(x: 0.0, y: offset - messageSize.height), size: messageSize), transition: transition) + messageContainer.update(size: size, hasAvatar: self.chatType != .channel && self.chatType != .user, rect: CGRect(origin: CGPoint(x: 0.0, y: offset - messageSize.height), size: messageSize), transition: transition) offset -= messageSize.height } } @@ -388,6 +388,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { enum ChatType: Equatable { case generic + case user case group case channel } @@ -395,7 +396,9 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { func updatePresentationInterfaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState) { var chatType: ChatType = .channel if let peer = chatPresentationInterfaceState.renderedPeer?.peer { - if peer is TelegramGroup { + if peer is TelegramUser { + chatType = .user + } else if peer is TelegramGroup { chatType = .group } else if let channel = peer as? TelegramChannel { if case .group = channel.info { @@ -469,7 +472,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { for messageContainer in self.messageContainers { let messageSize = dimensions[index % 14] - messageContainer.update(size: bounds.size, hasAvatar: self.chatType != .channel, rect: CGRect(origin: CGPoint(x: 0.0, y: bounds.size.height - insets.bottom - offset - messageSize.height), size: messageSize), transition: transition) + messageContainer.update(size: bounds.size, hasAvatar: self.chatType != .channel && self.chatType != .user, rect: CGRect(origin: CGPoint(x: 0.0, y: bounds.size.height - insets.bottom - offset - messageSize.height), size: messageSize), transition: transition) offset += messageSize.height index += 1 } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 8aed6eda5b..6a1ec04f8a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -1431,8 +1431,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } else { if isCrosspostFromChannel, let sourceReference = sourceReference, let _ = firstMessage.peers[sourceReference.messageId.peerId] as? TelegramChannel { authorIsChannel = true + authorRank = attributes.rank + } else { + authorRank = attributes.rank + if authorRank == nil && message.author?.id == peer.id { + authorRank = .admin + } } - authorRank = attributes.rank } } else { if isCrosspostFromChannel, let _ = firstMessage.forwardInfo?.source as? TelegramChannel { diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index 2a6388245d..8341979f19 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -168,7 +168,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { let messageText: NSAttributedString if isText { - var text = arguments.message.text + var text = foldLineBreaks(arguments.message.text) var messageEntities = arguments.message.textEntitiesAttribute?.entities ?? [] if let translateToLanguage = arguments.associatedData.translateToLanguage, !text.isEmpty { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 8c99cffcda..75015054fe 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -5171,27 +5171,28 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate subItems.append(.separator) - if case .group = channel.info { - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in - return nil - }, textLinkAction: { [weak c] in - c?.dismiss(completion: nil) - + let baseText: String + if case .broadcast = channel.info { + baseText = strongSelf.presentationData.strings.PeerInfo_ChannelAutoDeleteInfo + } else { + baseText = strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + } + + subItems.append(.action(ContextMenuActionItem(text: baseText + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in + return nil + }, textLinkAction: { [weak c] in + c?.dismiss(completion: nil) + + guard let self else { + return + } + self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in guard let self else { return } - self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in - guard let self else { - return - } - self.controller?.view.endEditing(true) - }, contentContext: nil) - }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) - } else { - subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo, textLayout: .multiline, textFont: .small, icon: { _ in - return nil - }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) - } + self.controller?.view.endEditing(true) + }, contentContext: nil) + }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) }))) diff --git a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift index 4f144e12ad..9f19f80371 100644 --- a/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Sources/PeerSelectionControllerNode.swift @@ -203,7 +203,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } if hasFilters { - self.mainContainerNode = ChatListContainerNode(context: context, location: chatListLocation, chatListMode: chatListMode, previewing: false, controlsHistoryPreload: false, isInlineMode: false, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in + self.mainContainerNode = ChatListContainerNode(context: context, controller: nil, location: chatListLocation, chatListMode: chatListMode, previewing: false, controlsHistoryPreload: false, isInlineMode: false, presentationData: presentationData, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filterBecameEmpty: { _ in }, filterEmptyAction: { _ in }, secondaryEmptyAction: { })