diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 81e5101a7f..178835ee82 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9071,3 +9071,5 @@ Sorry for the inconvenience."; "DataUsage.SettingsHelpWifi" = "You can change your auto-download settings for media to reduce data usage when on wifi."; "DataUsage.Reset" = "Reset Statistics"; + +"Conversation.SendWhenOnlineTooltip" = "Long tap to send the message later."; 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/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/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/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/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 360243c674..b5e2bb1294 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -169,6 +169,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case dismissedTrendingEmojiPacks = 35 case audioRateOptionsTip = 36 case translationSuggestion = 37 + case sendWhenOnlineTip = 38 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -374,6 +375,10 @@ private struct ApplicationSpecificNoticeKeys { static func audioRateOptionsTip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.audioRateOptionsTip.key) } + + static func sendWhenOnlineTip() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sendWhenOnlineTip.key) + } } public struct ApplicationSpecificNotice { @@ -1327,6 +1332,30 @@ public struct ApplicationSpecificNotice { } } + public static func getSendWhenOnlineTip(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Int32 in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.sendWhenOnlineTip())?.get(ApplicationSpecificCounterNotice.self) { + return value.value + } else { + return 0 + } + } + } + + public static func incrementSendWhenOnlineTip(accountManager: AccountManager, count: Int32 = 1) -> Signal { + return accountManager.transaction { transaction -> Void in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.sendWhenOnlineTip())?.get(ApplicationSpecificCounterNotice.self) { + currentValue = value.value + } + currentValue += count + + if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.sendWhenOnlineTip(), entry) + } + } + } + public static func reset(accountManager: AccountManager) -> Signal { return accountManager.transaction { transaction -> Void in } 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/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 0e3ca261b1..10b0c69025 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 @@ -431,6 +431,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private weak var messageTooltipController: TooltipController? private weak var videoUnmuteTooltipController: TooltipController? private var didDisplayVideoUnmuteTooltip = false + private var didDisplaySendWhenOnlineTip = false private weak var silentPostTooltipController: TooltipController? private weak var mediaRecordingModeTooltipController: TooltipController? private weak var mediaRestrictedTooltipController: TooltipController? @@ -3875,16 +3876,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 @@ -7572,6 +7570,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G Signal.single(false) |> delay(4.0, queue: Queue.mainQueue()) )) + + if !strongSelf.didDisplaySendWhenOnlineTip { + strongSelf.didDisplaySendWhenOnlineTip = true + Queue.mainQueue().after(2.0) { + strongSelf.displaySendWhenOnlineTooltip() + } + } } else { strongSelf.typingActivityPromise.set(.single(false)) } @@ -9878,6 +9883,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G sendWhenOnlineAvailable = false } + if sendWhenOnlineAvailable { + let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: strongSelf.context.sharedContext.accountManager, count: 4).start() + } + let controller = ChatSendMessageActionSheetController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputNode: textInputNode, canSendWhenOnline: sendWhenOnlineAvailable, completion: { [weak self] in if let strongSelf = self { strongSelf.supportedOrientations = previousSupportedOrientations @@ -17507,6 +17516,59 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + private func displaySendWhenOnlineTooltip() { + guard let rect = self.chatDisplayNode.frameForInputActionButton(), self.effectiveNavigationController?.topViewController === self, let peerId = self.chatLocation.peerId else { + return + } + let inputText = self.presentationInterfaceState.interfaceState.effectiveInputState.inputText.string + guard !inputText.isEmpty else { + return + } + + self.sendingOptionsTooltipController?.dismiss() + + let _ = (ApplicationSpecificNotice.getSendWhenOnlineTip(accountManager: self.context.sharedContext.accountManager) + |> deliverOnMainQueue).start(next: { [weak self] counter in + if let strongSelf = self, counter < 3 { + let _ = (strongSelf.context.account.viewTracker.peerView(peerId) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] peerView in + guard let strongSelf = self, let peer = peerViewMainPeer(peerView) else { + return + } + var sendWhenOnlineAvailable = false + if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status { + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if currentTime > until { + sendWhenOnlineAvailable = true + } + } + if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 { + sendWhenOnlineAvailable = false + } + + if sendWhenOnlineAvailable { + let _ = ApplicationSpecificNotice.incrementSendWhenOnlineTip(accountManager: strongSelf.context.sharedContext.accountManager).start() + + let tooltipController = TooltipController(content: .text(strongSelf.presentationData.strings.Conversation_SendWhenOnlineTooltip), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize, timeout: 3.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 2.0) + strongSelf.sendingOptionsTooltipController = tooltipController + tooltipController.dismissed = { [weak self, weak tooltipController] _ in + if let strongSelf = self, let tooltipController = tooltipController, strongSelf.sendingOptionsTooltipController === tooltipController { + strongSelf.sendingOptionsTooltipController = nil + } + } + strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in + if let strongSelf = self { + return (strongSelf.chatDisplayNode, rect) + } + return nil + })) + } + }) + } + }) + } + private func displaySendingOptionsTooltip() { guard let rect = self.chatDisplayNode.frameForInputActionButton(), self.effectiveNavigationController?.topViewController === self else { return 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/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 }