diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 7f66316dbb..78b80dfbb2 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1842,7 +1842,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController if let mediaId = info.media.id { validIds.append(mediaId) if self.preloadStoryResourceDisposables[mediaId] == nil { - self.preloadStoryResourceDisposables[mediaId] = preloadStoryMedia(context: self.context, peer: info.peer, storyId: info.storyId, media: info.media).start() + self.preloadStoryResourceDisposables[mediaId] = preloadStoryMedia(context: self.context, peer: info.peer, storyId: info.storyId, media: info.media, reactions: info.reactions).start() } } } diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift index 7cf2e21e96..d9976fb3b7 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift @@ -457,25 +457,11 @@ public final class DrawingStickerEntityView: DrawingEntityView { return } - let _ = (self.context.engine.stickers.availableReactions() - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] availableReactions in - guard let self, let availableReactions else { + let continueWithAnimationFile: (TelegramMediaFile) -> Void = { [weak self] animation in + guard let self else { return } - var animation: TelegramMediaFile? - for reaction in availableReactions.reactions { - if reaction.value == updateReaction.reaction { - animation = reaction.selectAnimation - break - } - } - - guard let animation else { - return - } - if case let .file(_, type) = self.stickerEntity.content, case let .reaction(_, style) = type { self.stickerEntity.content = .file(animation, .reaction(updateReaction.reaction, style)) } @@ -503,7 +489,39 @@ public final class DrawingStickerEntityView: DrawingEntityView { self.animationNode?.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) let _ = self.dismissReactionSelection() - }) + } + + switch updateReaction { + case .builtin: + let _ = (self.context.engine.stickers.availableReactions() + |> take(1) + |> deliverOnMainQueue).start(next: { availableReactions in + guard let availableReactions else { + return + } + var animation: TelegramMediaFile? + for reaction in availableReactions.reactions { + if reaction.value == updateReaction.reaction { + animation = reaction.selectAnimation + break + } + } + if let animation { + continueWithAnimationFile(animation) + } + }) + case let .custom(fileId, file): + if let file { + continueWithAnimationFile(file) + } else { + let _ = (self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId]) + |> deliverOnMainQueue).start(next: { files in + if let itemFile = files[fileId] { + continueWithAnimationFile(itemFile) + } + }) + } + } } reactionContextNode.premiumReactionsSelected = { [weak self] file in diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index d2ffb161b3..6980923433 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -305,10 +305,10 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) - let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [])) + let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil)) let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) - let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [])) + let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil)) let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let timestamp = self.referenceTimestamp diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index 6b4d9af2bb..bff04bcf23 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -938,7 +938,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) - let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [])) + let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil)) let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index e48ad2ef67..ccf1581433 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -453,10 +453,10 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { let selfPeer: EnginePeer = .user(TelegramUser(id: self.context.account.peerId, accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let peer1: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_1_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let peer2: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(2)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_2_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) - let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [])) + let peer3: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(3)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil)) let peer3Author: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_3_AuthorName, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let peer4: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(4)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_4_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) - let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [])) + let peer5: EnginePeer = .channel(TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, title: self.presentationData.strings.Appearance_ThemePreview_ChatList_5_Name, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .broadcast(.init(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil)) let peer6: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.SecretChat, id: PeerId.Id._internalFromInt64Value(5)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_6_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) let peer7: EnginePeer = .user(TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(6)), accessHash: nil, firstName: self.presentationData.strings.Appearance_ThemePreview_ChatList_7_Name, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [], emojiStatus: nil, usernames: [], storiesHidden: nil)) diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index 2169e0db07..d8b037b690 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -3612,22 +3612,6 @@ public extension Api.functions.contacts { }) } } -public extension Api.functions.contacts { - static func toggleStoriesHidden(id: Api.InputUser, hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1967110245) - id.serialize(buffer, true) - hidden.serialize(buffer, true) - return (FunctionDescription(name: "contacts.toggleStoriesHidden", parameters: [("id", String(describing: id)), ("hidden", String(describing: hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } -} public extension Api.functions.contacts { static func toggleTopPeers(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -8734,15 +8718,16 @@ public extension Api.functions.stories { } } public extension Api.functions.stories { - static func getStoriesViews(id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func getStoriesViews(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1703553370) + buffer.appendInt32(685862088) + peer.serialize(buffer, true) buffer.appendInt32(481674261) buffer.appendInt32(Int32(id.count)) for item in id { serializeInt32(item, buffer: buffer, boxed: false) } - return (FunctionDescription(name: "stories.getStoriesViews", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViews? in + return (FunctionDescription(name: "stories.getStoriesViews", parameters: [("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViews? in let reader = BufferReader(buffer) var result: Api.stories.StoryViews? if let signature = reader.readInt32() { @@ -8753,15 +8738,16 @@ public extension Api.functions.stories { } } public extension Api.functions.stories { - static func getStoryViewsList(flags: Int32, q: String?, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func getStoryViewsList(flags: Int32, peer: Api.InputPeer, q: String?, id: Int32, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-111189596) + buffer.appendInt32(2127707223) serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) if Int(flags) & Int(1 << 1) != 0 {serializeString(q!, buffer: buffer, boxed: false)} serializeInt32(id, buffer: buffer, boxed: false) serializeString(offset, buffer: buffer, boxed: false) serializeInt32(limit, buffer: buffer, boxed: false) - return (FunctionDescription(name: "stories.getStoryViewsList", parameters: [("flags", String(describing: flags)), ("q", String(describing: q)), ("id", String(describing: id)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViewsList? in + return (FunctionDescription(name: "stories.getStoryViewsList", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("q", String(describing: q)), ("id", String(describing: id)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stories.StoryViewsList? in let reader = BufferReader(buffer) var result: Api.stories.StoryViewsList? if let signature = reader.readInt32() { @@ -8897,6 +8883,22 @@ public extension Api.functions.stories { }) } } +public extension Api.functions.stories { + static func togglePeerStoriesHidden(peer: Api.InputPeer, hidden: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1123805756) + peer.serialize(buffer, true) + hidden.serialize(buffer, true) + return (FunctionDescription(name: "stories.togglePeerStoriesHidden", parameters: [("peer", String(describing: peer)), ("hidden", String(describing: hidden))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.stories { static func togglePinned(peer: Api.InputPeer, id: [Int32], pinned: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Int32]>) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift b/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift index b245d6d68e..e8879a99da 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ApiGroupOrChannel.swift @@ -61,7 +61,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: "", photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0) case let .chatForbidden(id, title): return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(id)), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0) - case let .channel(flags, _, id, accessHash, title, username, photo, date, restrictionReason, adminRights, bannedRights, defaultBannedRights, _, usernames): + case let .channel(flags, flags2, id, accessHash, title, username, photo, date, restrictionReason, adminRights, bannedRights, defaultBannedRights, _, usernames): let isMin = (flags & (1 << 12)) != 0 let participationStatus: TelegramChannelParticipationStatus @@ -128,6 +128,15 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { if (flags & Int32(1 << 30)) != 0 { channelFlags.insert(.isForum) } + + var storiesHidden: Bool? + if flags2 & (1 << 2) == 0 { // stories_hidden_min + if flags2 & (1 << 1) != 0 { + storiesHidden = true + } else if !isMin { + storiesHidden = false + } + } let restrictionInfo: PeerAccessRestrictionInfo? if let restrictionReason = restrictionReason { @@ -144,7 +153,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { } } - return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: accessHashValue, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: 0, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChatAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChatBannedRights.init), defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? []) + return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: accessHashValue, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: 0, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChatAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChatBannedRights.init), defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden) case let .channelForbidden(flags, id, accessHash, title, untilDate): let info: TelegramChannelInfo if (flags & Int32(1 << 8)) != 0 { @@ -153,7 +162,7 @@ func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? { info = .broadcast(TelegramChannelBroadcastInfo(flags: [])) } - return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: .personal(accessHash), title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), defaultBannedRights: nil, usernames: []) + return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(id)), accessHash: .personal(accessHash), title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), defaultBannedRights: nil, usernames: [], storiesHidden: nil) } } @@ -161,7 +170,7 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? { switch rhs { case .chat, .chatEmpty, .chatForbidden, .channelForbidden: return parseTelegramGroupOrChannel(chat: rhs) - case let .channel(flags, _, _, accessHash, title, username, photo, _, _, _, _, defaultBannedRights, _, usernames): + case let .channel(flags, flags2, _, accessHash, title, username, photo, _, _, _, _, defaultBannedRights, _, usernames): let isMin = (flags & (1 << 12)) != 0 if accessHash != nil && !isMin { return parseTelegramGroupOrChannel(chat: rhs) @@ -194,7 +203,16 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? { info = .group(TelegramChannelGroupInfo(flags: infoFlags)) } - return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? []) + var storiesHidden: Bool? = lhs.storiesHidden + if flags2 & (1 << 2) == 0 { // stories_hidden_min + if flags2 & (1 << 1) != 0 { + storiesHidden = true + } else if !isMin { + storiesHidden = false + } + } + + return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init), usernames: usernames?.map(TelegramPeerUsername.init(apiUsername:)) ?? [], storiesHidden: storiesHidden) } else { return parseTelegramGroupOrChannel(chat: rhs) } @@ -245,6 +263,8 @@ func mergeChannel(lhs: TelegramChannel?, rhs: TelegramChannel) -> TelegramChanne accessHash = rhs.accessHash ?? lhs.accessHash } - return TelegramChannel(id: lhs.id, accessHash: accessHash, title: rhs.title, username: rhs.username, photo: rhs.photo, creationDate: rhs.creationDate, version: rhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: rhs.restrictionInfo, adminRights: rhs.adminRights, bannedRights: rhs.bannedRights, defaultBannedRights: rhs.defaultBannedRights, usernames: rhs.usernames) + let storiesHidden: Bool? = rhs.storiesHidden ?? lhs.storiesHidden + + return TelegramChannel(id: lhs.id, accessHash: accessHash, title: rhs.title, username: rhs.username, photo: rhs.photo, creationDate: rhs.creationDate, version: rhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: rhs.restrictionInfo, adminRights: rhs.adminRights, bannedRights: rhs.bannedRights, defaultBannedRights: rhs.defaultBannedRights, usernames: rhs.usernames, storiesHidden: storiesHidden) } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift index 34452e744a..699fe3dd6d 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramChannel.swift @@ -166,6 +166,7 @@ public final class TelegramChannel: Peer, Equatable { public let bannedRights: TelegramChatBannedRights? public let defaultBannedRights: TelegramChatBannedRights? public let usernames: [TelegramPeerUsername] + public let storiesHidden: Bool? public var indexName: PeerIndexNameRepresentation { var addressNames = self.usernames.map { $0.username } @@ -182,7 +183,7 @@ public final class TelegramChannel: Peer, Equatable { public var timeoutAttribute: UInt32? { return nil } - public init(id: PeerId, accessHash: TelegramPeerAccessHash?, title: String, username: String?, photo: [TelegramMediaImageRepresentation], creationDate: Int32, version: Int32, participationStatus: TelegramChannelParticipationStatus, info: TelegramChannelInfo, flags: TelegramChannelFlags, restrictionInfo: PeerAccessRestrictionInfo?, adminRights: TelegramChatAdminRights?, bannedRights: TelegramChatBannedRights?, defaultBannedRights: TelegramChatBannedRights?, usernames: [TelegramPeerUsername]) { + public init(id: PeerId, accessHash: TelegramPeerAccessHash?, title: String, username: String?, photo: [TelegramMediaImageRepresentation], creationDate: Int32, version: Int32, participationStatus: TelegramChannelParticipationStatus, info: TelegramChannelInfo, flags: TelegramChannelFlags, restrictionInfo: PeerAccessRestrictionInfo?, adminRights: TelegramChatAdminRights?, bannedRights: TelegramChatBannedRights?, defaultBannedRights: TelegramChatBannedRights?, usernames: [TelegramPeerUsername], storiesHidden: Bool?) { self.id = id self.accessHash = accessHash self.title = title @@ -198,6 +199,7 @@ public final class TelegramChannel: Peer, Equatable { self.bannedRights = bannedRights self.defaultBannedRights = defaultBannedRights self.usernames = usernames + self.storiesHidden = storiesHidden } public init(decoder: PostboxDecoder) { @@ -226,6 +228,7 @@ public final class TelegramChannel: Peer, Equatable { self.bannedRights = decoder.decodeObjectForKey("br", decoder: { TelegramChatBannedRights(decoder: $0) }) as? TelegramChatBannedRights self.defaultBannedRights = decoder.decodeObjectForKey("dbr", decoder: { TelegramChatBannedRights(decoder: $0) }) as? TelegramChatBannedRights self.usernames = decoder.decodeObjectArrayForKey("uns") + self.storiesHidden = decoder.decodeOptionalBoolForKey("sth") } public func encode(_ encoder: PostboxEncoder) { @@ -275,6 +278,12 @@ public final class TelegramChannel: Peer, Equatable { encoder.encodeNil(forKey: "dbr") } encoder.encodeObjectArray(self.usernames, forKey: "uns") + + if let storiesHidden = self.storiesHidden { + encoder.encodeBool(storiesHidden, forKey: "sth") + } else { + encoder.encodeNil(forKey: "sth") + } } public func isEqual(_ other: Peer) -> Bool { @@ -312,23 +321,30 @@ public final class TelegramChannel: Peer, Equatable { if lhs.usernames != rhs.usernames { return false } + if lhs.storiesHidden != rhs.storiesHidden { + return false + } return true } public func withUpdatedAddressName(_ addressName: String?) -> TelegramChannel { - return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: addressName, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames) + return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: addressName, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden) } public func withUpdatedAddressNames(_ addressNames: [TelegramPeerUsername]) -> TelegramChannel { - return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: addressNames) + return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: addressNames, storiesHidden: self.storiesHidden) } public func withUpdatedDefaultBannedRights(_ defaultBannedRights: TelegramChatBannedRights?) -> TelegramChannel { - return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: defaultBannedRights, usernames: self.usernames) + return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden) } public func withUpdatedFlags(_ flags: TelegramChannelFlags) -> TelegramChannel { - return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames) + return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: self.storiesHidden) + } + + public func withUpdatedStoriesHidden(_ storiesHidden: Bool?) -> TelegramChannel { + return TelegramChannel(id: self.id, accessHash: self.accessHash, title: self.title, username: self.username, photo: self.photo, creationDate: self.creationDate, version: self.version, participationStatus: self.participationStatus, info: self.info, flags: self.flags, restrictionInfo: self.restrictionInfo, adminRights: self.adminRights, bannedRights: self.bannedRights, defaultBannedRights: self.defaultBannedRights, usernames: self.usernames, storiesHidden: storiesHidden) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift index 3244d7539d..22bdb93a47 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AdMessages.swift @@ -263,7 +263,8 @@ private class AdMessagesHistoryContextImpl { adminRights: nil, bannedRights: nil, defaultBannedRights: nil, - usernames: [] + usernames: [], + storiesHidden: nil ) case let .webPage(webPage): author = TelegramChannel( @@ -281,7 +282,9 @@ private class AdMessagesHistoryContextImpl { adminRights: nil, bannedRights: nil, defaultBannedRights: nil, - usernames: []) + usernames: [], + storiesHidden: nil + ) } messagePeers[author.id] = author diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift index 78e282955c..c2cae9d9bd 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift @@ -94,6 +94,7 @@ public final class EngineStoryViewListContext { let queue: Queue let account: Account + let peerId: EnginePeer.Id let storyId: Int32 let listMode: ListMode let sortMode: SortMode @@ -108,9 +109,10 @@ public final class EngineStoryViewListContext { private var parentSource: Impl? var isLoadingMore: Bool = false - init(queue: Queue, account: Account, storyId: Int32, views: EngineStoryItem.Views, listMode: ListMode, sortMode: SortMode, searchQuery: String?, parentSource: Impl?) { + init(queue: Queue, account: Account, peerId: EnginePeer.Id, storyId: Int32, views: EngineStoryItem.Views, listMode: ListMode, sortMode: SortMode, searchQuery: String?, parentSource: Impl?) { self.queue = queue self.account = account + self.peerId = peerId self.storyId = storyId self.listMode = listMode self.sortMode = sortMode @@ -260,15 +262,21 @@ public final class EngineStoryViewListContext { let account = self.account let accountPeerId = account.peerId + let peerId = self.peerId let storyId = self.storyId let listMode = self.listMode let sortMode = self.sortMode let searchQuery = self.searchQuery let currentOffset = state.nextOffset let limit = state.items.isEmpty ? 50 : 100 - let signal: Signal = self.account.postbox.transaction { transaction -> Void in + let signal: Signal = self.account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(peerId).flatMap(apiInputPeer) } - |> mapToSignal { _ -> Signal in + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .complete() + } + var flags: Int32 = 0 switch listMode { case .everyone: @@ -286,7 +294,7 @@ public final class EngineStoryViewListContext { flags |= (1 << 1) } - return account.network.request(Api.functions.stories.getStoryViewsList(flags: flags, q: searchQuery, id: storyId, offset: currentOffset?.value ?? "", limit: Int32(limit))) + return account.network.request(Api.functions.stories.getStoryViewsList(flags: flags, peer: inputPeer, q: searchQuery, id: storyId, offset: currentOffset?.value ?? "", limit: Int32(limit))) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -523,11 +531,11 @@ public final class EngineStoryViewListContext { } } - init(account: Account, storyId: Int32, views: EngineStoryItem.Views, listMode: ListMode, sortMode: SortMode, searchQuery: String?, parentSource: EngineStoryViewListContext?) { + init(account: Account, peerId: EnginePeer.Id, storyId: Int32, views: EngineStoryItem.Views, listMode: ListMode, sortMode: SortMode, searchQuery: String?, parentSource: EngineStoryViewListContext?) { let queue = Queue.mainQueue() self.queue = queue self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, account: account, storyId: storyId, views: views, listMode: listMode, sortMode: sortMode, searchQuery: searchQuery, parentSource: parentSource?.impl.syncWith { $0 }) + return Impl(queue: queue, account: account, peerId: peerId, storyId: storyId, views: views, listMode: listMode, sortMode: sortMode, searchQuery: searchQuery, parentSource: parentSource?.impl.syncWith { $0 }) }) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index eca6cbe345..5be310ccfa 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -1626,53 +1626,66 @@ public final class StoryViewList { } } -func _internal_getStoryViews(account: Account, ids: [Int32]) -> Signal<[Int32: Stories.Item.Views], NoError> { - let accountPeerId = account.peerId - return account.network.request(Api.functions.stories.getStoriesViews(id: ids)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) +func _internal_getStoryViews(account: Account, peerId: PeerId, ids: [Int32]) -> Signal<[Int32: Stories.Item.Views], NoError> { + return account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(peerId).flatMap(apiInputPeer) } - |> mapToSignal { result -> Signal<[Int32: Stories.Item.Views], NoError> in - guard let result = result else { + |> mapToSignal { inputPeer -> Signal<[Int32: Stories.Item.Views], NoError> in + guard let inputPeer = inputPeer else { return .single([:]) } - return account.postbox.transaction { transaction -> [Int32: Stories.Item.Views] in - var parsedViews: [Int32: Stories.Item.Views] = [:] - switch result { - case let .storyViews(views, users): - updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users)) - - for i in 0 ..< views.count { - if i < ids.count { - parsedViews[ids[i]] = Stories.Item.Views(apiViews: views[i]) + + let accountPeerId = account.peerId + return account.network.request(Api.functions.stories.getStoriesViews(peer: inputPeer, id: ids)) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal<[Int32: Stories.Item.Views], NoError> in + guard let result = result else { + return .single([:]) + } + return account.postbox.transaction { transaction -> [Int32: Stories.Item.Views] in + var parsedViews: [Int32: Stories.Item.Views] = [:] + switch result { + case let .storyViews(views, users): + updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: AccumulatedPeers(users: users)) + + for i in 0 ..< views.count { + if i < ids.count { + parsedViews[ids[i]] = Stories.Item.Views(apiViews: views[i]) + } } } + + return parsedViews } - - return parsedViews } } } func _internal_updatePeerStoriesHidden(account: Account, id: PeerId, isHidden: Bool) -> Signal { - return account.postbox.transaction { transaction -> Api.InputUser? in + return account.postbox.transaction { transaction -> Api.InputPeer? in guard let peer = transaction.getPeer(id) else { return nil } - guard let user = peer as? TelegramUser else { - return nil + if let user = peer as? TelegramUser { + updatePeersCustom(transaction: transaction, peers: [user.withUpdatedStoriesHidden(isHidden)], update: { _, updated in + return updated + }) + } else if let channel = peer as? TelegramChannel { + updatePeersCustom(transaction: transaction, peers: [channel.withUpdatedStoriesHidden(isHidden)], update: { _, updated in + return updated + }) } - updatePeersCustom(transaction: transaction, peers: [user.withUpdatedStoriesHidden(isHidden)], update: { _, updated in - return updated - }) - return apiInputUser(peer) + + return apiInputPeer(peer) } - |> mapToSignal { inputUser -> Signal in - guard let inputUser = inputUser else { + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { return .complete() } - return account.network.request(Api.functions.contacts.toggleStoriesHidden(id: inputUser, hidden: isHidden ? .boolTrue : .boolFalse)) + return account.network.request(Api.functions.stories.togglePeerStoriesHidden(peer: inputPeer, hidden: isHidden ? .boolTrue : .boolFalse)) |> `catch` { _ -> Signal in return .single(.boolFalse) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index c5583b8f78..3713d7cd99 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -1407,6 +1407,20 @@ public func _internal_pollPeerStories(postbox: Postbox, network: Network, accoun transaction.replaceAllStorySubscriptions(key: .filtered, state: state, peerIds: peerIds) } } + } else if let channel = transaction.getPeer(peerId) as? TelegramChannel, let storiesHidden = channel.storiesHidden { + if storiesHidden { + if !transaction.storySubscriptionsContains(key: .hidden, peerId: peerId) { + var (state, peerIds) = transaction.getAllStorySubscriptions(key: .hidden) + peerIds.append(peerId) + transaction.replaceAllStorySubscriptions(key: .hidden, state: state, peerIds: peerIds) + } + } else { + if !transaction.storySubscriptionsContains(key: .filtered, peerId: peerId) { + var (state, peerIds) = transaction.getAllStorySubscriptions(key: .filtered) + peerIds.append(peerId) + transaction.replaceAllStorySubscriptions(key: .filtered, state: state, peerIds: peerIds) + } + } } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 8c36bb5fb1..957a100c32 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -18,17 +18,20 @@ public final class StoryPreloadInfo { public let peer: PeerReference public let storyId: Int32 public let media: EngineMedia + public let reactions: [MessageReaction.Reaction] public let priority: Priority public init( peer: PeerReference, storyId: Int32, media: EngineMedia, + reactions: [MessageReaction.Reaction], priority: Priority ) { self.peer = peer self.storyId = storyId self.media = media + self.reactions = reactions self.priority = priority } } @@ -1063,11 +1066,20 @@ public extension TelegramEngine { guard let media = itemAndPeer.item.media, let mediaId = media.id else { continue } + var reactions: [MessageReaction.Reaction] = [] + for mediaArea in itemAndPeer.item.mediaAreas { + if case let .reaction(_, reaction, _) = mediaArea { + if !reactions.contains(reaction) { + reactions.append(reaction) + } + } + } resultResources[mediaId] = StoryPreloadInfo( peer: peerReference, storyId: itemAndPeer.item.id, media: EngineMedia(media), + reactions: reactions, priority: .top(position: nextPriority) ) nextPriority += 1 @@ -1083,11 +1095,11 @@ public extension TelegramEngine { } public func refreshStoryViews(peerId: EnginePeer.Id, ids: [Int32]) -> Signal { - if peerId != self.account.peerId { + if peerId != self.account.peerId && peerId.namespace != Namespaces.Peer.CloudChannel { return .complete() } - return _internal_getStoryViews(account: self.account, ids: ids) + return _internal_getStoryViews(account: self.account, peerId: peerId, ids: ids) |> mapToSignal { views -> Signal in return self.account.postbox.transaction { transaction -> Void in var currentItems = transaction.getStoryItems(peerId: peerId) @@ -1183,8 +1195,8 @@ public extension TelegramEngine { return _internal_updateStoriesArePinned(account: self.account, peerId: peerId, ids: ids, isPinned: isPinned) } - public func storyViewList(id: Int32, views: EngineStoryItem.Views, listMode: EngineStoryViewListContext.ListMode, sortMode: EngineStoryViewListContext.SortMode, searchQuery: String? = nil, parentSource: EngineStoryViewListContext? = nil) -> EngineStoryViewListContext { - return EngineStoryViewListContext(account: self.account, storyId: id, views: views, listMode: listMode, sortMode: sortMode, searchQuery: searchQuery, parentSource: parentSource) + public func storyViewList(peerId: EnginePeer.Id, id: Int32, views: EngineStoryItem.Views, listMode: EngineStoryViewListContext.ListMode, sortMode: EngineStoryViewListContext.SortMode, searchQuery: String? = nil, parentSource: EngineStoryViewListContext? = nil) -> EngineStoryViewListContext { + return EngineStoryViewListContext(account: self.account, peerId: peerId, storyId: id, views: views, listMode: listMode, sortMode: sortMode, searchQuery: searchQuery, parentSource: parentSource) } public func exportStoryLink(peerId: EnginePeer.Id, id: Int32) -> Signal { diff --git a/submodules/TelegramCore/Sources/UpdatePeers.swift b/submodules/TelegramCore/Sources/UpdatePeers.swift index da97d79573..13a1184433 100644 --- a/submodules/TelegramCore/Sources/UpdatePeers.swift +++ b/submodules/TelegramCore/Sources/UpdatePeers.swift @@ -127,9 +127,7 @@ func _internal_updatePeerIsContact(transaction: Transaction, user: TelegramUser, } private func _internal_updateChannelMembership(transaction: Transaction, channel: TelegramChannel, isMember: Bool) { - if isMember { - let storiesHidden = !"".isEmpty - + if isMember, let storiesHidden = channel.storiesHidden { if storiesHidden { if transaction.storySubscriptionsContains(key: .filtered, peerId: channel.id) { var (state, peerIds) = transaction.getAllStorySubscriptions(key: .filtered) @@ -183,13 +181,15 @@ public func updatePeersCustom(transaction: Transaction, peers: [Peer], update: ( if let updatedChannel = updated as? TelegramChannel { var wasMember = false + var wasHidden: Bool? if let previous = previous as? TelegramChannel { wasMember = previous.participationStatus == .member + wasHidden = previous.storiesHidden updated = mergeChannel(lhs: previous, rhs: updatedChannel) } let isMember = updatedChannel.participationStatus == .member - if isMember != wasMember { + if isMember != wasMember || updatedChannel.storiesHidden != wasHidden { _internal_updateChannelMembership(transaction: transaction, channel: updatedChannel, isMember: isMember) } } diff --git a/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift b/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift index 9877c192a0..9f44e8205b 100644 --- a/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift +++ b/submodules/TelegramUI/Components/LottieComponent/Sources/LottieComponent.swift @@ -82,6 +82,7 @@ public final class LottieComponent: Component { public let color: UIColor? public let startingPosition: StartingPosition public let size: CGSize? + public let renderingScale: CGFloat? public let loop: Bool public init( @@ -89,12 +90,14 @@ public final class LottieComponent: Component { color: UIColor? = nil, startingPosition: StartingPosition = .end, size: CGSize? = nil, + renderingScale: CGFloat? = nil, loop: Bool = false ) { self.content = content self.color = color self.startingPosition = startingPosition self.size = size + self.renderingScale = renderingScale self.loop = loop } @@ -111,6 +114,9 @@ public final class LottieComponent: Component { if lhs.size != rhs.size { return false } + if lhs.renderingScale != rhs.renderingScale { + return false + } if lhs.loop != rhs.loop { return false } @@ -353,7 +359,9 @@ public final class LottieComponent: Component { var redrawImage = false - let displaySize = CGSize(width: size.width * UIScreenScale, height: size.height * UIScreenScale) + let renderingScale = component.renderingScale ?? UIScreenScale + + let displaySize = CGSize(width: size.width * renderingScale, height: size.height * renderingScale) if self.currentDisplaySize != displaySize { self.currentDisplaySize = displaySize redrawImage = true diff --git a/submodules/TelegramUI/Components/LottieComponentResourceContent/BUILD b/submodules/TelegramUI/Components/LottieComponentResourceContent/BUILD new file mode 100644 index 0000000000..e745190452 --- /dev/null +++ b/submodules/TelegramUI/Components/LottieComponentResourceContent/BUILD @@ -0,0 +1,22 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "LottieComponentResourceContent", + module_name = "LottieComponentResourceContent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramCore", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/AccountContext", + "//submodules/GZip:GZip", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/LottieComponentResourceContent/Sources/LottieComponentResourceContent.swift b/submodules/TelegramUI/Components/LottieComponentResourceContent/Sources/LottieComponentResourceContent.swift new file mode 100644 index 0000000000..22ea9206ea --- /dev/null +++ b/submodules/TelegramUI/Components/LottieComponentResourceContent/Sources/LottieComponentResourceContent.swift @@ -0,0 +1,82 @@ +import Foundation +import LottieComponent +import SwiftSignalKit +import TelegramCore +import AccountContext +import GZip + +public extension LottieComponent { + final class ResourceContent: LottieComponent.Content { + private let context: AccountContext + private let file: TelegramMediaFile + private let attemptSynchronously: Bool + + override public var frameRange: Range { + return 0.0 ..< 1.0 + } + + public init( + context: AccountContext, + file: TelegramMediaFile, + attemptSynchronously: Bool + ) { + self.context = context + self.file = file + self.attemptSynchronously = attemptSynchronously + + super.init() + } + + override public func isEqual(to other: Content) -> Bool { + guard let other = other as? ResourceContent else { + return false + } + if self.file.fileId != other.file.fileId { + return false + } + if self.attemptSynchronously != other.attemptSynchronously { + return false + } + return true + } + + override public func load(_ f: @escaping (Data, String?) -> Void) -> Disposable { + let attemptSynchronously = self.attemptSynchronously + let file = self.file + let mediaBox = self.context.account.postbox.mediaBox + return Signal { subscriber in + if attemptSynchronously { + if let path = mediaBox.completedResourcePath(file.resource), let contents = try? Data(contentsOf: URL(fileURLWithPath: path)) { + let result = TGGUnzipData(contents, 2 * 1024 * 1024) ?? contents + subscriber.putNext(result) + subscriber.putCompletion() + + return EmptyDisposable + } + } + + let dataDisposable = (mediaBox.resourceData(file.resource) + |> filter { data in return data.complete }).start(next: { data in + if let contents = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + let result = TGGUnzipData(contents, 2 * 1024 * 1024) ?? contents + subscriber.putNext(result) + subscriber.putCompletion() + } else { + subscriber.putNext(nil) + } + }) + let fetchDisposable = mediaBox.fetchedResource(file.resource, parameters: nil).start() + + return ActionDisposable { + dataDisposable.dispose() + fetchDisposable.dispose() + } + }.start(next: { data in + guard let data else { + return + } + f(data, nil) + }) + } + } +} diff --git a/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD new file mode 100644 index 0000000000..2cacc7517f --- /dev/null +++ b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/BUILD @@ -0,0 +1,28 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "MessageInputActionButtonComponent", + module_name = "MessageInputActionButtonComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/AppBundle", + "//submodules/TelegramUI/Components/ChatTextInputMediaRecordingButton", + "//submodules/AccountContext", + "//submodules/TelegramPresentationData", + "//submodules/ChatPresentationInterfaceState", + "//submodules/TelegramUI/Components/MoreHeaderButton", + "//submodules/ContextUI", + "//submodules/Components/ReactionButtonListComponent", + "//submodules/TelegramCore", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift b/submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift similarity index 100% rename from submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputActionButtonComponent.swift rename to submodules/TelegramUI/Components/MessageInputActionButtonComponent/Sources/MessageInputActionButtonComponent.swift diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD index 559f2e1c57..3c9954c354 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/BUILD @@ -35,6 +35,7 @@ swift_library( "//submodules/Components/ReactionButtonListComponent", "//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/AnimatedCountLabelNode", + "//submodules/TelegramUI/Components/MessageInputActionButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index a0f7b2e8b9..29313ed18f 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -17,6 +17,7 @@ import EmojiSuggestionsComponent import AudioToolbox import AnimatedTextComponent import AnimatedCountLabelNode +import MessageInputActionButtonComponent public final class MessageInputPanelComponent: Component { public struct ContextQueryTypes: OptionSet { diff --git a/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift b/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift index 615cc5965e..4d85261323 100644 --- a/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift +++ b/submodules/TelegramUI/Components/NotificationExceptionsScreen/Sources/NotificationExceptionsScreen.swift @@ -332,7 +332,7 @@ private func notificationsPeerCategoryEntries(peerId: EnginePeer.Id, notificatio } } existingThreadIds.insert(value.threadId) - entries.append(.exception(Int32(index), presentationData.dateTimeFormat, presentationData.nameDisplayOrder, .channel(TelegramChannel(id: peerId, accessHash: nil, title: "", username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(TelegramChannelGroupInfo(flags: [])), flags: [.isForum], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [])), value.threadId, value.info, title, value.notificationSettings._asNotificationSettings(), state.editing, state.revealedThreadId == value.threadId)) + entries.append(.exception(Int32(index), presentationData.dateTimeFormat, presentationData.nameDisplayOrder, .channel(TelegramChannel(id: peerId, accessHash: nil, title: "", username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .member, info: .group(TelegramChannelGroupInfo(flags: [])), flags: [.isForum], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil)), value.threadId, value.info, title, value.notificationSettings._asNotificationSettings(), state.editing, state.revealedThreadId == value.threadId)) index += 1 } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD index a35aff1bfb..38d97818b2 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/BUILD @@ -79,6 +79,7 @@ swift_library( "//submodules/Utils/RangeSet", "//submodules/TelegramUI/Components/Stories/StoryStealthModeSheetScreen", "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramUI/Components/LottieComponentResourceContent", "//submodules/TextSelectionNode", "//submodules/Pasteboard", "//submodules/Speak", @@ -90,6 +91,7 @@ swift_library( "//submodules/TelegramUI/Components/TabSelectorComponent", "//submodules/TelegramUI/Components/OptionButtonComponent", "//submodules/TelegramUI/Components/EmojiTextAttachmentView", + "//submodules/AnimatedCountLabelNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index 12edb2b3f7..016fb713b8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -88,6 +88,18 @@ public final class StoryContentContextImpl: StoryContentContext { } } } + for mediaArea in itemValue.mediaAreas { + if case let .reaction(_, reaction, _) = mediaArea { + if case let .custom(fileId) = reaction { + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) + if allEntityFiles[mediaId] == nil { + if let file = transaction.getMedia(mediaId) as? TelegramMediaFile { + allEntityFiles[file.fileId] = file + } + } + } + } + } } } } @@ -825,10 +837,19 @@ public final class StoryContentContextImpl: StoryContentContext { let peer = possibleItems[i].0 let item = possibleItems[i].1 if let peerReference = PeerReference(peer._asPeer()), let mediaId = item.media.id { + var reactions: [MessageReaction.Reaction] = [] + for mediaArea in item.mediaAreas { + if case let .reaction(_, reaction, _) = mediaArea { + if !reactions.contains(reaction) { + reactions.append(reaction) + } + } + } resultResources[mediaId] = StoryPreloadInfo( peer: peerReference, storyId: item.id, media: item.media, + reactions: reactions, priority: .top(position: nextPriority) ) nextPriority += 1 @@ -839,7 +860,7 @@ public final class StoryContentContextImpl: StoryContentContext { for (id, info) in resultResources.sorted(by: { $0.value.priority < $1.value.priority }) { validIds.append(id) if self.preloadStoryResourceDisposables[id] == nil { - self.preloadStoryResourceDisposables[id] = preloadStoryMedia(context: context, peer: info.peer, storyId: info.storyId, media: info.media).start() + self.preloadStoryResourceDisposables[id] = preloadStoryMedia(context: context, peer: info.peer, storyId: info.storyId, media: info.media, reactions: info.reactions).start() } } @@ -996,6 +1017,18 @@ public final class SingleStoryContentContextImpl: StoryContentContext { } } } + for mediaArea in item.mediaAreas { + if case let .reaction(_, reaction, _) = mediaArea { + if case let .custom(fileId) = reaction { + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) + if allEntityFiles[mediaId] == nil { + if let file = transaction.getMedia(mediaId) as? TelegramMediaFile { + allEntityFiles[file.fileId] = file + } + } + } + } + } } return (item, peers, allEntityFiles) } @@ -1325,10 +1358,20 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { let peer = possibleItems[i].0 let item = possibleItems[i].1 if let peerReference = PeerReference(peer._asPeer()), let mediaId = item.media.id { + var reactions: [MessageReaction.Reaction] = [] + for mediaArea in item.mediaAreas { + if case let .reaction(_, reaction, _) = mediaArea { + if !reactions.contains(reaction) { + reactions.append(reaction) + } + } + } + resultResources[mediaId] = StoryPreloadInfo( peer: peerReference, storyId: item.id, media: item.media, + reactions: reactions, priority: .top(position: nextPriority) ) nextPriority += 1 @@ -1341,7 +1384,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { if let mediaId = info.media.id { validIds.append(mediaId) if self.preloadStoryResourceDisposables[mediaId] == nil { - self.preloadStoryResourceDisposables[mediaId] = preloadStoryMedia(context: context, peer: info.peer, storyId: info.storyId, media: info.media).start() + self.preloadStoryResourceDisposables[mediaId] = preloadStoryMedia(context: context, peer: info.peer, storyId: info.storyId, media: info.media, reactions: info.reactions).start() } } } @@ -1427,7 +1470,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext { } } -public func preloadStoryMedia(context: AccountContext, peer: PeerReference, storyId: Int32, media: EngineMedia) -> Signal { +public func preloadStoryMedia(context: AccountContext, peer: PeerReference, storyId: Int32, media: EngineMedia, reactions: [MessageReaction.Reaction]) -> Signal { var signals: [Signal] = [] switch media { @@ -1461,6 +1504,127 @@ public func preloadStoryMedia(context: AccountContext, peer: PeerReference, stor break } + var builtinReactions: [String] = [] + var customReactions: [Int64] = [] + for reaction in reactions { + switch reaction { + case let .builtin(value): + if !builtinReactions.contains(value) { + builtinReactions.append(value) + } + case let .custom(fileId): + if !customReactions.contains(fileId) { + customReactions.append(fileId) + } + } + } + if !builtinReactions.isEmpty { + signals.append(context.engine.stickers.availableReactions() + |> take(1) + |> mapToSignal { availableReactions -> Signal in + guard let availableReactions = availableReactions else { + return .complete() + } + + var files: [TelegramMediaFile] = [] + + for reaction in availableReactions.reactions { + for value in builtinReactions { + if case .builtin(value) = reaction.value { + files.append(reaction.selectAnimation) + } + } + } + + return combineLatest(files.map { file -> Signal in + return Signal { subscriber in + let loadSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)) + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } + + let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + |> filter { status in + if case .Local = status { + return true + } else { + return false + } + } + |> take(1) + |> map { _ -> Void in + return Void() + } + + //let fileFetchPriorityDisposable = context.engine.resources.pushPriorityDownload(resourceId: file.resource.id.stringRepresentation, priority: 1) + + let statusDisposable = statusSignal.start(completed: { + subscriber.putCompletion() + }) + let loadDisposable = loadSignal.start() + + return ActionDisposable { + statusDisposable.dispose() + loadDisposable.dispose() + //fileFetchPriorityDisposable.dispose() + } + } + }) + |> ignoreValues + }) + } + if !customReactions.isEmpty { + signals.append(context.engine.stickers.resolveInlineStickers(fileIds: customReactions) + |> take(1) + |> mapToSignal { resolvedFiles -> Signal in + var files: [TelegramMediaFile] = [] + + for (_, file) in resolvedFiles { + if customReactions.contains(file.fileId.id) { + files.append(file) + } + } + + return combineLatest(files.map { file -> Signal in + return Signal { subscriber in + let loadSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)) + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } + + let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + |> filter { status in + if case .Local = status { + return true + } else { + return false + } + } + |> take(1) + |> map { _ -> Void in + return Void() + } + + let statusDisposable = statusSignal.start(completed: { + subscriber.putCompletion() + }) + let loadDisposable = loadSignal.start() + + //let fileFetchPriorityDisposable = context.engine.resources.pushPriorityDownload(resourceId: file.resource.id.stringRepresentation, priority: 1) + + return ActionDisposable { + statusDisposable.dispose() + loadDisposable.dispose() + //fileFetchPriorityDisposable.dispose() + } + } + }) + |> ignoreValues + }) + } + return combineLatest(signals) |> ignoreValues } @@ -1549,6 +1713,127 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine break } + var builtinReactions: [String] = [] + var customReactions: [Int64] = [] + for mediaArea in storyItem.mediaAreas { + if case let .reaction(_, reaction, _) = mediaArea { + switch reaction { + case let .builtin(value): + if !builtinReactions.contains(value) { + builtinReactions.append(value) + } + case let .custom(fileId): + if !customReactions.contains(fileId) { + customReactions.append(fileId) + } + } + } + } + if !builtinReactions.isEmpty { + statusSignals.append(context.engine.stickers.availableReactions() + |> take(1) + |> mapToSignal { availableReactions -> Signal in + guard let availableReactions = availableReactions else { + return .complete() + } + + var files: [TelegramMediaFile] = [] + + for reaction in availableReactions.reactions { + for value in builtinReactions { + if case .builtin(value) = reaction.value { + files.append(reaction.selectAnimation) + } + } + } + + return combineLatest(files.map { file -> Signal in + return Signal { subscriber in + let loadSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)) + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } + + let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + |> filter { status in + if case .Local = status { + return true + } else { + return false + } + } + |> take(1) + |> map { _ -> Void in + return Void() + } + + let statusDisposable = statusSignal.start(completed: { + subscriber.putCompletion() + }) + let loadDisposable = loadSignal.start() + let fileFetchPriorityDisposable = context.engine.resources.pushPriorityDownload(resourceId: file.resource.id.stringRepresentation, priority: 1) + + return ActionDisposable { + statusDisposable.dispose() + loadDisposable.dispose() + fileFetchPriorityDisposable.dispose() + } + } + }) + |> ignoreValues + }) + } + if !customReactions.isEmpty { + statusSignals.append(context.engine.stickers.resolveInlineStickers(fileIds: customReactions) + |> take(1) + |> mapToSignal { resolvedFiles -> Signal in + var files: [TelegramMediaFile] = [] + + for (_, file) in resolvedFiles { + if customReactions.contains(file.fileId.id) { + files.append(file) + } + } + + return combineLatest(files.map { file -> Signal in + return Signal { subscriber in + let loadSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)) + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } + + let statusSignal = context.account.postbox.mediaBox.resourceStatus(file.resource) + |> filter { status in + if case .Local = status { + return true + } else { + return false + } + } + |> take(1) + |> map { _ -> Void in + return Void() + } + + let statusDisposable = statusSignal.start(completed: { + subscriber.putCompletion() + }) + let loadDisposable = loadSignal.start() + let fileFetchPriorityDisposable = context.engine.resources.pushPriorityDownload(resourceId: file.resource.id.stringRepresentation, priority: 1) + + return ActionDisposable { + statusDisposable.dispose() + loadDisposable.dispose() + fileFetchPriorityDisposable.dispose() + } + } + }) + |> ignoreValues + }) + } + return Signal { subscriber in let statusDisposable = combineLatest(statusSignals).start(completed: { subscriber.putCompletion() @@ -1574,5 +1859,15 @@ func extractItemEntityFiles(item: EngineStoryItem, allEntityFiles: [MediaId: Tel } } } + for mediaArea in item.mediaAreas { + if case let .reaction(_, reaction, _) = mediaArea { + if case let .custom(fileId) = reaction { + let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId) + if let file = allEntityFiles[mediaId] { + result[file.fileId] = file + } + } + } + } return result } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 41cb115af7..095351bd41 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -31,16 +31,18 @@ final class StoryItemContentComponent: Component { let peer: EnginePeer let item: EngineStoryItem let availableReactions: StoryAvailableReactions? + let entityFiles: [MediaId: TelegramMediaFile] let audioMode: StoryContentItem.AudioMode let isVideoBuffering: Bool let isCurrent: Bool let activateReaction: (UIView, MessageReaction.Reaction) -> Void - init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, audioMode: StoryContentItem.AudioMode, isVideoBuffering: Bool, isCurrent: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) { + init(context: AccountContext, strings: PresentationStrings, peer: EnginePeer, item: EngineStoryItem, availableReactions: StoryAvailableReactions?, entityFiles: [MediaId: TelegramMediaFile], audioMode: StoryContentItem.AudioMode, isVideoBuffering: Bool, isCurrent: Bool, activateReaction: @escaping (UIView, MessageReaction.Reaction) -> Void) { self.context = context self.strings = strings self.peer = peer self.item = item + self.entityFiles = entityFiles self.availableReactions = availableReactions self.audioMode = audioMode self.isVideoBuffering = isVideoBuffering @@ -64,6 +66,9 @@ final class StoryItemContentComponent: Component { if lhs.availableReactions != rhs.availableReactions { return false } + if lhs.entityFiles.keys != rhs.entityFiles.keys { + return false + } if lhs.isVideoBuffering != rhs.isVideoBuffering { return false } @@ -136,6 +141,12 @@ final class StoryItemContentComponent: Component { return } component.activateReaction(view, reaction) + } + self.overlaysView.requestUpdate = { [weak self] in + guard let self else { + return + } + self.state?.updated(transition: .immediate) } } @@ -605,6 +616,7 @@ final class StoryItemContentComponent: Component { peer: component.peer, story: component.item, availableReactions: component.availableReactions, + entityFiles: component.entityFiles, size: availableSize, isCaptureProtected: component.item.isForwardingDisabled, attemptSynchronous: synchronousLoad, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift index 59a997e30a..a5205b6939 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemOverlaysView.swift @@ -15,8 +15,15 @@ import MultilineTextComponent import AppBundle import EmojiTextAttachmentView import TextFormat +import AnimatedCountLabelNode +import LottieComponent +import LottieComponentResourceContent final class StoryItemOverlaysView: UIView { + static let counterFont: UIFont = { + return Font.with(size: 17.0, design: .camera, weight: .semibold, traits: .monospacedNumbers) + }() + private static let shadowImage: UIImage = { return UIImage(bundleImageName: "Stories/ReactionShadow")! }() @@ -28,11 +35,22 @@ final class StoryItemOverlaysView: UIView { private final class ItemView: HighlightTrackingButton { private let shadowView: UIImageView private let coverView: UIImageView - private var stickerView: EmojiTextAttachmentView? + + private var directStickerView: ComponentView? + private var customEmojiView: EmojiTextAttachmentView? private var file: TelegramMediaFile? + private var counterText: AnimatedCountLabelView? private var reaction: MessageReaction.Reaction? var activate: ((UIView, MessageReaction.Reaction) -> Void)? + var requestUpdate: (() -> Void)? + + private var demoCounter: Bool = false + + private var requestStickerDisposable: Disposable? + private var resolvedFile: TelegramMediaFile? + + private var customEmojiLoadDisposable: Disposable? override init(frame: CGRect) { self.shadowView = UIImageView(image: StoryItemOverlaysView.shadowImage) @@ -54,7 +72,12 @@ final class StoryItemOverlaysView: UIView { } else { let transition: Transition = .immediate transition.setSublayerTransform(view: self, transform: CATransform3DIdentity) - self.layer.animateSpring(from: 0.9 as NSNumber, to: 1.0 as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.4) + var fromScale: Double = 0.9 + if self.layer.animation(forKey: "sublayerTransform") != nil, let presentation = self.layer.presentation() { + let t = presentation.sublayerTransform + fromScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + } + self.layer.animateSpring(from: fromScale as NSNumber, to: 1.0 as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.4) } } self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) @@ -64,21 +87,41 @@ final class StoryItemOverlaysView: UIView { fatalError("init(coder:) has not been implemented") } + deinit { + self.requestStickerDisposable?.dispose() + self.customEmojiLoadDisposable?.dispose() + } + @objc private func pressed() { guard let activate = self.activate, let reaction = self.reaction else { return } activate(self, reaction) + + self.demoCounter = !self.demoCounter + self.requestUpdate?() } func update( context: AccountContext, reaction: MessageReaction.Reaction, flags: MediaArea.ReactionFlags, + counter: Int, availableReactions: StoryAvailableReactions?, + entityFiles: [MediaId: TelegramMediaFile], synchronous: Bool, size: CGSize ) { + var counter = counter + if self.demoCounter { + counter += 1 + } + + var transition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut)) + if self.reaction == nil { + transition = .immediate + } + self.reaction = reaction let insets = UIEdgeInsets(top: -0.08, left: -0.05, bottom: -0.01, right: -0.02) @@ -107,43 +150,187 @@ final class StoryItemOverlaysView: UIView { } } case let .custom(fileId): - let _ = fileId + if let resolvedFile = self.resolvedFile, resolvedFile.fileId.id == fileId { + file = resolvedFile + } else if let value = entityFiles[MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)] { + file = value + } else { + if self.requestStickerDisposable == nil { + self.requestStickerDisposable = (context.engine.stickers.resolveInlineStickers(fileIds: [fileId]) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self else { + return + } + if let value = result[fileId] { + self.resolvedFile = value + self.requestUpdate?() + } + }) + } + } + } + } + + if counter != 0 { + var conterTransition = transition + let counterText: AnimatedCountLabelView + if let current = self.counterText { + counterText = current + } else { + conterTransition = conterTransition.withAnimation(.none) + counterText = AnimatedCountLabelView(frame: CGRect()) + counterText.isUserInteractionEnabled = false + self.counterText = counterText + self.addSubview(counterText) + } + + var segments: [AnimatedCountLabelView.Segment] = [] + segments.append(.number(counter, NSAttributedString(string: "\(counter)", font: counterFont, textColor: flags.contains(.isDark) ? .white : .black))) + let counterTextLayout = counterText.update(size: CGSize(width: 200.0, height: 200.0), segments: segments, transition: conterTransition.containedViewLayoutTransition) + conterTransition.setPosition(view: counterText, position: CGPoint(x: size.width * 0.5, y: size.height * 0.765)) + conterTransition.setBounds(view: counterText, bounds: CGRect(origin: CGPoint(), size: counterTextLayout.size)) + + let counterScale = max(0.01, min(1.8, size.width / 140.0)) + conterTransition.setScale(view: counterText, scale: counterScale) + + if !transition.animation.isImmediate && conterTransition.animation.isImmediate { + transition.animateAlpha(view: counterText, from: 0.0, to: 1.0) + transition.animateScale(view: counterText, from: 0.001, to: counterScale) + } + } else { + if let counterText = self.counterText { + self.counterText = nil + transition.setAlpha(view: counterText, alpha: 0.0, completion: { [weak counterText] _ in + counterText?.removeFromSuperview() + }) + transition.setScale(view: counterText, scale: 0.001) } } if self.file?.fileId != file?.fileId, let file { self.file = file - let stickerView: EmojiTextAttachmentView - if let current = self.stickerView { - stickerView = current - } else { - stickerView = EmojiTextAttachmentView( - context: context, - userLocation: .other, - emoji: ChatTextInputTextCustomEmojiAttribute( - interactivelySelectedFromPackId: nil, - fileId: file.fileId.id, - file: file - ), - file: file, - cache: context.animationCache, - renderer: context.animationRenderer, - placeholderColor: UIColor(white: 0.0, alpha: 0.1), - pointSize: CGSize(width: itemSize.width, height: itemSize.height) + let isBuiltinSticker = file.isAnimatedSticker && !file.isVideoSticker && !file.isVideoEmoji + + if isBuiltinSticker { + let directStickerView: ComponentView + if let current = self.directStickerView { + directStickerView = current + } else { + directStickerView = ComponentView() + self.directStickerView = directStickerView + + self.customEmojiLoadDisposable?.dispose() + self.customEmojiLoadDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)).start() + } + var color: UIColor? + if file.isCustomTemplateEmoji { + color = flags.contains(.isDark) ? .white : .black + } + let _ = directStickerView.update( + transition: .immediate, + component: AnyComponent(LottieComponent( + content: LottieComponent.ResourceContent(context: context, file: file, attemptSynchronously: synchronous), + color: color, + renderingScale: 2.0, + loop: true + )), + environment: {}, + containerSize: itemSize ) - stickerView.isUserInteractionEnabled = false - self.stickerView = stickerView - self.addSubview(stickerView) + } else { + if let directStickerView = self.directStickerView { + self.directStickerView = nil + directStickerView.view?.removeFromSuperview() + } } - stickerView.frame = itemSize.centered(around: CGPoint(x: size.width * 0.5, y: size.height * 0.47)) + if !isBuiltinSticker { + let customEmojiView: EmojiTextAttachmentView + if let current = self.customEmojiView { + customEmojiView = current + } else { + customEmojiView = EmojiTextAttachmentView( + context: context, + userLocation: .other, + emoji: ChatTextInputTextCustomEmojiAttribute( + interactivelySelectedFromPackId: nil, + fileId: file.fileId.id, + file: file + ), + file: file, + cache: context.animationCache, + renderer: context.animationRenderer, + placeholderColor: flags.contains(.isDark) ? UIColor(white: 1.0, alpha: 0.1) : UIColor(white: 0.0, alpha: 0.1), + pointSize: CGSize(width: min(256, itemSize.width), height: min(256, itemSize.height)) + ) + customEmojiView.updateTextColor(flags.contains(.isDark) ? .white : .black) + + self.customEmojiLoadDisposable?.dispose() + self.customEmojiLoadDisposable = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .sticker, reference: .standalone(resource: file.resource)).start() + + customEmojiView.isUserInteractionEnabled = false + self.customEmojiView = customEmojiView + self.addSubview(customEmojiView) + } + } else { + if let customEmojiView = self.customEmojiView { + self.customEmojiView = nil + customEmojiView.removeFromSuperview() + } + } + } + + if let customEmojiView = self.customEmojiView { + var stickerTransition = transition + if customEmojiView.bounds.isEmpty { + stickerTransition = stickerTransition.withAnimation(.none) + } + + let counterFractionOffset: CGFloat + let stickerScale: CGFloat + if counter != 0 { + counterFractionOffset = -0.05 + stickerScale = 0.8 + } else { + counterFractionOffset = 0.0 + stickerScale = 1.0 + } + let stickerFrame = itemSize.centered(around: CGPoint(x: size.width * 0.5, y: size.height * (0.47 + counterFractionOffset))) + + stickerTransition.setPosition(view: customEmojiView, position: stickerFrame.center) + stickerTransition.setBounds(view: customEmojiView, bounds: CGRect(origin: CGPoint(), size: stickerFrame.size)) + stickerTransition.setScale(view: customEmojiView, scale: stickerScale) + } + + if let directStickerView = self.directStickerView?.view { + var stickerTransition = transition + if directStickerView.superview == nil { + self.addSubview(directStickerView) + stickerTransition = stickerTransition.withAnimation(.none) + } + + let counterFractionOffset: CGFloat + let stickerScale: CGFloat + if counter != 0 { + counterFractionOffset = -0.05 + stickerScale = 0.8 + } else { + counterFractionOffset = 0.0 + stickerScale = 1.0 + } + let stickerFrame = itemSize.centered(around: CGPoint(x: size.width * 0.5, y: size.height * (0.47 + counterFractionOffset))) + + stickerTransition.setPosition(view: directStickerView, position: stickerFrame.center) + stickerTransition.setBounds(view: directStickerView, bounds: CGRect(origin: CGPoint(), size: stickerFrame.size)) + stickerTransition.setScale(view: directStickerView, scale: stickerScale) } } } private var itemViews: [Int: ItemView] = [:] var activate: ((UIView, MessageReaction.Reaction) -> Void)? + var requestUpdate: (() -> Void)? override init(frame: CGRect) { super.init(frame: frame) @@ -171,6 +358,7 @@ final class StoryItemOverlaysView: UIView { peer: EnginePeer, story: EngineStoryItem, availableReactions: StoryAvailableReactions?, + entityFiles: [MediaId: TelegramMediaFile], size: CGSize, isCaptureProtected: Bool, attemptSynchronous: Bool, @@ -181,7 +369,9 @@ final class StoryItemOverlaysView: UIView { switch mediaArea { case let .reaction(coordinates, reaction, flags): let referenceSize = size - let areaSize = CGSize(width: coordinates.width / 100.0 * referenceSize.width, height: coordinates.height / 100.0 * referenceSize.height) + var areaSize = CGSize(width: coordinates.width / 100.0 * referenceSize.width, height: coordinates.height / 100.0 * referenceSize.height) + areaSize.width *= 0.97 + areaSize.height *= 0.97 let targetFrame = CGRect(x: coordinates.x / 100.0 * referenceSize.width - areaSize.width * 0.5, y: coordinates.y / 100.0 * referenceSize.height - areaSize.height * 0.5, width: areaSize.width, height: areaSize.height) if targetFrame.width < 5.0 || targetFrame.height < 5.0 { continue @@ -196,6 +386,9 @@ final class StoryItemOverlaysView: UIView { itemView.activate = { [weak self] view, reaction in self?.activate?(view, reaction) } + itemView.requestUpdate = { [weak self] in + self?.requestUpdate?() + } self.itemViews[itemId] = itemView self.addSubview(itemView) } @@ -207,7 +400,9 @@ final class StoryItemOverlaysView: UIView { context: context, reaction: reaction, flags: flags, + counter: 0, availableReactions: availableReactions, + entityFiles: entityFiles, synchronous: attemptSynchronous, size: targetFrame.size ) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index fc01e1b627..2629646c12 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -1166,7 +1166,14 @@ public final class StoryItemSetContainerComponent: Component { self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) self.component?.controller()?.dismiss() } else if translation.y < -200.0 || (translation.y < -100.0 && velocity.y < -100.0) { + 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 { self.viewListDisplayState = self.targetViewListDisplayStateIsFull ? .full : .half self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring))) self.dismissAllTooltips() @@ -1488,6 +1495,7 @@ public final class StoryItemSetContainerComponent: Component { peer: component.slice.peer, item: item.storyItem, availableReactions: component.availableReactions, + entityFiles: item.entityFiles, audioMode: component.audioMode, isVideoBuffering: visibleItem.isBuffering, isCurrent: index == centralIndex, @@ -1574,7 +1582,18 @@ public final class StoryItemSetContainerComponent: Component { view.setProgressMode(itemProgressMode) } - if component.slice.peer.id == component.context.account.peerId || component.slice.item.storyItem.isPending { + var isChannel = false + var displayFooter = false + if case .channel = component.slice.peer { + displayFooter = true + isChannel = true + } else if component.slice.peer.id == component.context.account.peerId { + displayFooter = true + } else if component.slice.item.storyItem.isPending { + displayFooter = true + } + + if displayFooter { let contentViewsShadowView: UIImageView if let current = visibleItem.contentViewsShadowView { contentViewsShadowView = current @@ -1608,8 +1627,34 @@ public final class StoryItemSetContainerComponent: Component { transition: itemTransition, component: AnyComponent(StoryFooterPanelComponent( context: component.context, + theme: component.theme, strings: component.strings, storyItem: item.storyItem, + myReaction: item.storyItem.myReaction.flatMap { value -> StoryFooterPanelComponent.MyReaction? in + var centerAnimation: TelegramMediaFile? + var animationFileId: Int64? + + switch value { + case .builtin: + if let availableReactions = component.availableReactions { + for availableReaction in availableReactions.reactionItems { + if availableReaction.reaction.rawValue == value { + centerAnimation = availableReaction.listAnimation + break + } + } + } + case let .custom(fileId): + animationFileId = fileId + } + + if animationFileId == nil && centerAnimation == nil { + return nil + } + + return StoryFooterPanelComponent.MyReaction(reaction: value, file: centerAnimation, animationFileId: animationFileId) + }, + isChannel: isChannel, externalViews: nil, expandFraction: footerExpandFraction, expandViewStats: { [weak self] in @@ -1672,6 +1717,18 @@ public final class StoryItemSetContainerComponent: Component { return } self.performMoreAction(sourceView: sourceView, gesture: gesture) + }, + likeAction: { [weak self] in + guard let self else { + return + } + self.performLikeAction() + }, + forwardAction: { [weak self] in + guard let self else { + return + } + self.sendMessageContext.performShareAction(view: self) } )), environment: {}, @@ -1759,7 +1816,15 @@ public final class StoryItemSetContainerComponent: Component { guard let component = self.component else { return false } + + 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 { self.viewListDisplayState = .half self.state?.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) @@ -2579,7 +2644,12 @@ public final class StoryItemSetContainerComponent: Component { let startTime23 = CFAbsoluteTimeGetCurrent() - if component.slice.peer.id != component.context.account.peerId { + var isChannel = false + if case .channel = component.slice.peer { + isChannel = true + } + + if component.slice.peer.id != component.context.account.peerId && !isChannel { var haveLikeOptions = false if case .user = component.slice.peer { haveLikeOptions = true @@ -2589,11 +2659,6 @@ public final class StoryItemSetContainerComponent: Component { } } - var isChannel = false - if case .channel = component.slice.peer { - isChannel = true - } - inputPanelSize = self.inputPanel.update( transition: inputPanelTransition, component: AnyComponent(MessageInputPanelComponent( @@ -2897,7 +2962,15 @@ public final class StoryItemSetContainerComponent: Component { let startTime4 = CFAbsoluteTimeGetCurrent() var validViewListIds: [Int32] = [] - if component.slice.peer.id == component.context.account.peerId, let currentIndex = component.slice.allItems.firstIndex(where: { $0.storyItem.id == component.slice.item.storyItem.id }) { + + 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 }) { var visibleViewListIds: [Int32] = [component.slice.item.storyItem.id] if self.viewListDisplayState != .hidden, let viewListPanState = self.viewListPanState { if currentIndex != 0 { @@ -2925,7 +2998,7 @@ public final class StoryItemSetContainerComponent: Component { for (id, views) in preloadViewListIds { if component.sharedViewListsContext.viewLists[StoryId(peerId: component.slice.peer.id, id: id)] == nil { - let viewList = component.context.engine.messages.storyViewList(id: id, views: views, listMode: .everyone, sortMode: .reactionsFirst) + let viewList = component.context.engine.messages.storyViewList(peerId: component.slice.peer.id, id: id, views: views, listMode: .everyone, sortMode: .reactionsFirst) component.sharedViewListsContext.viewLists[StoryId(peerId: component.slice.peer.id, id: id)] = viewList } } @@ -3594,14 +3667,18 @@ public final class StoryItemSetContainerComponent: Component { } let storyPrivacyIcon: StoryPrivacyIconComponent.Privacy? - if component.slice.item.storyItem.isCloseFriends { - storyPrivacyIcon = .closeFriends - } else if component.slice.item.storyItem.isContacts { - storyPrivacyIcon = .contacts - } else if component.slice.item.storyItem.isSelectedContacts { - storyPrivacyIcon = .selectedContacts - } else if component.slice.peer.id == component.context.account.peerId { - storyPrivacyIcon = .everyone + if case .user = component.slice.peer { + if component.slice.item.storyItem.isCloseFriends { + storyPrivacyIcon = .closeFriends + } else if component.slice.item.storyItem.isContacts { + storyPrivacyIcon = .contacts + } else if component.slice.item.storyItem.isSelectedContacts { + storyPrivacyIcon = .selectedContacts + } else if component.slice.peer.id == component.context.account.peerId { + storyPrivacyIcon = .everyone + } else { + storyPrivacyIcon = nil + } } else { storyPrivacyIcon = nil } @@ -5174,10 +5251,19 @@ public final class StoryItemSetContainerComponent: Component { guard let self, let component = self.component else { return } - guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else { - return + + var likeButtonView: UIView? + + if let visibleItem = self.visibleItems[component.slice.item.storyItem.id], let footerPanelView = visibleItem.footerPanel?.view as? StoryFooterPanelComponent.View { + likeButtonView = footerPanelView.likeButtonView + } else { + guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else { + return + } + likeButtonView = inputPanelView.likeButtonView } - guard let likeButtonView = inputPanelView.likeButtonView else { + + guard let likeButtonView else { return } @@ -5703,6 +5789,49 @@ public final class StoryItemSetContainerComponent: Component { }))) } + items.append(.action(ContextMenuActionItem(text: component.strings.Story_ContextDeleteStory, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) + }, action: { [weak self] _, a in + a(.default) + + guard let self, let component = self.component else { + return + } + + let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) + let actionSheet = ActionSheetController(presentationData: presentationData) + + actionSheet.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: component.strings.Story_ContextDeleteStory, color: .destructive, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + + guard let self, let component = self.component else { + return + } + component.delete() + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + + actionSheet.dismissed = { [weak self] _ in + guard let self else { + return + } + self.sendMessageContext.actionSheet = nil + self.updateIsProgressPaused() + } + self.sendMessageContext.actionSheet = actionSheet + self.updateIsProgressPaused() + + component.presentController(actionSheet, nil) + }))) + let (tip, tipSignal) = self.getLinkedStickerPacks() let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal) @@ -5814,6 +5943,8 @@ public final class StoryItemSetContainerComponent: Component { var isHidden = false if case let .user(user) = component.slice.peer, let storiesHidden = user.storiesHidden { isHidden = storiesHidden + } else if case let .channel(channel) = component.slice.peer, let storiesHidden = channel.storiesHidden { + isHidden = storiesHidden } items.append(.action(ContextMenuActionItem(text: isHidden ? component.strings.StoryFeed_ContextUnarchive : component.strings.StoryFeed_ContextArchive, icon: { theme in diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 42e0decde2..d17163dc8a 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -3318,7 +3318,10 @@ final class StoryItemSetContainerSendMessage { guard let view, let component = view.component else { return } - let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: reaction).start() + if case .channel = component.slice.peer { + } else { + let _ = component.context.engine.messages.setStoryReaction(peerId: component.slice.peer.id, id: component.slice.item.storyItem.id, reaction: reaction).start() + } let targetFrame = reactionView.convert(reactionView.bounds, to: view) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift index 8e035b7b07..12f9512f23 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift @@ -702,7 +702,7 @@ final class StoryItemSetViewListComponent: Component { parentSource = nil } - self.viewList = component.context.engine.messages.storyViewList(id: component.storyItem.id, views: views, listMode: mappedListMode, sortMode: mappedSortMode, searchQuery: query, parentSource: parentSource) + self.viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: mappedListMode, sortMode: mappedSortMode, searchQuery: query, parentSource: parentSource) } } } else { @@ -711,7 +711,7 @@ final class StoryItemSetViewListComponent: Component { if let current = component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] { viewList = current } else { - viewList = component.context.engine.messages.storyViewList(id: component.storyItem.id, views: views, listMode: .everyone, sortMode: .reactionsFirst) + viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: .everyone, sortMode: .reactionsFirst) component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] = viewList } self.viewList = viewList @@ -730,7 +730,7 @@ final class StoryItemSetViewListComponent: Component { case .recentFirst: mappedSortMode = .recentFirst } - self.viewList = component.context.engine.messages.storyViewList(id: component.storyItem.id, views: views, listMode: mappedListMode, sortMode: mappedSortMode, parentSource: component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)]) + self.viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: mappedListMode, sortMode: mappedSortMode, parentSource: component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)]) } } } @@ -1376,7 +1376,7 @@ final class StoryItemSetViewListComponent: Component { if let current = component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] { viewList = current } else { - viewList = component.context.engine.messages.storyViewList(id: component.storyItem.id, views: views, listMode: .everyone, sortMode: .reactionsFirst) + viewList = component.context.engine.messages.storyViewList(peerId: component.peerId, id: component.storyItem.id, views: views, listMode: .everyone, sortMode: .reactionsFirst) component.sharedListsContext.viewLists[StoryId(peerId: component.peerId, id: component.storyItem.id)] = viewList } self.mainViewList = viewList diff --git a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/BUILD b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/BUILD index 3a7676e609..89ed0682d2 100644 --- a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/BUILD +++ b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/BUILD @@ -20,6 +20,7 @@ swift_library( "//submodules/TelegramUI/Components/MoreHeaderButton", "//submodules/SemanticStatusNode", "//submodules/AnimatedCountLabelNode", + "//submodules/TelegramUI/Components/MessageInputActionButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift index 366c1eb2e0..cc7058958b 100644 --- a/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryFooterPanelComponent/Sources/StoryFooterPanelComponent.swift @@ -12,6 +12,7 @@ import SemanticStatusNode import SwiftSignalKit import TelegramPresentationData import AnimatedCountLabelNode +import MessageInputActionButtonComponent public final class StoryFooterPanelComponent: Component { public final class AnimationHint { @@ -22,45 +23,81 @@ public final class StoryFooterPanelComponent: Component { } } + public struct MyReaction: Equatable { + public let reaction: MessageReaction.Reaction + public let file: TelegramMediaFile? + public let animationFileId: Int64? + + public init(reaction: MessageReaction.Reaction, file: TelegramMediaFile?, animationFileId: Int64?) { + self.reaction = reaction + self.file = file + self.animationFileId = animationFileId + } + } + public let context: AccountContext + public let theme: PresentationTheme public let strings: PresentationStrings public let storyItem: EngineStoryItem + public let myReaction: MyReaction? + public let isChannel: Bool public let externalViews: EngineStoryItem.Views? public let expandFraction: CGFloat public let expandViewStats: () -> Void public let deleteAction: () -> Void public let moreAction: (UIView, ContextGesture?) -> Void + public let likeAction: () -> Void + public let forwardAction: () -> Void public init( context: AccountContext, + theme: PresentationTheme, strings: PresentationStrings, storyItem: EngineStoryItem, + myReaction: MyReaction?, + isChannel: Bool, externalViews: EngineStoryItem.Views?, expandFraction: CGFloat, expandViewStats: @escaping () -> Void, deleteAction: @escaping () -> Void, - moreAction: @escaping (UIView, ContextGesture?) -> Void + moreAction: @escaping (UIView, ContextGesture?) -> Void, + likeAction: @escaping () -> Void, + forwardAction: @escaping () -> Void ) { self.context = context + self.theme = theme self.strings = strings self.storyItem = storyItem + self.myReaction = myReaction + self.isChannel = isChannel self.externalViews = externalViews self.expandViewStats = expandViewStats self.expandFraction = expandFraction self.deleteAction = deleteAction self.moreAction = moreAction + self.likeAction = likeAction + self.forwardAction = forwardAction } public static func ==(lhs: StoryFooterPanelComponent, rhs: StoryFooterPanelComponent) -> Bool { if lhs.context !== rhs.context { return false } + if lhs.theme !== rhs.theme { + return false + } if lhs.strings !== rhs.strings { return false } if lhs.storyItem != rhs.storyItem { return false } + if lhs.myReaction != rhs.myReaction { + return false + } + if lhs.isChannel != rhs.isChannel { + return false + } if lhs.externalViews != rhs.externalViews { return false } @@ -76,6 +113,10 @@ public final class StoryFooterPanelComponent: Component { private let viewStatsLabelText = ComponentView() private let deleteButton = ComponentView() + private var likeButton: ComponentView? + private var likeStatsText: AnimatedCountLabelView? + private var forwardButton: ComponentView? + private var reactionStatsIcon: UIImageView? private var reactionStatsText: AnimatedCountLabelView? @@ -96,6 +137,10 @@ public final class StoryFooterPanelComponent: Component { public let externalContainerView: UIView + public var likeButtonView: UIView? { + return self.likeButton?.view + } + override init(frame: CGRect) { self.viewStatsButton = HighlightTrackingButton() self.viewStatsCountText = AnimatedCountLabelView(frame: CGRect()) @@ -168,6 +213,7 @@ public final class StoryFooterPanelComponent: Component { func update(component: StoryFooterPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { let isFirstTime = self.component == nil + let previousComponent = self.component self.isUserInteractionEnabled = component.expandFraction == 0.0 @@ -287,8 +333,10 @@ public final class StoryFooterPanelComponent: Component { let _ = baseViewCountAlpha var peers: [EnginePeer] = [] - if let seenPeers = component.externalViews?.seenPeers ?? component.storyItem.views?.seenPeers { - peers = Array(seenPeers.prefix(3)) + if !component.isChannel { + if let seenPeers = component.externalViews?.seenPeers ?? component.storyItem.views?.seenPeers { + peers = Array(seenPeers.prefix(3)) + } } let avatarsContent = self.avatarsContext.update(peers: peers, animated: false) let avatarsSize = self.avatarsView.update(context: component.context, content: avatarsContent, itemSize: CGSize(width: 30.0, height: 30.0), animation: isFirstTime ? ListViewItemUpdateAnimation.None : ListViewItemUpdateAnimation.System(duration: 0.25, transition: ControlledTransition(duration: 0.25, curve: .easeInOut, interactive: false)), synchronousLoad: synchronousLoad) @@ -300,15 +348,180 @@ public final class StoryFooterPanelComponent: Component { reactionCount = views.reactedCount } + if component.isChannel { + viewCount = max(1, viewCount) + if component.storyItem.myReaction != nil { + reactionCount = max(1, reactionCount) + } + } + self.viewStatsButton.isEnabled = viewCount != 0 + var rightContentOffset: CGFloat = availableSize.width - 12.0 + + if component.isChannel { + var likeStatsTransition = transition + + if transition.animation.isImmediate, !isFirstTime, let previousComponent, previousComponent.storyItem.id == component.storyItem.id, previousComponent.expandFraction == component.expandFraction { + likeStatsTransition = .easeInOut(duration: 0.2) + } + + let likeStatsText: AnimatedCountLabelView + if let current = self.likeStatsText { + likeStatsText = current + } else { + likeStatsTransition = likeStatsTransition.withAnimation(.none) + likeStatsText = AnimatedCountLabelView(frame: CGRect()) + likeStatsText.isUserInteractionEnabled = false + self.likeStatsText = likeStatsText + self.externalContainerView.addSubview(likeStatsText) + } + + let reactionStatsLayout = likeStatsText.update( + size: CGSize(width: availableSize.width, height: size.height), + segments: [ + .number(reactionCount, NSAttributedString(string: "\(reactionCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white)) + ], + transition: (isFirstTime || likeStatsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) + ) + 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 + + let likeButton: ComponentView + if let current = self.likeButton { + likeButton = current + } else { + likeButton = ComponentView() + self.likeButton = likeButton + } + + let forwardButton: ComponentView + if let current = self.forwardButton { + forwardButton = current + } else { + forwardButton = ComponentView() + self.forwardButton = forwardButton + } + + let likeButtonSize = likeButton.update( + transition: likeStatsTransition, + component: AnyComponent(MessageInputActionButtonComponent( + mode: .like(reaction: component.myReaction?.reaction, file: component.myReaction?.file, animationFileId: component.myReaction?.animationFileId), + storyId: component.storyItem.id, + action: { [weak self] _, action, _ in + guard let self, let component = self.component else { + return + } + guard case .up = action else { + return + } + component.likeAction() + }, + longPressAction: nil, + switchMediaInputMode: { + }, + updateMediaCancelFraction: { _ in + }, + lockMediaRecording: { + }, + stopAndPreviewMediaRecording: { + }, + moreAction: { _, _ in }, + context: component.context, + theme: component.theme, + strings: component.strings, + presentController: { _ in }, + audioRecorder: nil, + videoRecordingStatus: nil + )), + environment: {}, + containerSize: CGSize(width: 33.0, height: 33.0) + ) + if let likeButtonView = likeButton.view { + if likeButtonView.superview == nil { + self.addSubview(likeButtonView) + } + var likeButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - likeButtonSize.width, y: floor((size.height - likeButtonSize.height) * 0.5)), size: likeButtonSize) + likeButtonFrame.origin.y += component.expandFraction * 45.0 + + likeStatsTransition.setPosition(view: likeButtonView, position: likeButtonFrame.center) + likeStatsTransition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size)) + likeStatsTransition.setAlpha(view: likeButtonView, alpha: 1.0 - component.expandFraction) + + rightContentOffset -= likeButtonSize.width + 14.0 + } + + let forwardButtonSize = forwardButton.update( + transition: likeStatsTransition, + component: AnyComponent(MessageInputActionButtonComponent( + mode: .forward, + storyId: component.storyItem.id, + action: { [weak self] _, action, _ in + guard let self, let component = self.component else { + return + } + guard case .up = action else { + return + } + component.forwardAction() + }, + longPressAction: nil, + switchMediaInputMode: { + }, + updateMediaCancelFraction: { _ in + }, + lockMediaRecording: { + }, + stopAndPreviewMediaRecording: { + }, + moreAction: { _, _ in }, + context: component.context, + theme: component.theme, + strings: component.strings, + presentController: { _ in }, + audioRecorder: nil, + videoRecordingStatus: nil + )), + environment: {}, + containerSize: CGSize(width: 33.0, height: 33.0) + ) + if let forwardButtonView = forwardButton.view { + if forwardButtonView.superview == nil { + self.addSubview(forwardButtonView) + } + var forwardButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - likeButtonSize.width, y: floor((size.height - forwardButtonSize.height) * 0.5)), size: forwardButtonSize) + forwardButtonFrame.origin.y += component.expandFraction * 45.0 + + likeStatsTransition.setPosition(view: forwardButtonView, position: forwardButtonFrame.center) + likeStatsTransition.setBounds(view: forwardButtonView, bounds: CGRect(origin: CGPoint(), size: forwardButtonFrame.size)) + likeStatsTransition.setAlpha(view: forwardButtonView, alpha: 1.0 - component.expandFraction) + + rightContentOffset -= forwardButtonSize.width + 8.0 + } + } else { + if let likeButton = self.likeButton { + self.likeButton = nil + likeButton.view?.removeFromSuperview() + } + if let forwardButton = self.forwardButton { + self.forwardButton = nil + forwardButton.view?.removeFromSuperview() + } + } + var regularSegments: [AnimatedCountLabelView.Segment] = [] if viewCount != 0 { - regularSegments.append(.number(viewCount, NSAttributedString(string: "\(viewCount)", font: Font.regular(15.0), textColor: .white))) + regularSegments.append(.number(viewCount, NSAttributedString(string: "\(viewCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white))) } let viewPart: String - if viewCount == 0 { + if component.isChannel { + viewPart = "" + } else if viewCount == 0 { viewPart = component.strings.Story_Footer_NoViews } else { var string = component.strings.Story_Footer_ViewCount(Int32(viewCount)) @@ -336,7 +549,7 @@ public final class StoryFooterPanelComponent: Component { var reactionsIconSize: CGSize? var reactionsTextSize: CGSize? - if reactionCount != 0 { + if reactionCount != 0 && !component.isChannel { var reactionsTransition = transition let reactionStatsIcon: UIImageView if let current = self.reactionStatsIcon { @@ -365,7 +578,7 @@ public final class StoryFooterPanelComponent: Component { let reactionStatsLayout = reactionStatsText.update( size: CGSize(width: availableSize.width, height: size.height), segments: [ - .number(reactionCount, NSAttributedString(string: "\(reactionCount)", font: Font.regular(15.0), textColor: .white)) + .number(reactionCount, NSAttributedString(string: "\(reactionCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white)) ], transition: (isFirstTime || reactionsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut) ) @@ -396,14 +609,18 @@ public final class StoryFooterPanelComponent: Component { let avatarViewsSpacing: CGFloat = 18.0 let viewsIconSpacing: CGFloat = 2.0 - let reactionsIconSpacing: CGFloat = 2.0 + let reactionsIconSpacing: CGFloat = component.isChannel ? 5.0 : 2.0 var contentWidth: CGFloat = 0.0 contentWidth += (avatarsSize.width + avatarViewsSpacing) * (1.0 - component.expandFraction) if let image = self.viewsIconView.image { - if viewCount != 0 { - contentWidth += (image.size.width + viewsIconSpacing) * component.expandFraction + if component.isChannel { + contentWidth += image.size.width + viewsIconSpacing + } else { + if viewCount != 0 { + contentWidth += (image.size.width + viewsIconSpacing) * component.expandFraction + } } } @@ -412,13 +629,22 @@ public final class StoryFooterPanelComponent: Component { } else { contentWidth += viewStatsTextLayout.size.width } - contentWidth += viewStatsLabelSize.width * (1.0 - component.expandFraction) + if !component.isChannel { + contentWidth += viewStatsLabelSize.width * (1.0 - component.expandFraction) + } - if let reactionsIconSize, let reactionsTextSize { - contentWidth += viewsReactionsSpacing - contentWidth += reactionsIconSize.width - contentWidth += reactionsIconSpacing - contentWidth += reactionsTextSize.width + if component.isChannel { + /*if let reactionsIconSize { + contentWidth += viewsReactionsSpacing + contentWidth += reactionsIconSize.width + }*/ + } else { + if let reactionsIconSize, let reactionsTextSize { + contentWidth += viewsReactionsSpacing + contentWidth += reactionsIconSize.width + contentWidth += reactionsIconSpacing + contentWidth += reactionsTextSize.width + } } let minContentX: CGFloat = 16.0 @@ -435,19 +661,31 @@ public final class StoryFooterPanelComponent: Component { let viewsIconFrame = CGRect(origin: CGPoint(x: contentX, y: floor((size.height - image.size.height) * 0.5)), size: image.size) transition.setPosition(view: self.viewsIconView, position: viewsIconFrame.center) transition.setBounds(view: self.viewsIconView, bounds: CGRect(origin: CGPoint(), size: viewsIconFrame.size)) - if viewCount == 0 { - transition.setAlpha(view: self.viewsIconView, alpha: 0.0) + + if component.isChannel { + transition.setAlpha(view: self.viewsIconView, alpha: 1.0) + transition.setScale(view: self.viewsIconView, scale: 1.0) } else { - transition.setAlpha(view: self.viewsIconView, alpha: component.expandFraction) + if viewCount == 0 { + transition.setAlpha(view: self.viewsIconView, alpha: 0.0) + } else { + transition.setAlpha(view: self.viewsIconView, alpha: component.expandFraction) + } + transition.setScale(view: self.viewsIconView, scale: CGFloat(1.0).interpolate(to: CGFloat(0.1), amount: 1.0 - component.expandFraction)) } - transition.setScale(view: self.viewsIconView, scale: CGFloat(1.0).interpolate(to: CGFloat(0.1), amount: 1.0 - component.expandFraction)) } - if !avatarsSize.width.isZero { - contentX += (avatarsSize.width + avatarViewsSpacing) * (1.0 - component.expandFraction) - } - if let image = self.viewsIconView.image { - contentX += (image.size.width + viewsIconSpacing) * component.expandFraction + if component.isChannel { + if let image = self.viewsIconView.image { + contentX += image.size.width + viewsIconSpacing + } + } else { + if !avatarsSize.width.isZero { + contentX += (avatarsSize.width + avatarViewsSpacing) * (1.0 - component.expandFraction) + } + if let image = self.viewsIconView.image { + contentX += (image.size.width + viewsIconSpacing) * component.expandFraction + } } transition.setFrame(view: self.viewStatsCountText, frame: CGRect(origin: CGPoint(x: contentX, y: floor((size.height - viewStatsTextLayout.size.height) * 0.5)), size: viewStatsTextLayout.size)) @@ -471,7 +709,9 @@ public final class StoryFooterPanelComponent: Component { transition.setAlpha(view: viewStatsLabelTextView, alpha: 1.0 - component.expandFraction) transition.setScale(view: viewStatsLabelTextView, scale: CGFloat(1.0).interpolate(to: CGFloat(0.1), amount: component.expandFraction)) } - contentX += viewStatsLabelSize.width * (1.0 - component.expandFraction) + if !component.isChannel { + contentX += viewStatsLabelSize.width * (1.0 - component.expandFraction) + } if let reactionStatsIcon = self.reactionStatsIcon, let reactionsIconSize, let reactionStatsText = self.reactionStatsText, let reactionsTextSize { contentX += viewsReactionsSpacing @@ -489,8 +729,6 @@ public final class StoryFooterPanelComponent: Component { transition.setFrame(view: self.viewStatsButton, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: statsButtonWidth, height: baseHeight))) self.viewStatsButton.isUserInteractionEnabled = component.expandFraction == 0.0 - var rightContentOffset: CGFloat = availableSize.width - 12.0 - let isPending = component.storyItem.isPending self.viewsIconView.isHidden = isPending self.viewStatsCountText.isHidden = isPending @@ -522,10 +760,15 @@ public final class StoryFooterPanelComponent: Component { transition.setPosition(view: deleteButtonView, position: deleteButtonFrame.center) transition.setBounds(view: deleteButtonView, bounds: CGRect(origin: CGPoint(), size: deleteButtonFrame.size)) - rightContentOffset -= deleteButtonSize.width + 8.0 - transition.setAlpha(view: deleteButtonView, alpha: 1.0 - sideContentFraction) transition.setScale(view: deleteButtonView, scale: CGFloat(1.0).interpolate(to: CGFloat(0.1), amount: sideContentFraction)) + + if component.isChannel { + deleteButtonView.isHidden = true + } else { + deleteButtonView.isHidden = false + rightContentOffset -= deleteButtonSize.width + 8.0 + } } return size diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index b9b414cad5..ec7277dbc2 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -865,56 +865,6 @@ final class AuthorizedApplicationContext { self.rootController.setViewControllers(controllers, animated: false) self.rootController.chatListController?.openStoriesFromNotification(peerId: storyId.peerId, storyId: storyId.id) - - /*if let chatListController = self.rootController.chatListController as? ChatListControllerImpl { - let _ = (chatListController.context.account.postbox.transaction { transaction -> Bool in - if let peer = transaction.getPeer(storyId.peerId) as? TelegramUser, let storiesHidden = peer.storiesHidden, storiesHidden { - return true - } else { - return false - } - } - |> deliverOnMainQueue).start(next: { [weak self] isArchived in - guard let self, let chatListController = self.rootController.chatListController as? ChatListControllerImpl else { - return - } - if isArchived { - if let navigationController = (chatListController.navigationController as? NavigationController) { - var viewControllers = navigationController.viewControllers - if let index = viewControllers.firstIndex(where: { c in - if let c = c as? ChatListControllerImpl { - if case .chatList(groupId: .archive) = c.location { - return true - } - } - return false - }) { - (viewControllers[index] as? ChatListControllerImpl)?.scrollToStories() - viewControllers.removeSubrange((index + 1) ..< viewControllers.count) - navigationController.setViewControllers(viewControllers, animated: false) - } else { - let archive = ChatListControllerImpl(context: chatListController.context, location: .chatList(groupId: .archive), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) - archive.onDidAppear = { [weak archive] in - Queue.mainQueue().after(0.1, { - guard let archive else { - return - } - if archive.hasStorySubscriptions { - archive.scrollToStoriesAnimated() - } - }) - } - navigationController.pushViewController(archive, animated: false, completion: {}) - } - } - } else { - chatListController.scrollToStories() - if let navigationController = (chatListController.navigationController as? NavigationController) { - navigationController.popToRoot(animated: true) - } - } - }) - }*/ } else { var visiblePeerId: PeerId? if let controller = self.rootController.topViewController as? ChatControllerImpl, controller.chatLocation.peerId == peerId, controller.chatLocation.threadId == threadId { diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index 1eb00baaf2..fe41129173 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -1567,6 +1567,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { streamingState = .none if case .progress = state { + } else if case .check = state { } else { let adjustedProgress: CGFloat = 0.027 state = .progress(value: CGFloat(adjustedProgress), cancelEnabled: true, appearance: .init(inset: 1.0, lineWidth: 2.0)) diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift index d933d4dcf6..b9df92d31b 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsHistoryTransition.swift @@ -67,7 +67,7 @@ private func filterOriginalMessageFlags(_ message: Message) -> Message { private func filterMessageChannelPeer(_ peer: Peer) -> Peer { if let peer = peer as? TelegramChannel { - return TelegramChannel(id: peer.id, accessHash: peer.accessHash, title: peer.title, username: peer.username, photo: peer.photo, creationDate: peer.creationDate, version: peer.version, participationStatus: peer.participationStatus, info: .group(TelegramChannelGroupInfo(flags: [])), flags: peer.flags, restrictionInfo: peer.restrictionInfo, adminRights: peer.adminRights, bannedRights: peer.bannedRights, defaultBannedRights: peer.defaultBannedRights, usernames: peer.usernames) + return TelegramChannel(id: peer.id, accessHash: peer.accessHash, title: peer.title, username: peer.username, photo: peer.photo, creationDate: peer.creationDate, version: peer.version, participationStatus: peer.participationStatus, info: .group(TelegramChannelGroupInfo(flags: [])), flags: peer.flags, restrictionInfo: peer.restrictionInfo, adminRights: peer.adminRights, bannedRights: peer.bannedRights, defaultBannedRights: peer.defaultBannedRights, usernames: peer.usernames, storiesHidden: peer.storiesHidden) } return peer } diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index fb692de00a..47ad60579d 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -406,11 +406,11 @@ public final class TelegramRootController: NavigationController, TelegramRootCon } let target: Stories.PendingTarget - #if DEBUG - target = .peer(PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(2200678799))) - #else - target = .myStories - #endif + if let sendAsPeerId = options.sendAsPeerId { + target = .peer(sendAsPeerId) + } else { + target = .myStories + } storyTarget = target if let _ = self.chatListController as? ChatListControllerImpl { @@ -419,7 +419,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon if let imageData = compressImageToJPEG(image, quality: 0.7) { let entities = generateChatInputTextEntities(caption) Logger.shared.log("MediaEditor", "Calling uploadStory for image, randomId \(randomId)") - let _ = (context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: options.pin, privacy: options.privacy, isForwardingDisabled: options.isForwardingDisabled, period: options.timeout, randomId: randomId) + let _ = (context.engine.messages.uploadStory(target: target, media: .image(dimensions: dimensions, data: imageData, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: options.pin, privacy: options.privacy, isForwardingDisabled: options.isForwardingDisabled, period: options.timeout, randomId: randomId) |> deliverOnMainQueue).start(next: { stableId in moveStorySource(engine: context.engine, peerId: context.account.peerId, from: randomId, to: Int64(stableId)) }) @@ -453,7 +453,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon } Logger.shared.log("MediaEditor", "Calling uploadStory for video, randomId \(randomId)") let entities = generateChatInputTextEntities(caption) - let _ = (context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: options.pin, privacy: options.privacy, isForwardingDisabled: options.isForwardingDisabled, period: options.timeout, randomId: randomId) + let _ = (context.engine.messages.uploadStory(target: target, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: stickers), mediaAreas: mediaAreas, text: caption.string, entities: entities, pin: options.pin, privacy: options.privacy, isForwardingDisabled: options.isForwardingDisabled, period: options.timeout, randomId: randomId) |> deliverOnMainQueue).start(next: { stableId in moveStorySource(engine: context.engine, peerId: context.account.peerId, from: randomId, to: Int64(stableId)) })