diff --git a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index be8ba1b554..6b4edc58d2 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -320,7 +320,8 @@ public final class AuthorizationSequenceController: NavigationController, MFMail let bold = MarkdownAttributeSet(font: Font.semibold(self.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) if let _ = resetPendingDate { self.actionDisposable.set( - resetLoginEmail(account: self.account, phoneNumber: number, phoneCodeHash: phoneCodeHash).start(error: { [weak self] error in + (resetLoginEmail(account: self.account, phoneNumber: number, phoneCodeHash: phoneCodeHash) + |> deliverOnMainQueue).start(error: { [weak self] error in if let self, case .alreadyInProgress = error { let formattedNumber = formatPhoneNumber(number) let title = NSAttributedString(string: self.presentationData.strings.Login_Email_PremiumRequiredTitle, font: Font.semibold(self.presentationData.listsFontSize.baseDisplaySize), textColor: self.presentationData.theme.actionSheet.primaryTextColor) diff --git a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift index 752e4bd0b7..0602b878ef 100644 --- a/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotCheckoutNativeCardEntryControllerNode.swift @@ -115,7 +115,7 @@ final class BotCheckoutNativeCardEntryControllerNode: ViewControllerTracingNode, sectionItems.append(BotPaymentHeaderItemNode(text: strings.Checkout_NewCard_CardholderNameTitle)) - let cardholderItem = BotPaymentFieldItemNode(title: "", placeholder: strings.Checkout_NewCard_CardholderNamePlaceholder, contentType: .name) + let cardholderItem = BotPaymentFieldItemNode(title: "", placeholder: strings.Checkout_NewCard_CardholderNamePlaceholder, contentType: .asciiName) self.cardholderItem = cardholderItem sectionItems.append(cardholderItem) diff --git a/submodules/BotPaymentsUI/Sources/BotPaymentFieldItemNode.swift b/submodules/BotPaymentsUI/Sources/BotPaymentFieldItemNode.swift index ad28dc16e7..41150436ee 100644 --- a/submodules/BotPaymentsUI/Sources/BotPaymentFieldItemNode.swift +++ b/submodules/BotPaymentsUI/Sources/BotPaymentFieldItemNode.swift @@ -9,6 +9,7 @@ private let titleFont = Font.regular(17.0) enum BotPaymentFieldContentType { case generic case name + case asciiName case phoneNumber case email case address @@ -51,6 +52,9 @@ final class BotPaymentFieldItemNode: BotPaymentItemNode, UITextFieldDelegate { case .generic: break case .name: + self.textField.textField.autocorrectionType = .no + self.textField.textField.keyboardType = .default + case .asciiName: self.textField.textField.autocorrectionType = .no self.textField.textField.keyboardType = .asciiCapable case .address: diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index ecc992b3ad..5af6a04022 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -103,9 +103,10 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch return context.engine.data.get( TelegramEngine.EngineData.Item.Peer.IsContact(id: peer.id), TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peer.id), + TelegramEngine.EngineData.Item.NotificationSettings.Global(), TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: peer.id) ) - |> map { [weak chatListController] isContact, notificationSettings, readCounters -> [ContextMenuItem] in + |> map { [weak chatListController] isContact, notificationSettings, globalNotificationSettings, readCounters -> [ContextMenuItem] in if promoInfo != nil { return [] } @@ -164,8 +165,21 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } var isMuted = false - if case .muted = notificationSettings.muteState { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { isMuted = true + } else if case .default = notificationSettings.muteState { + if case .user = peer { + isMuted = !globalNotificationSettings.privateChats.enabled + } else if case .legacyGroup = peer { + isMuted = !globalNotificationSettings.groupChats.enabled + } else if case let .channel(channel) = peer { + switch channel.info { + case .group: + isMuted = !globalNotificationSettings.groupChats.enabled + case .broadcast: + isMuted = !globalNotificationSettings.channels.enabled + } + } } var isUnread = false @@ -406,8 +420,21 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch if !isSavedMessages { var isMuted = false - if case .muted = notificationSettings.muteState { + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { isMuted = true + } else if case .default = notificationSettings.muteState { + if case .user = peer { + isMuted = !globalNotificationSettings.privateChats.enabled + } else if case .legacyGroup = peer { + isMuted = !globalNotificationSettings.groupChats.enabled + } else if case let .channel(channel) = peer { + switch channel.info { + case .group: + isMuted = !globalNotificationSettings.groupChats.enabled + case .broadcast: + isMuted = !globalNotificationSettings.channels.enabled + } + } } items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { _, f in let _ = (context.engine.peers.togglePeerMuted(peerId: peerId, threadId: nil) @@ -506,9 +533,10 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: return context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peerId), - TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId) + TelegramEngine.EngineData.Item.Peer.ThreadData(id: peerId, threadId: threadId), + TelegramEngine.EngineData.Item.NotificationSettings.Global() ) - |> mapToSignal { peer, peerNotificationSettings, threadData -> Signal<[ContextMenuItem], NoError> in + |> mapToSignal { peer, peerNotificationSettings, threadData, globalNotificationSettings -> Signal<[ContextMenuItem], NoError> in guard case let .channel(channel) = peer else { return .single([]) } @@ -560,9 +588,15 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId: case .unmuted: isMuted = false case .default: - if case .muted = peerNotificationSettings.muteState { - isMuted = true + var peerIsMuted = false + if case let .muted(until) = peerNotificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + peerIsMuted = true + } else if case .default = peerNotificationSettings.muteState { + if case let .channel(channel) = peer, case .group = channel.info { + peerIsMuted = !globalNotificationSettings.groupChats.enabled + } } + isMuted = peerIsMuted } items.append(.action(ContextMenuActionItem(text: isMuted ? strings.ChatList_Context_Unmute : strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak chatListController] c, f in if isMuted { diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index a08f64a149..ca4c846ae7 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -38,13 +38,13 @@ private enum ChatListRecentEntryStableId: Hashable { private enum ChatListRecentEntry: Comparable, Identifiable { case topPeers([EnginePeer], PresentationTheme, PresentationStrings) - case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder) + case peer(index: Int, peer: RecentlySearchedPeer, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, PresentationPersonNameOrder, EngineGlobalNotificationSettings) var stableId: ChatListRecentEntryStableId { switch self { case .topPeers: return .topPeers - case let .peer(_, peer, _, _, _, _, _): + case let .peer(_, peer, _, _, _, _, _, _): return .peerId(peer.peer.peerId) } } @@ -66,8 +66,8 @@ private enum ChatListRecentEntry: Comparable, Identifiable { } else { return false } - case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder): - if case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings && lhsTimeFormat == rhsTimeFormat && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder { + case let .peer(lhsIndex, lhsPeer, lhsTheme, lhsStrings, lhsTimeFormat, lhsSortOrder, lhsDisplayOrder, lhsGlobalNotificationsSettings): + if case let .peer(rhsIndex, rhsPeer, rhsTheme, rhsStrings, rhsTimeFormat, rhsSortOrder, rhsDisplayOrder, rhsGlobalNotificationsSettings) = rhs, lhsPeer == rhsPeer && lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings && lhsTimeFormat == rhsTimeFormat && lhsSortOrder == rhsSortOrder && lhsDisplayOrder == rhsDisplayOrder && lhsGlobalNotificationsSettings == rhsGlobalNotificationsSettings { return true } else { return false @@ -79,11 +79,11 @@ private enum ChatListRecentEntry: Comparable, Identifiable { switch lhs { case .topPeers: return true - case let .peer(lhsIndex, _, _, _, _, _, _): + case let .peer(lhsIndex, _, _, _, _, _, _, _): switch rhs { case .topPeers: return false - case let .peer(rhsIndex, _, _, _, _, _, _): + case let .peer(rhsIndex, _, _, _, _, _, _, _): return lhsIndex <= rhsIndex } } @@ -101,7 +101,7 @@ private enum ChatListRecentEntry: Comparable, Identifiable { gesture?.cancel() } }) - case let .peer(_, peer, theme, strings, timeFormat, nameSortOrder, nameDisplayOrder): + case let .peer(_, peer, theme, strings, timeFormat, nameSortOrder, nameDisplayOrder, globalNotificationSettings): let primaryPeer: EnginePeer var chatPeer: EnginePeer? let maybeChatPeer = EnginePeer(peer.peer.peers[peer.peer.peerId]!) @@ -185,11 +185,27 @@ private enum ChatListRecentEntry: Comparable, Identifiable { } else { status = .none } - + var isMuted = false if let notificationSettings = peer.notificationSettings { - isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false) + if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { + isMuted = true + } else if case .default = notificationSettings.muteState { + if case .user = primaryPeer { + isMuted = !globalNotificationSettings.privateChats.enabled + } else if case .legacyGroup = primaryPeer { + isMuted = !globalNotificationSettings.groupChats.enabled + } else if case let .channel(channel) = primaryPeer { + switch channel.info { + case .group: + isMuted = !globalNotificationSettings.groupChats.enabled + case .broadcast: + isMuted = !globalNotificationSettings.channels.enabled + } + } + } } + var badge: ContactsPeerItemBadge? if peer.unreadCount > 0 { badge = ContactsPeerItemBadge(count: peer.unreadCount, type: isMuted ? .inactive : .active) @@ -1374,7 +1390,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { context.engine.contacts.searchLocalPeers(query: query.lowercased()), fixedOrRemovedRecentlySearchedPeers ) - |> mapToSignal { local, allRecentlySearched -> Signal<([EnginePeer.Id: Optional], [EnginePeer.Id: Int], [EngineRenderedPeer], Set), NoError> in + |> mapToSignal { local, allRecentlySearched -> Signal<([EnginePeer.Id: Optional], [EnginePeer.Id: Int], [EngineRenderedPeer], Set, EngineGlobalNotificationSettings), NoError> in let recentlySearched = allRecentlySearched.filter { peer in guard let peer = peer.peer.peer else { return false @@ -1408,25 +1424,37 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { peerIds.map { peerId -> TelegramEngine.EngineData.Item.Messages.PeerUnreadCount in return TelegramEngine.EngineData.Item.Messages.PeerUnreadCount(id: peerId) } - ) + ), + TelegramEngine.EngineData.Item.NotificationSettings.Global() ) - |> map { notificationSettings, unreadCounts in - return (notificationSettings, unreadCounts, peers, Set(recentlySearched.map(\.peer.peerId))) + |> map { notificationSettings, unreadCounts, globalNotificationSettings in + return (notificationSettings, unreadCounts, peers, Set(recentlySearched.map(\.peer.peerId)), globalNotificationSettings) } } - |> map { notificationSettings, unreadCounts, peers, recentlySearchedPeerIds -> (peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set) in + |> map { notificationSettings, unreadCounts, peers, recentlySearchedPeerIds, globalNotificationSettings -> (peers: [EngineRenderedPeer], unread: [EnginePeer.Id: (Int32, Bool)], recentlySearchedPeerIds: Set) in var unread: [EnginePeer.Id: (Int32, Bool)] = [:] for peer in peers { - var isMuted: Bool = false - if let nofiticationSettings = notificationSettings[peer.peerId] { - switch nofiticationSettings?.muteState { - case .muted: + var isMuted = false + if let peerNotificationSettings = notificationSettings[peer.peerId], let peerNotificationSettings { + if case let .muted(until) = peerNotificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) { isMuted = true - default: - break + } else if case .default = peerNotificationSettings.muteState { + if let peer = peer.peer { + if case .user = peer { + isMuted = !globalNotificationSettings.privateChats.enabled + } else if case .legacyGroup = peer { + isMuted = !globalNotificationSettings.groupChats.enabled + } else if case let .channel(channel) = peer { + switch channel.info { + case .group: + isMuted = !globalNotificationSettings.groupChats.enabled + case .broadcast: + isMuted = !globalNotificationSettings.channels.enabled + } + } + } } } - let unreadCount = unreadCounts[peer.peerId] if let unreadCount = unreadCount, unreadCount > 0 { unread[peer.peerId] = (Int32(unreadCount), isMuted) @@ -2399,8 +2427,13 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } |> distinctUntilChanged - var recentItems = combineLatest(hasRecentPeers, fixedRecentlySearchedPeers, presentationDataPromise.get()) - |> mapToSignal { hasRecentPeers, peers, presentationData -> Signal<[ChatListRecentEntry], NoError> in + var recentItems = combineLatest( + hasRecentPeers, + fixedRecentlySearchedPeers, + presentationDataPromise.get(), + context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()) + ) + |> mapToSignal { hasRecentPeers, peers, presentationData, globalNotificationSettings -> Signal<[ChatListRecentEntry], NoError> in var entries: [ChatListRecentEntry] = [] if !peersFilter.contains(.onlyGroups) { if hasRecentPeers { @@ -2419,7 +2452,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { } peerIds.insert(peer.id) - entries.append(.peer(index: index, peer: searchedPeer, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameSortOrder, presentationData.nameDisplayOrder)) + entries.append(.peer(index: index, peer: searchedPeer, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.nameSortOrder, presentationData.nameDisplayOrder, globalNotificationSettings)) index += 1 } } diff --git a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift index a8e6776991..6120890cf6 100644 --- a/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift +++ b/submodules/ChatListUI/Sources/TabBarChatListFilterController.swift @@ -32,8 +32,11 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL for groupId in additionalGroupIds { unreadCountItems.append(.totalInGroup(groupId)) } + + let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.globalNotifications])) let unreadKey: PostboxViewKey = .unreadCounts(items: unreadCountItems) var keys: [PostboxViewKey] = [] + keys.append(globalNotificationsKey) keys.append(unreadKey) for peerId in additionalPeerIds { keys.append(.basicPeer(peerId)) @@ -45,6 +48,13 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL return (0, []) } + var globalNotificationSettings: GlobalNotificationSettingsSet + if let settingsView = view.views[globalNotificationsKey] as? PreferencesView, let settings = settingsView.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) { + globalNotificationSettings = settings.effective + } else { + globalNotificationSettings = GlobalNotificationSettings.defaultSettings.effective + } + var result: [(ChatListFilter, Int, Bool)] = [] var peerTagAndCount: [PeerId: (PeerSummaryCounterTags, Int, Bool, PeerGroupId?, Bool)] = [:] @@ -66,7 +76,28 @@ public func chatListFilterItems(context: AccountContext) -> Signal<(Int, [(ChatL peerCount = max(1, peerCount) } - if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings, case .muted = notificationSettings.muteState { + var isMuted = false + if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings { + if case .muted = notificationSettings.muteState { + isMuted = true + } else if case .default = notificationSettings.muteState { + if let peer = peerView.peer { + if peer is TelegramUser { + isMuted = !globalNotificationSettings.privateChats.enabled + } else if peer is TelegramGroup { + isMuted = !globalNotificationSettings.groupChats.enabled + } else if let channel = peer as? TelegramChannel { + switch channel.info { + case .group: + isMuted = !globalNotificationSettings.groupChats.enabled + case .broadcast: + isMuted = !globalNotificationSettings.channels.enabled + } + } + } + } + } + if isMuted { peerTagAndCount[peerId] = (tag, peerCount, false, peerView.groupId, true) } else { peerTagAndCount[peerId] = (tag, peerCount, true, peerView.groupId, false) diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 7bb59da58d..892d0d3bb2 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -1328,16 +1328,19 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } else if let file = m as? TelegramMediaFile { subject = .media(.message(message: MessageReference(messages[0]._asMessage()), media: file)) if file.isAnimated { - preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Preview_SaveGif, action: { [weak self] in - if let strongSelf = self { - let message = messages[0] - - let context = strongSelf.context - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - let controllerInteraction = strongSelf.controllerInteraction - let _ = (toggleGifSaved(account: context.account, fileReference: .message(message: MessageReference(message._asMessage()), media: file), saved: true) - |> deliverOnMainQueue).start(next: { result in - switch result { + if messages[0].id.peerId.namespace == Namespaces.Peer.SecretChat { + preferredAction = .default + } else { + preferredAction = .custom(action: ShareControllerAction(title: presentationData.strings.Preview_SaveGif, action: { [weak self] in + if let strongSelf = self { + let message = messages[0] + + let context = strongSelf.context + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controllerInteraction = strongSelf.controllerInteraction + let _ = (toggleGifSaved(account: context.account, fileReference: .message(message: MessageReference(message._asMessage()), media: file), saved: true) + |> deliverOnMainQueue).start(next: { result in + switch result { case .generic: controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved, customUndoText: nil, timeout: nil), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false }), nil) case let .limitExceeded(limit, premiumLimit): @@ -1356,10 +1359,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } return false }), nil) - } - }) - } - })) + } + }) + } + })) + } } else if file.mimeType.hasPrefix("image/") { preferredAction = .saveToCameraRoll actionCompletionText = strongSelf.presentationData.strings.Gallery_ImageSaved diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index 37bd422bfa..a2c1817353 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -1195,7 +1195,7 @@ public final class ListMessageFileItemNode: ListMessageNode { strongSelf.currentIconImage = iconImage if let updateIconImageSignal, let iconImage, case .albumArt = iconImage { - strongSelf.iconStatusNode.setBackgroundImage(updateIconImageSignal) + strongSelf.iconStatusNode.setBackgroundImage(updateIconImageSignal, size: CGSize(width: 40.0, height: 40.0)) } if let iconImageApply = iconImageApply { diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift index bf94a0446c..2d845df038 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift @@ -234,7 +234,7 @@ public final class MediaGroupsScreen: ViewController { albums.append(collection) } } - state.albums.enumerateObjects { collection, _, _ in + state.albums.enumerateObjects(options: [.reverse]) { collection, _, _ in albums.append(collection) } entries.append(.albums(self.presentationData.theme, albums)) diff --git a/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift b/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift index 440082d668..37fd617d91 100644 --- a/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift +++ b/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift @@ -864,16 +864,19 @@ public final class SemanticStatusNode: ASControlNode { } } - public func setBackgroundImage(_ image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>) { + public func setBackgroundImage(_ image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, size: CGSize) { let start = CACurrentMediaTime() - self.disposable = combineLatest(queue: Queue.mainQueue(), image, self.hasLayoutPromise.get()).start(next: { [weak self] transform, ready in + let imageSignal: Signal = image + |> map { transform -> UIImage? in + let context = transform(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets())) + return context?.generateImage() + } + self.disposable = combineLatest(queue: Queue.mainQueue(), imageSignal, self.hasLayoutPromise.get()).start(next: { [weak self] image, ready in guard let strongSelf = self, ready else { return } - let context = transform(TransformImageArguments(corners: ImageCorners(radius: strongSelf.bounds.width / 2.0), imageSize: strongSelf.bounds.size, boundingSize: strongSelf.bounds.size, intrinsicInsets: UIEdgeInsets())) - let previousAppearanceContext = strongSelf.appearanceContext - strongSelf.appearanceContext = strongSelf.appearanceContext.withUpdatedBackgroundImage(context?.generateImage()) + strongSelf.appearanceContext = strongSelf.appearanceContext.withUpdatedBackgroundImage(image) if CACurrentMediaTime() - start > 0.3 { strongSelf.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: nil, previousAppearanceContext: previousAppearanceContext, completion: {}) @@ -915,7 +918,7 @@ public final class SemanticStatusNode: ASControlNode { self.displaysAsynchronously = true if let image { - self.setBackgroundImage(image) + self.setBackgroundImage(image, size: CGSize(width: 44.0, height: 44.0)) } } diff --git a/submodules/SettingsUI/Sources/Reactions/ItemListReactionItem.swift b/submodules/SettingsUI/Sources/Reactions/ItemListReactionItem.swift index d833e77578..4e244e9371 100644 --- a/submodules/SettingsUI/Sources/Reactions/ItemListReactionItem.swift +++ b/submodules/SettingsUI/Sources/Reactions/ItemListReactionItem.swift @@ -45,13 +45,13 @@ public class ItemListReactionItem: ListViewItem, ItemListItem { public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { async { - let node = ItemListReactionItemNode() - let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) - - node.contentSize = layout.contentSize - node.insets = layout.insets - Queue.mainQueue().async { + let node = ItemListReactionItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + completion(node, { return (nil, { _ in apply() }) }) diff --git a/submodules/SettingsUI/Sources/ThemeCarouselItem.swift b/submodules/SettingsUI/Sources/ThemeCarouselItem.swift index 80fb839bf8..7061318417 100644 --- a/submodules/SettingsUI/Sources/ThemeCarouselItem.swift +++ b/submodules/SettingsUI/Sources/ThemeCarouselItem.swift @@ -711,11 +711,10 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode { strongSelf.item = item strongSelf.layoutParams = params - strongSelf.listNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor - + if strongSelf.backgroundNode.supernode == nil { strongSelf.containerNode.insertSubnode(strongSelf.backgroundNode, at: 0) } @@ -828,10 +827,11 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode { self.snapshotView = snapshotView } - self.listNode.forEachVisibleItemNode { node in + self.listNode.enumerateItemNodes { node in if let node = node as? ThemeCarouselThemeItemIconNode { node.prepareCrossfadeTransition() } + return true } } @@ -839,22 +839,23 @@ class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode { guard self.snapshotView?.layer.animationKeys()?.isEmpty ?? true else { return } - + var views: [UIView] = [] if let snapshotView = self.snapshotView { views.append(snapshotView) self.snapshotView = nil } - - self.listNode.forEachVisibleItemNode { node in + + self.listNode.enumerateItemNodes { node in if let node = node as? ThemeCarouselThemeItemIconNode { if let snapshotView = node.snapshotView { views.append(snapshotView) node.snapshotView = nil } } + return true } - + UIView.animate(withDuration: 0.3, animations: { for view in views { view.alpha = 0.0 diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 1009b43d5b..93eab9cfd5 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -1292,9 +1292,7 @@ public final class ThemeSettingsCrossfadeController: ViewController { public init(view: UIView? = nil, topOffset: CGFloat? = nil, bottomOffset: CGFloat? = nil, leftOffset: CGFloat? = nil, sideInset: CGFloat = 0.0) { if let view = view { - if var leftOffset = leftOffset { - leftOffset += UIScreenPixel - + if let leftOffset = leftOffset { if let view = view.snapshotView(afterScreenUpdates: false) { let clipView = UIView() clipView.clipsToBounds = true @@ -1306,13 +1304,13 @@ public final class ThemeSettingsCrossfadeController: ViewController { if let topOffset = topOffset, let bottomOffset = bottomOffset { var frame = view.frame frame.origin.y = topOffset - frame.size.width = leftOffset + frame.size.width = leftOffset + sideInset frame.size.height = bottomOffset - topOffset clipView.frame = frame frame = view.frame frame.origin.y = -topOffset - frame.size.width = leftOffset + frame.size.width = leftOffset + sideInset frame.size.height = bottomOffset view.frame = frame } @@ -1322,7 +1320,7 @@ public final class ThemeSettingsCrossfadeController: ViewController { } if sideInset > 0.0 { - if let view = view.snapshotView(afterScreenUpdates: false) { + if let view = view.snapshotView(afterScreenUpdates: false), leftOffset == nil { let clipView = UIView() clipView.clipsToBounds = true clipView.addSubview(view) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift index 79a6fca323..61d44d999b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/TelegramEngineData.swift @@ -220,6 +220,40 @@ public extension TelegramEngine { } } + public func subscribe< + T0: TelegramEngineDataItem, + T1: TelegramEngineDataItem, + T2: TelegramEngineDataItem, + T3: TelegramEngineDataItem + >( + _ t0: T0, + _ t1: T1, + _ t2: T2, + _ t3: T3 + ) -> Signal< + ( + T0.Result, + T1.Result, + T2.Result, + T3.Result + ), + NoError> { + return self._subscribe(items: [ + t0 as! AnyPostboxViewDataItem, + t1 as! AnyPostboxViewDataItem, + t2 as! AnyPostboxViewDataItem, + t3 as! AnyPostboxViewDataItem + ]) + |> map { results -> (T0.Result, T1.Result, T2.Result, T3.Result) in + return ( + results[0] as! T0.Result, + results[1] as! T1.Result, + results[2] as! T2.Result, + results[3] as! T3.Result + ) + } + } + public func get< T0: TelegramEngineDataItem, @@ -253,5 +287,26 @@ public extension TelegramEngine { NoError> { return self.subscribe(t0, t1, t2) |> take(1) } + + public func get< + T0: TelegramEngineDataItem, + T1: TelegramEngineDataItem, + T2: TelegramEngineDataItem, + T3: TelegramEngineDataItem + >( + _ t0: T0, + _ t1: T1, + _ t2: T2, + _ t3: T3 + ) -> Signal< + ( + T0.Result, + T1.Result, + T2.Result, + T3.Result + ), + NoError> { + return self.subscribe(t0, t1, t2, t3) |> take(1) + } } } diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index d8eff66339..73684b09d5 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -368,15 +368,12 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var inputActivitiesAllowed = true if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, _, _, isScheduledMessages, _, customMessageCount, _): + case let .peer(peerView, _, _, isScheduledMessages, _, _, _): if let peer = peerViewMainPeer(peerView) { if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isReplies { inputActivitiesAllowed = false } } - if customMessageCount != nil { - inputActivitiesAllowed = false - } case .replyThread: inputActivitiesAllowed = true default: diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 0b62234a2f..6bb3cfc864 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -8051,7 +8051,6 @@ public final class EmojiPagerContentComponent: Component { searchCategories ) |> map { view, hasPremium, featuredStickerPacks, featuredStickersConfiguration, dismissedTrendingStickerPacks, peerSpecificPack, searchCategories -> EmojiPagerContentComponent in - let actuallyHasPremium = hasPremium let hasPremium = forceHasPremium || hasPremium struct ItemGroup { var supergroupId: AnyHashable @@ -8070,14 +8069,11 @@ public final class EmojiPagerContentComponent: Component { var savedStickers: OrderedItemListView? var recentStickers: OrderedItemListView? - var cloudPremiumStickers: OrderedItemListView? for orderedView in view.orderedItemListsViews { if orderedView.collectionId == Namespaces.OrderedItemList.CloudRecentStickers { recentStickers = orderedView } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudSavedStickers { savedStickers = orderedView - } else if orderedView.collectionId == Namespaces.OrderedItemList.CloudAllPremiumStickers { - cloudPremiumStickers = orderedView } } @@ -8225,64 +8221,7 @@ public final class EmojiPagerContentComponent: Component { } } } - - var premiumStickers: [StickerPackItem] = [] - if hasPremium { - for entry in view.entries { - guard let item = entry.item as? StickerPackItem else { - continue - } - - if item.file.isPremiumSticker { - premiumStickers.append(item) - } - } - - if let cloudPremiumStickers = cloudPremiumStickers, !cloudPremiumStickers.items.isEmpty, actuallyHasPremium { - premiumStickers.append(contentsOf: cloudPremiumStickers.items.compactMap { item -> StickerPackItem? in guard let item = item.contents.get(RecentMediaItem.self) else { - return nil - } - return StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: 0), file: item.media, indexKeys: []) - }) - } - } - - if !premiumStickers.isEmpty { - var processedIds = Set() - for item in premiumStickers { - if isPremiumDisabled && item.file.isPremiumSticker { - continue - } - if processedIds.contains(item.file.fileId) { - continue - } - processedIds.insert(item.file.fileId) - - var tintMode: Item.TintMode = .none - if item.file.isCustomTemplateEmoji { - tintMode = .primary - } - - let animationData = EntityKeyboardAnimationData(file: item.file) - let resultItem = EmojiPagerContentComponent.Item( - animationData: animationData, - content: .animation(animationData), - itemFile: item.file, - subgroupId: nil, - icon: .none, - tintMode: tintMode - ) - - let groupId = "premium" - if let groupIndex = itemGroupIndexById[groupId] { - itemGroups[groupIndex].items.append(resultItem) - } else { - itemGroupIndexById[groupId] = itemGroups.count - itemGroups.append(ItemGroup(supergroupId: groupId, id: groupId, title: strings.EmojiInput_SectionTitlePremiumStickers, subtitle: nil, actionButtonTitle: nil, isPremiumLocked: false, isFeatured: false, displayPremiumBadges: false, headerItem: nil, items: [resultItem])) - } - } - } - + var avatarPeer: EnginePeer? if let peerSpecificPack = peerSpecificPack { avatarPeer = peerSpecificPack.peer diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 0e3ca261b1..c218c1d6f8 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -361,7 +361,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var recentlyUsedInlineBotsDisposable: Disposable? private var unpinMessageDisposable: MetaDisposable? - + private let typingActivityPromise = Promise(false) private var inputActivityDisposable: Disposable? private var recordingActivityValue: ChatRecordingActivity = .none @@ -3875,16 +3875,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = strongSelf.presentVoiceMessageDiscardAlert(action: { let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: id)) |> mapToSignal { message -> Signal<(EngineMessage.Id, Int32?)?, NoError> in - if let message = message, let sourceMessageId = message.forwardInfo?.sourceMessageId { - return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StatsDatacenterId(id: sourceMessageId.peerId)) + if let message { + return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StatsDatacenterId(id: message.id.peerId)) |> map { statsDatacenterId -> (EngineMessage.Id, Int32?)? in - return (sourceMessageId, statsDatacenterId) + return (message.id, statsDatacenterId) } } else { - return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StatsDatacenterId(id: id.peerId)) - |> map { statsDatacenterId -> (EngineMessage.Id, Int32?)? in - return (id, statsDatacenterId) - } + return .complete() } } |> deliverOnMainQueue).start(next: { [weak self] messageIdAndStatsDatacenterId in @@ -4106,7 +4103,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return state.updatedShowWebView(true).updatedForceInputCommandsHidden(true) } - let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: true, isInline: false, isSimple: false) + let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: true, fromAttachMenu: false, isInline: false, isSimple: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url in self?.openUrl(url, concealed: true, forceExternal: true) }, getInputContainerNode: { [weak self] in @@ -4161,7 +4158,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - let params = WebAppParameters(peerId: peerId, botId: botId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: false, isInline: isInline, isSimple: true) + let params = WebAppParameters(peerId: peerId, botId: botId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, fromMenu: false, fromAttachMenu: false, isInline: isInline, isSimple: true) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url in self?.openUrl(url, concealed: true, forceExternal: true) }, requestSwitchInline: { [weak self] query, chatTypes, completion in @@ -4201,7 +4198,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, fromMenu: false, isInline: false, isSimple: false) + let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, fromMenu: false, fromAttachMenu: false, isInline: false, isSimple: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url in self?.openUrl(url, concealed: true, forceExternal: true) }, completion: { [weak self] in @@ -12704,7 +12701,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, fromMenu: false, isInline: false, isSimple: false) + let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, fromMenu: false, fromAttachMenu: false, isInline: false, isSimple: false) let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url in self?.openUrl(url, concealed: true, forceExternal: true) }, completion: { [weak self] in @@ -13224,10 +13221,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } case let .app(bot, botName, _): var payload: String? + var fromAttachMenu = true if case let .bot(_, botPayload, _) = subject { payload = botPayload + fromAttachMenu = false } - let params = WebAppParameters(peerId: peer.id, botId: bot.id, botName: botName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, fromMenu: false, isInline: false, isSimple: false) + let params = WebAppParameters(peerId: peer.id, botId: bot.id, botName: botName, url: nil, queryId: nil, payload: payload, buttonText: nil, keepAliveSignal: nil, fromMenu: false, fromAttachMenu: fromAttachMenu, isInline: false, isSimple: false) let replyMessageId = strongSelf.presentationInterfaceState.interfaceState.replyMessageId let controller = WebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, replyToMessageId: replyMessageId, threadId: strongSelf.chatLocation.threadId) controller.openUrl = { [weak self] url in diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 36378e61d9..56421f8581 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1514,7 +1514,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState break } } - if let file = media as? TelegramMediaFile, !isCopyProtected { + if message.id.peerId.namespace == Namespaces.Peer.SecretChat { + + } else if let file = media as? TelegramMediaFile, !isCopyProtected { if file.isVideo { if file.isAnimated && !file.isVideoSticker { actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_SaveGif, icon: { theme in diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index fa3012d9bc..731862597a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -3540,9 +3540,33 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode var mediaMessage: Message? var forceOpen = false if let item = self.item { - for media in item.message.media { - if let file = media as? TelegramMediaFile, file.duration != nil { - mediaMessage = item.message + if case .group = item.content { + var message: Message? = item.content.firstMessage + loop: for contentNode in self.contentNodes { + if !(contentNode is ChatMessageTextBubbleContentNode) { + continue loop + } + let convertedNodeFrame = contentNode.view.convert(contentNode.bounds, to: self.view).insetBy(dx: 0.0, dy: -10.0) + if !convertedNodeFrame.contains(location) { + continue loop + } + if contentNode is ChatMessageEventLogPreviousMessageContentNode { + } else { + message = contentNode.item?.message + } + } + if let message { + for media in message.media { + if let file = media as? TelegramMediaFile, file.duration != nil { + mediaMessage = message + } + } + } + } else { + for media in item.message.media { + if let file = media as? TelegramMediaFile, file.duration != nil { + mediaMessage = item.message + } } } if mediaMessage == nil { diff --git a/submodules/TelegramUI/Sources/ChatTextInputAudioRecordingTimeNode.swift b/submodules/TelegramUI/Sources/ChatTextInputAudioRecordingTimeNode.swift index ba8dde0a1d..7905048a82 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputAudioRecordingTimeNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputAudioRecordingTimeNode.swift @@ -113,7 +113,7 @@ final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode { override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { let makeLayout = TextNode.asyncLayout(self.textNode) - let (size, apply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "00:00,00", font: Font.regular(15.0), textColor: theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 200.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (size, apply) = makeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "0:00:00,00", font: Font.regular(15.0), textColor: theme.chat.inputPanel.primaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 200.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let _ = apply() self.textNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 1.0 + UIScreenPixel), size: size.size) return size.size @@ -135,7 +135,12 @@ final class ChatTextInputAudioRecordingTimeNode: ASDisplayNode { if let parameters = parameters as? ChatTextInputAudioRecordingTimeNodeParameters { let currentAudioDurationSeconds = Int(parameters.timestamp) let currentAudioDurationMilliseconds = Int(parameters.timestamp * 100.0) % 100 - let text = String(format: "%d:%02d,%02d", currentAudioDurationSeconds / 60, currentAudioDurationSeconds % 60, currentAudioDurationMilliseconds) + let text: String + if currentAudioDurationSeconds >= 60 * 60 { + text = String(format: "%d:%02d:%02d,%02d", currentAudioDurationSeconds / 3600, currentAudioDurationSeconds / 60 % 60, currentAudioDurationSeconds % 60, currentAudioDurationMilliseconds) + } else { + text = String(format: "%d:%02d,%02d", currentAudioDurationSeconds / 60, currentAudioDurationSeconds % 60, currentAudioDurationMilliseconds) + } let string = NSAttributedString(string: text, font: textFont, textColor: parameters.theme.chat.inputPanel.primaryTextColor) string.draw(at: CGPoint()) } diff --git a/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift b/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift index 49855b427a..5e03f5aa11 100644 --- a/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/CommandMenuChatInputPanelItem.swift @@ -210,7 +210,7 @@ final class CommandMenuChatInputPanelItemNode: ListViewItemNode { let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 130.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (commandLayout, commandApply) = makeCommandLayout(TextNodeLayoutArguments(attributedString: commandString, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: 120.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (commandLayout, commandApply) = makeCommandLayout(TextNodeLayoutArguments(attributedString: commandString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - rightInset - textLayout.size.width - 16.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: max(CommandMenuChatInputPanelItemNode.itemHeight, textLayout.size.height + 14.0)), insets: UIEdgeInsets()) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 8bc6db0b8f..6e48b0f56f 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4908,15 +4908,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }))) } - let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: user, chatPeer: user, cachedData: strongSelf.data?.cachedData) + let clearPeerHistory = ClearPeerHistory(context: strongSelf.context, peer: user, chatPeer: chatPeer, cachedData: strongSelf.data?.cachedData) if clearPeerHistory.canClearForMyself != nil || clearPeerHistory.canClearForEveryone != nil { - if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser { - items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in - generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) - }, action: { c, _ in - self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: user, chatPeer: user) - }))) - } + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_ClearMessages, icon: { theme in + generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ClearMessages"), color: theme.contextMenu.primaryColor) + }, action: { c, _ in + self?.openClearHistory(contextController: c, clearPeerHistory: clearPeerHistory, peer: user, chatPeer: user) + }))) } if strongSelf.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) { @@ -5560,8 +5558,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }))) } - if let _ = clearPeerHistory.canClearForMyself { - let text: String = self.presentationData.strings.Conversation_DeleteMessagesForMe + if let canClearForMyself = clearPeerHistory.canClearForMyself { + let text: String + switch canClearForMyself { + case .secretChat: + text = self.presentationData.strings.Conversation_DeleteMessagesFor(EnginePeer(chatPeer).compactDisplayTitle).string + default: + text = self.presentationData.strings.Conversation_DeleteMessagesForMe + } subItems.append(.action(ContextMenuActionItem(text: text, textColor: .destructive, icon: { _ in return nil @@ -6012,7 +6016,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } private func openChatWithClearedHistory(type: InteractiveHistoryClearingType) { - guard let peer = self.data?.peer, let navigationController = self.controller?.navigationController as? NavigationController else { + guard let peer = self.data?.chatPeer, let navigationController = self.controller?.navigationController as? NavigationController else { return } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 532ea2fe32..b4dd071d1e 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -117,6 +117,9 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? { if !pathComponents.isEmpty { pathComponents.removeFirst() } + if let lastComponent = pathComponents.last, lastComponent.isEmpty { + pathComponents.removeLast() + } if !pathComponents.isEmpty && !pathComponents[0].isEmpty { let peerName: String = pathComponents[0] if pathComponents.count == 1 { diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index cdd22ea5f5..0095b1c454 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -154,6 +154,7 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver } private var isUsingSoftlight: Bool = false + private var useFilter: Bool = false var suspendCompositionUpdates: Bool = false private var needsCompositionUpdate: Bool = false @@ -172,10 +173,11 @@ private final class EffectImageLayer: SimpleLayer, GradientBackgroundPatternOver useSoftlight = true useFilter = false } - if self.isUsingSoftlight != useSoftlight { + if self.isUsingSoftlight != useSoftlight || self.useFilter != useFilter { self.isUsingSoftlight = useSoftlight + self.useFilter = useFilter - if self.isUsingSoftlight && useFilter { + if self.isUsingSoftlight && self.useFilter { self.compositingFilter = "softLightBlendMode" } else { self.compositingFilter = nil @@ -842,10 +844,6 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } private static var cachedSharedPattern: (PatternKey, UIImage)? - //private var inlineAnimationNodes: [(AnimatedStickerNode, CGPoint)] = [] - //private let hierarchyTrackingLayer = HierarchyTrackingLayer() - //private var activateInlineAnimationTimer: SwiftSignalKit.Timer? - private let _isReady = ValuePromise(false, ignoreRepeated: true) var isReady: Signal { return self._isReady.get() @@ -1308,13 +1306,6 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode } self.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: transition) - - /*for (animationNode, relativePosition) in self.inlineAnimationNodes { - let sizeNorm = CGSize(width: 1440, height: 2960) - let animationSize = CGSize(width: 512.0 / sizeNorm.width * size.width, height: 512.0 / sizeNorm.height * size.height) - animationNode.frame = CGRect(origin: CGPoint(x: relativePosition.x / sizeNorm.width * size.width, y: relativePosition.y / sizeNorm.height * size.height), size: animationSize) - animationNode.updateLayout(size: animationNode.frame.size) - }*/ if isFirstLayout && !self.frame.isEmpty { self.updateScale() diff --git a/submodules/WebSearchUI/Sources/WebSearchController.swift b/submodules/WebSearchUI/Sources/WebSearchController.swift index 7d8bfe60a7..062f6272c9 100644 --- a/submodules/WebSearchUI/Sources/WebSearchController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchController.swift @@ -122,6 +122,9 @@ public final class WebSearchController: ViewController { public var attemptItemSelection: (ChatContextResult) -> Bool = { _ in return true } + private var searchQueryPromise = ValuePromise() + private var searchQueryDisposable: Disposable? + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, chatLocation: ChatLocation?, configuration: EngineConfiguration.SearchBots, mode: WebSearchControllerMode, activateOnDisplay: Bool = true) { self.context = context self.mode = mode @@ -195,7 +198,7 @@ public final class WebSearchController: ViewController { self.navigationContentNode = navigationContentNode navigationContentNode.setQueryUpdated { [weak self] query in if let strongSelf = self, strongSelf.isNodeLoaded { - strongSelf.updateSearchQuery(query) + strongSelf.searchQueryPromise.set(query) strongSelf.searchingUpdated(!query.isEmpty) } } @@ -288,6 +291,23 @@ public final class WebSearchController: ViewController { } }) } + + let throttledSearchQuery = self.searchQueryPromise.get() + |> mapToSignal { query -> Signal in + if !query.isEmpty { + return (.complete() |> delay(0.6, queue: Queue.mainQueue())) + |> then(.single(query)) + } else { + return .single(query) + } + } + + self.searchQueryDisposable = (throttledSearchQuery + |> deliverOnMainQueue).start(next: { [weak self] query in + if let self { + self.updateSearchQuery(query) + } + }) } required public init(coder aDecoder: NSCoder) { @@ -298,6 +318,7 @@ public final class WebSearchController: ViewController { self.disposable?.dispose() self.resultsDisposable.dispose() self.selectionDisposable?.dispose() + self.searchQueryDisposable?.dispose() } public func cancel() { diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 1b29b48576..7797e366b3 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -134,6 +134,7 @@ public struct WebAppParameters { let buttonText: String? let keepAliveSignal: Signal? let fromMenu: Bool + let fromAttachMenu: Bool let isInline: Bool let isSimple: Bool @@ -147,6 +148,7 @@ public struct WebAppParameters { buttonText: String?, keepAliveSignal: Signal?, fromMenu: Bool, + fromAttachMenu: Bool, isInline: Bool, isSimple: Bool ) { @@ -159,6 +161,7 @@ public struct WebAppParameters { self.buttonText = buttonText self.keepAliveSignal = keepAliveSignal self.fromMenu = fromMenu + self.fromAttachMenu = fromAttachMenu self.isInline = isInline self.isSimple = isSimple } @@ -656,7 +659,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.handleSendData(data: eventData) } case "web_app_setup_main_button": - if let webView = self.webView, !webView.didTouchOnce && controller.url == nil { + if let webView = self.webView, !webView.didTouchOnce && controller.url == nil && controller.fromAttachMenu { self.delayedScriptMessage = message } else if let json = json { if var isVisible = json["is_visible"] as? Bool { @@ -1058,6 +1061,7 @@ public final class WebAppController: ViewController, AttachmentContainable { private let payload: String? private let buttonText: String? private let fromMenu: Bool + private let fromAttachMenu: Bool private let isInline: Bool private let isSimple: Bool private let keepAliveSignal: Signal? @@ -1083,6 +1087,7 @@ public final class WebAppController: ViewController, AttachmentContainable { self.payload = params.payload self.buttonText = params.buttonText self.fromMenu = params.fromMenu + self.fromAttachMenu = params.fromAttachMenu self.isInline = params.isInline self.isSimple = params.isSimple self.keepAliveSignal = params.keepAliveSignal