diff --git a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift index 22ff20c36f..e00e1a33e9 100644 --- a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift @@ -561,12 +561,12 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode { let textFont = Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)) let boldTextFont = Font.bold(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)) let textColor = self.presentationData.theme.contextMenu.primaryColor - let accentColor = self.presentationData.theme.contextMenu.badgeFillColor + let linkColor = self.presentationData.theme.overallDarkAppearance ? UIColor(rgb: 0x64d2ff) : self.presentationData.theme.contextMenu.badgeFillColor let iconSize = self.iconNode.image?.size ?? CGSize(width: 16.0, height: 16.0) - + let text = self.text.replacingOccurrences(of: "#", with: "# ") - let attributedText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: boldTextFont, textColor: accentColor), linkAttribute: { _ in + let attributedText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: boldTextFont, textColor: linkColor), linkAttribute: { _ in return nil }))) if let file = self.file { diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift index 0895c171ac..befadba83d 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntity.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntity.swift @@ -145,8 +145,11 @@ public final class DrawingStickerEntityView: DrawingEntityView { self.animationNode = animationNode animationNode.started = { [weak self, weak animationNode] in self?.imageNode.isHidden = true - + if let animationNode = animationNode { + if animationNode.currentFrameCount == 1 { + self?.stickerEntity.isExplicitlyStatic = true + } let _ = (animationNode.status |> take(1) |> deliverOnMainQueue).start(next: { [weak self] status in diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift index 75c72ed53a..cd36bcd610 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift @@ -62,6 +62,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { case scale case rotation case mirrored + case isExplicitlyStatic } public let uuid: UUID @@ -72,6 +73,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { public var scale: CGFloat public var rotation: CGFloat public var mirrored: Bool + + public var isExplicitlyStatic: Bool public var color: DrawingColor = DrawingColor.clear public var lineWidth: CGFloat = 0.0 @@ -88,7 +91,11 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { public var isAnimated: Bool { switch self.content { case let .file(file): - return file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm" + if self.isExplicitlyStatic { + return false + } else { + return file.isAnimatedSticker || file.isVideoSticker || file.mimeType == "video/webm" + } case .image: return false case .video: @@ -123,6 +130,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { self.scale = 1.0 self.rotation = 0.0 self.mirrored = false + + self.isExplicitlyStatic = false } public init(from decoder: Decoder) throws { @@ -150,6 +159,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { self.scale = try container.decode(CGFloat.self, forKey: .scale) self.rotation = try container.decode(CGFloat.self, forKey: .rotation) self.mirrored = try container.decode(Bool.self, forKey: .mirrored) + self.isExplicitlyStatic = try container.decodeIfPresent(Bool.self, forKey: .isExplicitlyStatic) ?? false } public func encode(to encoder: Encoder) throws { @@ -185,6 +195,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { try container.encode(self.scale, forKey: .scale) try container.encode(self.rotation, forKey: .rotation) try container.encode(self.mirrored, forKey: .mirrored) + try container.encode(self.isExplicitlyStatic, forKey: .isExplicitlyStatic) } public func duplicate() -> DrawingEntity { @@ -194,6 +205,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { newEntity.scale = self.scale newEntity.rotation = self.rotation newEntity.mirrored = self.mirrored + newEntity.isExplicitlyStatic = self.isExplicitlyStatic return newEntity } @@ -222,6 +234,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { if self.mirrored != other.mirrored { return false } + if self.isExplicitlyStatic != other.isExplicitlyStatic { + return false + } return true } } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift index a928dfca2f..3d145358fd 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorComposerEntity.swift @@ -25,7 +25,7 @@ func composerEntitiesForDrawingEntity(account: Account, entity: DrawingEntity, c case .dualVideoReference: return [] } - return [MediaEditorComposerStickerEntity(account: account, content: content, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: entity.mirrored, colorSpace: colorSpace)] + return [MediaEditorComposerStickerEntity(account: account, content: content, position: entity.position, scale: entity.scale, rotation: entity.rotation, baseSize: entity.baseSize, mirrored: entity.mirrored, colorSpace: colorSpace, isStatic: entity.isExplicitlyStatic)] } else if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) { if let entity = entity as? DrawingBubbleEntity { return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, mirrored: false)] @@ -90,6 +90,7 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { let baseSize: CGSize? let mirrored: Bool let colorSpace: CGColorSpace + let isStatic: Bool var isAnimated: Bool var source: AnimatedStickerNodeSource? @@ -113,7 +114,7 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { var imagePixelBuffer: CVPixelBuffer? let imagePromise = Promise() - init(account: Account, content: Content, position: CGPoint, scale: CGFloat, rotation: CGFloat, baseSize: CGSize, mirrored: Bool, colorSpace: CGColorSpace) { + init(account: Account, content: Content, position: CGPoint, scale: CGFloat, rotation: CGFloat, baseSize: CGSize, mirrored: Bool, colorSpace: CGColorSpace, isStatic: Bool) { self.content = content self.position = position self.scale = scale @@ -121,6 +122,7 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { self.baseSize = baseSize self.mirrored = mirrored self.colorSpace = colorSpace + self.isStatic = isStatic switch content { case let .file(file): @@ -132,7 +134,13 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity { let pathPrefix = account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) if let source = self.source { let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512) - let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384, height: 384)) + let fitToSize: CGSize + if self.isStatic { + fitToSize = CGSize(width: 768, height: 768) + } else { + fitToSize = CGSize(width: 384, height: 384) + } + let fittedDimensions = dimensions.cgSize.aspectFitted(fitToSize) self.disposables.add((source.directDataPath(attemptSynchronously: true) |> deliverOn(self.queue)).start(next: { [weak self] path in if let strongSelf = self, let path { diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift index 3ebf667d10..a0bf9324a3 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift @@ -163,20 +163,35 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp } let normalizedQuery = query.lowercased() - let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.contacts.searchLocalPeers(query: normalizedQuery) - |> map { peersAndPresences -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in - let peers = peersAndPresences.filter { peer in - if let peer = peer.peer, case .user = peer, peer.addressName != nil { - return true + if normalizedQuery.isEmpty { + let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.peers.recentPeers() + |> map { recentPeers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + if case let .peers(peers) = recentPeers { + let peers = peers.filter { peer in + return peer.addressName != nil + }.compactMap { EnginePeer($0) } + return { _ in return .mentions(peers) } } else { - return false + return { _ in return .mentions([]) } } - }.compactMap { $0.peer } - return { _ in return .mentions(peers) } + } + |> castError(ChatContextQueryError.self) + return signal |> then(peers) + } else { + let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.contacts.searchLocalPeers(query: normalizedQuery) + |> map { peersAndPresences -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in + let peers = peersAndPresences.filter { peer in + if let peer = peer.peer, case .user = peer, peer.addressName != nil { + return true + } else { + return false + } + }.compactMap { $0.peer } + return { _ in return .mentions(peers) } + } + |> castError(ChatContextQueryError.self) + return signal |> then(peers) } - |> castError(ChatContextQueryError.self) - - return signal |> then(peers) case let .emojiSearch(query, languageCode, range): let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) |> map { peer -> Bool in diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 8b9f068824..e98c02bd60 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -908,7 +908,7 @@ public final class StoryItemSetContainerComponent: Component { if component.pinchState != nil { return true } - if self.inputPanelExternalState.isEditing || component.isProgressPaused || self.sendMessageContext.actionSheet != nil || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil || self.displayViewList { + if self.inputPanelExternalState.isEditing || component.isProgressPaused || self.sendMessageContext.actionSheet != nil || self.sendMessageContext.isViewingAttachedStickers || self.contextController != nil || self.sendMessageContext.audioRecorderValue != nil || self.sendMessageContext.videoRecorderValue != nil || self.displayViewList { return true } if let reactionContextNode = self.reactionContextNode, reactionContextNode.isReactionSearchActive { @@ -3349,62 +3349,6 @@ public final class StoryItemSetContainerComponent: Component { } } - private func openAttachedStickers(packs: Signal<[StickerPackReference], NoError>) { - guard let component = self.component else { - return - } - - guard let parentController = component.controller() as? StoryContainerScreen else { - return - } - let context = component.context - let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme) - let progressSignal = Signal { [weak parentController] subscriber in - let progressController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) - parentController?.present(progressController, in: .window(.root), with: nil) - return ActionDisposable { [weak progressController] in - Queue.mainQueue().async() { - progressController?.dismiss() - } - } - } - |> runOn(Queue.mainQueue()) - |> delay(0.15, queue: Queue.mainQueue()) - let progressDisposable = progressSignal.start() - - let signal = packs - |> afterDisposed { - Queue.mainQueue().async { - progressDisposable.dispose() - } - } - let _ = (signal - |> deliverOnMainQueue).start(next: { [weak parentController] packs in - guard !packs.isEmpty else { - return - } - let controller = StickerPackScreen(context: context, updatedPresentationData: (presentationData, .single(presentationData)), mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { actions in - if let (info, items, action) = actions.first { - let animateInAsReplacement = false - switch action { - case .add: - parentController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in - return true - }), in: .window(.root)) - case let .remove(positionInList): - parentController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in - if case .undo = action { - let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start() - } - return true - }), in: .window(.root)) - } - } - }) - parentController?.present(controller, in: .window(.root), with: nil) - }) - } - private func performMoreAction(sourceView: UIView, gesture: ContextGesture?) { guard let component = self.component else { return @@ -3432,15 +3376,7 @@ public final class StoryItemSetContainerComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) var items: [ContextMenuItem] = [] - - var hasLinkedStickers = false - let media = component.slice.item.storyItem.media._asMedia() - if let image = media as? TelegramMediaImage { - hasLinkedStickers = image.flags.contains(.hasStickers) - } else if let file = media as? TelegramMediaFile { - hasLinkedStickers = file.hasLinkedStickers - } - + let additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0 let privacyText: String switch component.slice.item.storyItem.privacy?.base { @@ -3578,9 +3514,16 @@ public final class StoryItemSetContainerComponent: Component { }))) } + var hasLinkedStickers = false + let media = component.slice.item.storyItem.media._asMedia() + if let image = media as? TelegramMediaImage { + hasLinkedStickers = image.flags.contains(.hasStickers) + } else if let file = media as? TelegramMediaFile { + hasLinkedStickers = file.hasLinkedStickers + } + var tip: ContextController.Tip? var tipSignal: Signal? - if hasLinkedStickers { let context = component.context tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil) @@ -3589,7 +3532,9 @@ public final class StoryItemSetContainerComponent: Component { packsPromise.set(context.engine.stickers.stickerPacksAttachedToMedia(media: .standalone(media: media))) let action: () -> Void = { [weak self] in - self?.openAttachedStickers(packs: packsPromise.get() |> take(1)) + if let self { + self.sendMessageContext.openAttachedStickers(view: self, packs: packsPromise.get() |> take(1)) + } } tipSignal = packsPromise.get() |> mapToSignal { packReferences -> Signal in @@ -3606,8 +3551,9 @@ public final class StoryItemSetContainerComponent: Component { } |> mapToSignal { result -> Signal in if case let .result(info, items, _) = result { + let isEmoji = info.flags.contains(.isEmoji) let tip: ContextController.Tip = .animatedEmoji( - text: "This story contains\n#[\(info.title)]() stickers.", + text: isEmoji ? "This story contains\n#[\(info.title)]() emoji." : "This story contains\n#[\(info.title)]() stickers.", arguments: TextNodeWithEntities.Arguments( context: context, cache: context.animationCache, @@ -3626,40 +3572,6 @@ public final class StoryItemSetContainerComponent: Component { return .complete() } } - -// if packReferences.count > 1 { -// items.tip = .animatedEmoji(text: presentationData.strings.ChatContextMenu_EmojiSet(Int32(packReferences.count)), arguments: nil, file: nil, action: action) -// } else if let reference = packReferences.first { -// var tipSignal: Signal -// tipSignal = context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false) -// -// items.tipSignal = tipSignal -// |> filter { result in -// if case .result = result { -// return true -// } else { -// return false -// } -// } -// |> mapToSignal { result -> Signal in -// if case let .result(info, items, _) = result { -// let tip: ContextController.Tip = .animatedEmoji( -// text: presentationData.strings.ChatContextMenu_ReactionEmojiSetSingle(info.title).string, -// arguments: TextNodeWithEntities.Arguments( -// context: context, -// cache: presentationContext.animationCache, -// renderer: presentationContext.animationRenderer, -// placeholderColor: .clear, -// attemptSynchronous: true -// ), -// file: items.first?.file, -// action: action) -// return .single(tip) -// } else { -// return .complete() -// } -// } -// } } let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal) @@ -3699,16 +3611,7 @@ public final class StoryItemSetContainerComponent: Component { let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme) var items: [ContextMenuItem] = [] - - var hasLinkedStickers = false - let media = component.slice.item.storyItem.media._asMedia() - if let image = media as? TelegramMediaImage { - hasLinkedStickers = image.flags.contains(.hasStickers) - } else if let file = media as? TelegramMediaFile { - hasLinkedStickers = file.hasLinkedStickers - } - let _ = hasLinkedStickers - + let isMuted = resolvedAreStoriesMuted(globalSettings: globalSettings._asGlobalNotificationSettings(), peer: component.slice.peer._asPeer(), peerSettings: settings._asNotificationSettings()) items.append(.action(ContextMenuActionItem(text: isMuted ? "Notify" : "Don't Notify", icon: { theme in @@ -3833,8 +3736,70 @@ public final class StoryItemSetContainerComponent: Component { } ) }))) + + var hasLinkedStickers = false + let media = component.slice.item.storyItem.media._asMedia() + if let image = media as? TelegramMediaImage { + hasLinkedStickers = image.flags.contains(.hasStickers) + } else if let file = media as? TelegramMediaFile { + hasLinkedStickers = file.hasLinkedStickers + } + + var tip: ContextController.Tip? + var tipSignal: Signal? + if hasLinkedStickers { + let context = component.context + tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil) + + let packsPromise = Promise<[StickerPackReference]>() + packsPromise.set(context.engine.stickers.stickerPacksAttachedToMedia(media: .standalone(media: media))) + + let action: () -> Void = { [weak self] in + if let self { + self.sendMessageContext.openAttachedStickers(view: self, packs: packsPromise.get() |> take(1)) + } + } + tipSignal = packsPromise.get() + |> mapToSignal { packReferences -> Signal in + if packReferences.count > 1 { + return .single(.animatedEmoji(text: "This story contains stickers from [\(packReferences.count) packs]().", arguments: nil, file: nil, action: action)) + } else if let reference = packReferences.first { + return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false) + |> filter { result in + if case .result = result { + return true + } else { + return false + } + } + |> mapToSignal { result -> Signal in + if case let .result(info, items, _) = result { + let isEmoji = info.flags.contains(.isEmoji) + let tip: ContextController.Tip = .animatedEmoji( + text: isEmoji ? "This story contains\n#[\(info.title)]() emoji." : "This story contains\n#[\(info.title)]() stickers.", + arguments: TextNodeWithEntities.Arguments( + context: context, + cache: context.animationCache, + renderer: context.animationRenderer, + placeholderColor: .clear, + attemptSynchronous: true + ), + file: items.first?.file, + action: action) + return .single(tip) + } else { + return .complete() + } + } + } else { + return .complete() + } + } + } + + let contextItems = ContextController.Items(content: .list(items), tip: tip, tipSignal: tipSignal) - let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) + let contextController = ContextController(account: component.context.account, presentationData: presentationData, source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView)), items: .single(contextItems), gesture: gesture) contextController.dismissed = { [weak self] in guard let self else { return diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 5939dd0847..ef5be115d0 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -53,6 +53,7 @@ final class StoryItemSetContainerSendMessage { weak var shareController: ShareController? weak var tooltipScreen: ViewController? weak var actionSheet: ViewController? + var isViewingAttachedStickers = false var currentInputMode: InputMode = .text private var needsInputActivation = false @@ -2594,7 +2595,82 @@ final class StoryItemSetContainerSendMessage { actionSheet?.dismissAnimated() }) ])]) + actionSheet.dismissed = { [weak self, weak view] _ in + guard let self, let view else { + return + } + self.actionSheet = nil + view.updateIsProgressPaused() + } component.controller()?.present(actionSheet, in: .window(.root)) + + self.actionSheet = actionSheet + view.updateIsProgressPaused() + } + + func openAttachedStickers(view: StoryItemSetContainerComponent.View, packs: Signal<[StickerPackReference], NoError>) { + guard let component = view.component else { + return + } + + guard let parentController = component.controller() as? StoryContainerScreen else { + return + } + let context = component.context + let presentationData = context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: defaultDarkPresentationTheme) + let progressSignal = Signal { [weak parentController] subscriber in + let progressController = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil)) + parentController?.present(progressController, in: .window(.root), with: nil) + return ActionDisposable { [weak progressController] in + Queue.mainQueue().async() { + progressController?.dismiss() + } + } + } + |> runOn(Queue.mainQueue()) + |> delay(0.15, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.start() + + let signal = packs + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + let _ = (signal + |> deliverOnMainQueue).start(next: { [weak parentController] packs in + guard !packs.isEmpty else { + return + } + let controller = StickerPackScreen(context: context, updatedPresentationData: (presentationData, .single(presentationData)), mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { actions in + if let (info, items, action) = actions.first { + let animateInAsReplacement = false + switch action { + case .add: + parentController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in + return true + }), in: .window(.root)) + case let .remove(positionInList): + parentController?.present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in + if case .undo = action { + let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start() + } + return true + }), in: .window(.root)) + } + } + }, dismissed: { [weak self, weak view] in + guard let self, let view else { + return + } + self.isViewingAttachedStickers = false + view.updateIsProgressPaused() + }) + parentController?.present(controller, in: .window(.root), with: nil) + }) + + self.isViewingAttachedStickers = true + view.updateIsProgressPaused() } }