diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 78b80dfbb2..9faeb8ac77 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -2820,6 +2820,120 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController self.push(PeerInfoStoryGridScreen(context: self.context, peerId: self.context.account.peerId, scope: .archive)) }) }))) + } else if case .channel = peer { + //TODO:localize + items.append(.action(ContextMenuActionItem(text: "Open Channel", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Channels"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] c, _ in + c.dismiss(completion: { + guard let self else { + return + } + + let _ = (self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peer.id) + ) + |> deliverOnMainQueue).start(next: { [weak self] peer in + guard let self, let peer else { + return + } + guard let navigationController = self.navigationController as? NavigationController else { + return + } + + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer))) + }) + }) + }))) + + let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: peer._asPeer(), peerSettings: notificationSettings._asNotificationSettings(), topSearchPeers: topSearchPeers) + items.append(.action(ContextMenuActionItem(text: isMuted ? self.presentationData.strings.StoryFeed_ContextNotifyOn : self.presentationData.strings.StoryFeed_ContextNotifyOff, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.default) + + guard let self else { + return + } + let _ = self.context.engine.peers.togglePeerStoriesMuted(peerId: peer.id).start() + + let iconColor = UIColor.white + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + if isMuted { + self.present(UndoOverlayController( + presentationData: presentationData, + content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: presentationData.strings.StoryFeed_TooltipNotifyOn(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil), + elevatedLayout: false, + animateInAsReplacement: false, + action: { _ in return false } + ), in: .current) + } else { + self.present(UndoOverlayController( + presentationData: presentationData, + content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: presentationData.strings.StoryFeed_TooltipNotifyOff(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string, customUndoText: nil, timeout: nil), + elevatedLayout: false, + animateInAsReplacement: false, + action: { _ in return false } + ), in: .current) + } + }))) + + let hideText: String + if self.location == .chatList(groupId: .archive) { + hideText = self.presentationData.strings.StoryFeed_ContextUnarchive + } else { + hideText = self.presentationData.strings.StoryFeed_ContextArchive + } + let iconName = self.location == .chatList(groupId: .archive) ? "Chat/Context Menu/Unarchive" : "Chat/Context Menu/Archive" + items.append(.action(ContextMenuActionItem(text: hideText, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: iconName), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + guard let self else { + return + } + let undoValue: Bool + if self.location == .chatList(groupId: .archive) { + self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: false) + undoValue = true + } else { + self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: true) + undoValue = false + } + + if self.location == .chatList(groupId: .archive) { + self.present(UndoOverlayController(presentationData: self.presentationData, content: .archivedChat(peerId: peer.id.toInt64(), title: "", text: self.presentationData.strings.StoryFeed_TooltipUnarchive(peer.compactDisplayTitle).string, undo: true), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { [weak self] action in + if case .undo = action { + if let self { + self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: undoValue) + } + } + return false + }), in: .current) + } else { + self.present(UndoOverlayController(presentationData: self.presentationData, content: .archivedChat(peerId: peer.id.toInt64(), title: "", text: self.presentationData.strings.StoryFeed_TooltipArchive(peer.compactDisplayTitle).string, undo: true), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { [weak self] action in + if case .undo = action { + if let self { + self.context.engine.peers.updatePeerStoriesHidden(id: peer.id, isHidden: undoValue) + } + } + return false + }), in: .current) + } + }))) } else { items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.StoryFeed_ContextOpenChat, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor) diff --git a/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift b/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift index e8879a99da..3475948d4a 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift @@ -225,7 +225,8 @@ func mergeChannel(lhs: TelegramChannel?, rhs: TelegramChannel) -> TelegramChanne } if case .personal? = rhs.accessHash { - return rhs + let storiesHidden: Bool? = rhs.storiesHidden ?? lhs.storiesHidden + return rhs.withUpdatedStoriesHidden(storiesHidden) } var channelFlags = lhs.flags diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 5be310ccfa..0715ce9f37 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1268,7 +1268,7 @@ func _internal_deleteStories(account: Account, peerId: PeerId, ids: [Int32]) -> } } if updated { - transaction.setStoryItems(peerId: account.peerId, items: items) + transaction.setStoryItems(peerId: peerId, items: items) } account.stateManager.injectStoryUpdates(updates: ids.map { id in return .deleted(peerId: peerId, id: id) @@ -1342,7 +1342,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In return nil } - var items = transaction.getStoryItems(peerId: account.peerId) + var items = transaction.getStoryItems(peerId: peerId) var updatedItems: [Stories.Item] = [] for (id, referenceItem) in ids { if let index = items.firstIndex(where: { $0.id == id }), case let .item(item) = items[index].value.get(Stories.StoredItem.self) { @@ -1396,7 +1396,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In updatedItems.append(updatedItem) } } - transaction.setStoryItems(peerId: account.peerId, items: items) + transaction.setStoryItems(peerId: peerId, items: items) if !updatedItems.isEmpty { DispatchQueue.main.async { account.stateManager.injectStoryUpdates(updates: updatedItems.map { updatedItem in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift index 5620190e15..60633975b5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift @@ -546,6 +546,33 @@ func _internal_adminedPublicChannels(account: Account, scope: AdminedPublicChann } } +func _internal_channelsForStories(account: Account) -> Signal<[Peer], NoError> { + let accountPeerId = account.peerId + return account.network.request(Api.functions.stories.getChatsToSend()) + |> retryRequest + |> mapToSignal { result -> Signal<[Peer], NoError> in + return account.postbox.transaction { transaction -> [Peer] in + let chats: [Api.Chat] + let parsedPeers: AccumulatedPeers + switch result { + case let .chats(apiChats): + chats = apiChats + case let .chatsSlice(_, apiChats): + chats = apiChats + } + parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: []) + updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers) + var peers: [Peer] = [] + for chat in chats { + if let peer = transaction.getPeer(chat.peerId) { + peers.append(peer) + } + } + return peers + } + } +} + public enum ChannelAddressNameAssignmentAvailability { case available case unknown diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 6d7a6531a8..2d91867264 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -72,6 +72,13 @@ public extension TelegramEngine { return peers.map(EnginePeer.init) } } + + public func channelsForStories() -> Signal<[EnginePeer], NoError> { + return _internal_channelsForStories(account: self.account) + |> map { peers -> [EnginePeer] in + return peers.map(EnginePeer.init) + } + } public func channelAddressNameAssignmentAvailability(peerId: PeerId?) -> Signal { return _internal_channelAddressNameAssignmentAvailability(account: self.account, peerId: peerId) diff --git a/submodules/TelegramCore/Sources/UpdatePeers.swift b/submodules/TelegramCore/Sources/UpdatePeers.swift index 13a1184433..e53908e1c5 100644 --- a/submodules/TelegramCore/Sources/UpdatePeers.swift +++ b/submodules/TelegramCore/Sources/UpdatePeers.swift @@ -188,9 +188,11 @@ public func updatePeersCustom(transaction: Transaction, peers: [Peer], update: ( updated = mergeChannel(lhs: previous, rhs: updatedChannel) } - let isMember = updatedChannel.participationStatus == .member - if isMember != wasMember || updatedChannel.storiesHidden != wasHidden { - _internal_updateChannelMembership(transaction: transaction, channel: updatedChannel, isMember: isMember) + if let updated = updated as? TelegramChannel { + let isMember = updated.participationStatus == .member + if isMember != wasMember || updated.storiesHidden != wasHidden { + _internal_updateChannelMembership(transaction: transaction, channel: updated, isMember: isMember) + } } } diff --git a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift index e3f3dd33f3..ebc43e38ec 100644 --- a/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift +++ b/submodules/TelegramUI/Components/LegacyMessageInputPanel/Sources/LegacyMessageInputPanel.swift @@ -180,11 +180,11 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView { likeAction: nil, likeOptionsAction: nil, inputModeAction: nil, - timeoutAction: { [weak self] sourceView in + timeoutAction: self.chatLocation.peerId?.namespace == Namespaces.Peer.CloudUser ? { [weak self] sourceView in if let self { self.presentTimeoutSetup(sourceView: sourceView) } - }, + } : nil, forwardAction: nil, moreAction: nil, presentVoiceMessagesUnavailableTooltip: nil, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 618e1f5dce..8c88fd1dfa 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3712,7 +3712,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.displayNode.view.addInteraction(dropInteraction) Queue.mainQueue().after(1.0) { - self.adminedChannels.set(.single([]) |> then(self.context.engine.peers.adminedPublicChannels(scope: .all))) + self.adminedChannels.set(.single([]) |> then(self.context.engine.peers.channelsForStories())) self.closeFriends.set(self.context.engine.data.get(TelegramEngine.EngineData.Item.Contacts.CloseFriends())) } } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 38df123419..4bdf5a82cb 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -1169,8 +1169,6 @@ public final class StoryItemSetContainerComponent: Component { var displayViewLists = false if component.slice.peer.id == component.context.account.peerId { displayViewLists = true - } else if case let .channel(channel) = component.slice.peer, channel.hasPermission(.sendSomething) { - displayViewLists = true } if displayViewLists { @@ -1820,8 +1818,6 @@ public final class StoryItemSetContainerComponent: Component { var displayViewLists = false if component.slice.peer.id == component.context.account.peerId { displayViewLists = true - } else if case let .channel(channel) = component.slice.peer, channel.hasPermission(.sendSomething) { - displayViewLists = true } if displayViewLists { @@ -2967,8 +2963,6 @@ public final class StoryItemSetContainerComponent: Component { var displayViewLists = false if component.slice.peer.id == component.context.account.peerId { displayViewLists = true - } else if case let .channel(channel) = component.slice.peer, channel.hasPermission(.sendSomething) { - displayViewLists = true } if displayViewLists, let currentIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) { diff --git a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift index cc7058958b..937272a2c9 100644 --- a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift @@ -387,9 +387,18 @@ public final class StoryFooterPanelComponent: Component { var likeStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - reactionStatsLayout.size.width, y: floor((size.height - reactionStatsLayout.size.height) * 0.5)), size: reactionStatsLayout.size) likeStatsFrame.origin.y += component.expandFraction * 45.0 - likeStatsTransition.setFrame(view: likeStatsText, frame: likeStatsFrame) - likeStatsTransition.setAlpha(view: likeStatsText, alpha: 1.0 - component.expandFraction) - rightContentOffset -= reactionStatsLayout.size.width + 1.0 + likeStatsTransition.setPosition(view: likeStatsText, position: likeStatsFrame.center) + likeStatsTransition.setBounds(view: likeStatsText, bounds: CGRect(origin: CGPoint(), size: likeStatsFrame.size)) + var likeStatsAlpha: CGFloat = (1.0 - component.expandFraction) + if reactionCount == 0 { + likeStatsAlpha = 0.0 + } + likeStatsTransition.setAlpha(view: likeStatsText, alpha: likeStatsAlpha) + likeStatsTransition.setScale(view: likeStatsText, scale: reactionCount == 0 ? 0.001 : 1.0) + + if reactionCount != 0 { + rightContentOffset -= reactionStatsLayout.size.width + 1.0 + } let likeButton: ComponentView if let current = self.likeButton { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 56b47abba6..e38ad40d13 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9996,6 +9996,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } + + var signal = strongSelf.context.engine.messages.requestMessageSelectPollOption(messageId: id, opaqueIdentifiers: []) let disposables: DisposableDict if let current = strongSelf.selectMessagePollOptionDisposables { disposables = current @@ -10003,24 +10005,43 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G disposables = DisposableDict() strongSelf.selectMessagePollOptionDisposables = disposables } - let controller = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: nil)) - strongSelf.present(controller, in: .window(.root)) - let signal = strongSelf.context.engine.messages.requestMessageSelectPollOption(messageId: id, opaqueIdentifiers: []) - |> afterDisposed { [weak controller] in - Queue.mainQueue().async { - controller?.dismiss() + + var cancelImpl: (() -> Void)? + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + let progressSignal = Signal { subscriber in + let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { + cancelImpl?() + })) + strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + return ActionDisposable { [weak controller] in + Queue.mainQueue().async() { + controller?.dismiss() + } } } + |> runOn(Queue.mainQueue()) + |> delay(0.3, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + signal = signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + cancelImpl = { + disposables.set(nil, forKey: id) + } + disposables.set((signal - |> deliverOnMainQueue).start(error: { _ in - guard let _ = self else { + |> deliverOnMainQueue).start(completed: { [weak self] in + guard let self else { return } - }, completed: { - if strongSelf.selectPollOptionFeedback == nil { - strongSelf.selectPollOptionFeedback = HapticFeedback() + if self.selectPollOptionFeedback == nil { + self.selectPollOptionFeedback = HapticFeedback() } - strongSelf.selectPollOptionFeedback?.success() + self.selectPollOptionFeedback?.success() }), forKey: id) }, requestStopPollInMessage: { [weak self] id in guard let strongSelf = self, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(id) else {