mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-01 07:57:01 +00:00
Merge commit '5de8f50b79858bc50eef9e267ff6a6c1be3470db'
This commit is contained in:
commit
42ff2acdbc
@ -561,12 +561,12 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
|||||||
let textFont = Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0))
|
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 boldTextFont = Font.bold(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0))
|
||||||
let textColor = self.presentationData.theme.contextMenu.primaryColor
|
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 iconSize = self.iconNode.image?.size ?? CGSize(width: 16.0, height: 16.0)
|
||||||
|
|
||||||
let text = self.text.replacingOccurrences(of: "#", with: "# ")
|
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
|
return nil
|
||||||
})))
|
})))
|
||||||
if let file = self.file {
|
if let file = self.file {
|
||||||
|
@ -145,8 +145,11 @@ public final class DrawingStickerEntityView: DrawingEntityView {
|
|||||||
self.animationNode = animationNode
|
self.animationNode = animationNode
|
||||||
animationNode.started = { [weak self, weak animationNode] in
|
animationNode.started = { [weak self, weak animationNode] in
|
||||||
self?.imageNode.isHidden = true
|
self?.imageNode.isHidden = true
|
||||||
|
|
||||||
if let animationNode = animationNode {
|
if let animationNode = animationNode {
|
||||||
|
if animationNode.currentFrameCount == 1 {
|
||||||
|
self?.stickerEntity.isExplicitlyStatic = true
|
||||||
|
}
|
||||||
let _ = (animationNode.status
|
let _ = (animationNode.status
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||||
|
@ -62,6 +62,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
case scale
|
case scale
|
||||||
case rotation
|
case rotation
|
||||||
case mirrored
|
case mirrored
|
||||||
|
case isExplicitlyStatic
|
||||||
}
|
}
|
||||||
|
|
||||||
public let uuid: UUID
|
public let uuid: UUID
|
||||||
@ -72,6 +73,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
public var scale: CGFloat
|
public var scale: CGFloat
|
||||||
public var rotation: CGFloat
|
public var rotation: CGFloat
|
||||||
public var mirrored: Bool
|
public var mirrored: Bool
|
||||||
|
|
||||||
|
public var isExplicitlyStatic: Bool
|
||||||
|
|
||||||
public var color: DrawingColor = DrawingColor.clear
|
public var color: DrawingColor = DrawingColor.clear
|
||||||
public var lineWidth: CGFloat = 0.0
|
public var lineWidth: CGFloat = 0.0
|
||||||
@ -88,7 +91,11 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
public var isAnimated: Bool {
|
public var isAnimated: Bool {
|
||||||
switch self.content {
|
switch self.content {
|
||||||
case let .file(file):
|
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:
|
case .image:
|
||||||
return false
|
return false
|
||||||
case .video:
|
case .video:
|
||||||
@ -123,6 +130,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
self.scale = 1.0
|
self.scale = 1.0
|
||||||
self.rotation = 0.0
|
self.rotation = 0.0
|
||||||
self.mirrored = false
|
self.mirrored = false
|
||||||
|
|
||||||
|
self.isExplicitlyStatic = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
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.scale = try container.decode(CGFloat.self, forKey: .scale)
|
||||||
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
|
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
|
||||||
self.mirrored = try container.decode(Bool.self, forKey: .mirrored)
|
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 {
|
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.scale, forKey: .scale)
|
||||||
try container.encode(self.rotation, forKey: .rotation)
|
try container.encode(self.rotation, forKey: .rotation)
|
||||||
try container.encode(self.mirrored, forKey: .mirrored)
|
try container.encode(self.mirrored, forKey: .mirrored)
|
||||||
|
try container.encode(self.isExplicitlyStatic, forKey: .isExplicitlyStatic)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func duplicate() -> DrawingEntity {
|
public func duplicate() -> DrawingEntity {
|
||||||
@ -194,6 +205,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
newEntity.scale = self.scale
|
newEntity.scale = self.scale
|
||||||
newEntity.rotation = self.rotation
|
newEntity.rotation = self.rotation
|
||||||
newEntity.mirrored = self.mirrored
|
newEntity.mirrored = self.mirrored
|
||||||
|
newEntity.isExplicitlyStatic = self.isExplicitlyStatic
|
||||||
return newEntity
|
return newEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,6 +234,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
if self.mirrored != other.mirrored {
|
if self.mirrored != other.mirrored {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if self.isExplicitlyStatic != other.isExplicitlyStatic {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ func composerEntitiesForDrawingEntity(account: Account, entity: DrawingEntity, c
|
|||||||
case .dualVideoReference:
|
case .dualVideoReference:
|
||||||
return []
|
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]) {
|
} else if let renderImage = entity.renderImage, let image = CIImage(image: renderImage, options: [.colorSpace: colorSpace]) {
|
||||||
if let entity = entity as? DrawingBubbleEntity {
|
if let entity = entity as? DrawingBubbleEntity {
|
||||||
return [MediaEditorComposerStaticEntity(image: image, position: entity.position, scale: 1.0, rotation: entity.rotation, baseSize: entity.size, mirrored: false)]
|
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 baseSize: CGSize?
|
||||||
let mirrored: Bool
|
let mirrored: Bool
|
||||||
let colorSpace: CGColorSpace
|
let colorSpace: CGColorSpace
|
||||||
|
let isStatic: Bool
|
||||||
|
|
||||||
var isAnimated: Bool
|
var isAnimated: Bool
|
||||||
var source: AnimatedStickerNodeSource?
|
var source: AnimatedStickerNodeSource?
|
||||||
@ -113,7 +114,7 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
|
|||||||
var imagePixelBuffer: CVPixelBuffer?
|
var imagePixelBuffer: CVPixelBuffer?
|
||||||
let imagePromise = Promise<UIImage>()
|
let imagePromise = Promise<UIImage>()
|
||||||
|
|
||||||
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.content = content
|
||||||
self.position = position
|
self.position = position
|
||||||
self.scale = scale
|
self.scale = scale
|
||||||
@ -121,6 +122,7 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
|
|||||||
self.baseSize = baseSize
|
self.baseSize = baseSize
|
||||||
self.mirrored = mirrored
|
self.mirrored = mirrored
|
||||||
self.colorSpace = colorSpace
|
self.colorSpace = colorSpace
|
||||||
|
self.isStatic = isStatic
|
||||||
|
|
||||||
switch content {
|
switch content {
|
||||||
case let .file(file):
|
case let .file(file):
|
||||||
@ -132,7 +134,13 @@ private class MediaEditorComposerStickerEntity: MediaEditorComposerEntity {
|
|||||||
let pathPrefix = account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
let pathPrefix = account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
|
||||||
if let source = self.source {
|
if let source = self.source {
|
||||||
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
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)
|
self.disposables.add((source.directDataPath(attemptSynchronously: true)
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] path in
|
|> deliverOn(self.queue)).start(next: { [weak self] path in
|
||||||
if let strongSelf = self, let path {
|
if let strongSelf = self, let path {
|
||||||
|
@ -163,20 +163,35 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, inp
|
|||||||
}
|
}
|
||||||
|
|
||||||
let normalizedQuery = query.lowercased()
|
let normalizedQuery = query.lowercased()
|
||||||
let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.contacts.searchLocalPeers(query: normalizedQuery)
|
if normalizedQuery.isEmpty {
|
||||||
|> map { peersAndPresences -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
let peers: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.peers.recentPeers()
|
||||||
let peers = peersAndPresences.filter { peer in
|
|> map { recentPeers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
||||||
if let peer = peer.peer, case .user = peer, peer.addressName != nil {
|
if case let .peers(peers) = recentPeers {
|
||||||
return true
|
let peers = peers.filter { peer in
|
||||||
|
return peer.addressName != nil
|
||||||
|
}.compactMap { EnginePeer($0) }
|
||||||
|
return { _ in return .mentions(peers) }
|
||||||
} else {
|
} 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):
|
case let .emojiSearch(query, languageCode, range):
|
||||||
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
let hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||||
|> map { peer -> Bool in
|
|> map { peer -> Bool in
|
||||||
|
@ -908,7 +908,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if component.pinchState != nil {
|
if component.pinchState != nil {
|
||||||
return true
|
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
|
return true
|
||||||
}
|
}
|
||||||
if let reactionContextNode = self.reactionContextNode, reactionContextNode.isReactionSearchActive {
|
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<Never, NoError> { [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?) {
|
private func performMoreAction(sourceView: UIView, gesture: ContextGesture?) {
|
||||||
guard let component = self.component else {
|
guard let component = self.component else {
|
||||||
return
|
return
|
||||||
@ -3432,15 +3376,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||||
var items: [ContextMenuItem] = []
|
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 additionalCount = component.slice.item.storyItem.privacy?.additionallyIncludePeers.count ?? 0
|
||||||
let privacyText: String
|
let privacyText: String
|
||||||
switch component.slice.item.storyItem.privacy?.base {
|
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 tip: ContextController.Tip?
|
||||||
var tipSignal: Signal<ContextController.Tip?, NoError>?
|
var tipSignal: Signal<ContextController.Tip?, NoError>?
|
||||||
|
|
||||||
if hasLinkedStickers {
|
if hasLinkedStickers {
|
||||||
let context = component.context
|
let context = component.context
|
||||||
tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil)
|
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)))
|
packsPromise.set(context.engine.stickers.stickerPacksAttachedToMedia(media: .standalone(media: media)))
|
||||||
|
|
||||||
let action: () -> Void = { [weak self] in
|
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()
|
tipSignal = packsPromise.get()
|
||||||
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
|
|> mapToSignal { packReferences -> Signal<ContextController.Tip?, NoError> in
|
||||||
@ -3606,8 +3551,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<ContextController.Tip?, NoError> in
|
|> mapToSignal { result -> Signal<ContextController.Tip?, NoError> in
|
||||||
if case let .result(info, items, _) = result {
|
if case let .result(info, items, _) = result {
|
||||||
|
let isEmoji = info.flags.contains(.isEmoji)
|
||||||
let tip: ContextController.Tip = .animatedEmoji(
|
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(
|
arguments: TextNodeWithEntities.Arguments(
|
||||||
context: context,
|
context: context,
|
||||||
cache: context.animationCache,
|
cache: context.animationCache,
|
||||||
@ -3626,40 +3572,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
return .complete()
|
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<LoadedStickerPack, NoError>
|
|
||||||
// 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<ContextController.Tip?, NoError> 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)
|
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)
|
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||||
var items: [ContextMenuItem] = []
|
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())
|
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
|
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<ContextController.Tip?, NoError>?
|
||||||
|
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<ContextController.Tip?, NoError> 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<ContextController.Tip?, NoError> 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
|
contextController.dismissed = { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
@ -53,6 +53,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
weak var shareController: ShareController?
|
weak var shareController: ShareController?
|
||||||
weak var tooltipScreen: ViewController?
|
weak var tooltipScreen: ViewController?
|
||||||
weak var actionSheet: ViewController?
|
weak var actionSheet: ViewController?
|
||||||
|
var isViewingAttachedStickers = false
|
||||||
|
|
||||||
var currentInputMode: InputMode = .text
|
var currentInputMode: InputMode = .text
|
||||||
private var needsInputActivation = false
|
private var needsInputActivation = false
|
||||||
@ -2594,7 +2595,82 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
actionSheet?.dismissAnimated()
|
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))
|
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<Never, NoError> { [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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user