Merge commit '394c0d7a2601cebf5ace0c8a7ae1d93054c11818'

This commit is contained in:
Ali 2023-01-25 16:28:06 +01:00
commit 4609546c49
38 changed files with 866 additions and 342 deletions

View File

@ -25,6 +25,9 @@ swift_library(
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
"//submodules/AnimatedStickerNode",
"//submodules/TelegramAnimatedStickerNode",
"//submodules/StickerResources:StickerResources",
],
visibility = [
"//visibility:public",

View File

@ -13,6 +13,9 @@ import GradientBackground
import AnimationCache
import MultiAnimationRenderer
import EntityKeyboard
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import StickerResources
private let maxVideoLoopCount = 3
@ -26,19 +29,23 @@ public final class AvatarVideoNode: ASDisplayNode {
private var fileDisposable: Disposable?
private var animationFile: TelegramMediaFile?
private var itemLayer: EmojiPagerContentComponent.View.ItemLayer?
private var useAnimationNode = false
private var animationNode: AnimatedStickerNode?
private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private let playbackStartDisposable = MetaDisposable()
private var videoLoopCount = 0
private var size = CGSize(width: 60.0, height: 60.0)
private var validLayout: (CGSize, CGFloat)?
private var internalSize = CGSize(width: 60.0, height: 60.0)
public init(context: AccountContext) {
self.context = context
self.backgroundNode = ASImageNode()
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.isHidden = true
super.init()
@ -60,138 +67,123 @@ public final class AvatarVideoNode: ASDisplayNode {
}
}
private var didAppear = false
private func setupAnimation() {
guard let animationFile = self.animationFile else {
return
}
let itemNativeFitSize = CGSize(width: 128.0, height: 128.0)
let size = CGSize(width: self.size.width * 0.67, height: self.size.height * 0.67)
let itemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.size.width - size.width) / 2.0), y: floorToScreenPixels((self.size.height - size.height) / 2.0)), size: size)
let animationData = EntityKeyboardAnimationData(file: animationFile)
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
item: EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: animationFile,
subgroupId: nil,
icon: .none,
tintMode: animationData.isTemplate ? .primary : .none
),
context: context,
attemptSynchronousLoad: false,
content: .animation(animationData),
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: .clear,
blurredBadgeColor: .clear,
accentIconColor: .white,
pointSize: itemNativeFitSize,
onUpdateDisplayPlaceholder: { displayPlaceholder, _ in
if !displayPlaceholder {
print()
if self.useAnimationNode {
let animationNode = DefaultAnimatedStickerNodeImpl()
animationNode.autoplay = false
self.animationNode = animationNode
animationNode.started = { [weak self] in
if let self {
if !self.didAppear {
self.didAppear = true
Queue.mainQueue().after(0.15) {
self.backgroundNode.isHidden = false
}
}
}
// guard let strongSelf = self else {
// return
// }
// if displayPlaceholder {
// if let itemLayer = strongSelf.visibleItemLayers[itemId] {
// let placeholderView: EmojiPagerContentComponent.View.ItemPlaceholderView
// if let current = strongSelf.visibleItemPlaceholderViews[itemId] {
// placeholderView = current
// } else {
// placeholderView = EmojiPagerContentComponent.View.ItemPlaceholderView(
// context: context,
// dimensions: item.file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0),
// immediateThumbnailData: item.file.immediateThumbnailData,
// shimmerView: nil,//strongSelf.shimmerHostView,
// color: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08),
// size: itemNativeFitSize
// )
// strongSelf.visibleItemPlaceholderViews[itemId] = placeholderView
// strongSelf.view.insertSubview(placeholderView, at: 0)
// }
// placeholderView.frame = itemLayer.frame
// placeholderView.update(size: placeholderView.bounds.size)
//
// strongSelf.updateShimmerIfNeeded()
// }
// } else {
// if let placeholderView = strongSelf.visibleItemPlaceholderViews[itemId] {
// strongSelf.visibleItemPlaceholderViews.removeValue(forKey: itemId)
//
// if duration > 0.0 {
// placeholderView.layer.opacity = 0.0
// placeholderView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, completion: { [weak self, weak placeholderView] _ in
// guard let strongSelf = self else {
// return
// }
// placeholderView?.removeFromSuperview()
// strongSelf.updateShimmerIfNeeded()
// })
// } else {
// placeholderView.removeFromSuperview()
// strongSelf.updateShimmerIfNeeded()
// }
// }
// }
}
)
itemLayer.onContentsUpdate = { [weak self] in
self?.backgroundNode.isHidden = false
self.backgroundNode.addSubnode(animationNode)
} else {
let itemNativeFitSize = self.internalSize.width > 100.0 ? CGSize(width: 192.0, height: 192.0) : CGSize(width: 64.0, height: 64.0)
let animationData = EntityKeyboardAnimationData(file: animationFile)
let itemLayer = EmojiPagerContentComponent.View.ItemLayer(
item: EmojiPagerContentComponent.Item(
animationData: animationData,
content: .animation(animationData),
itemFile: animationFile,
subgroupId: nil,
icon: .none,
tintMode: animationData.isTemplate ? .primary : .none
),
context: context,
attemptSynchronousLoad: false,
content: .animation(animationData),
cache: context.animationCache,
renderer: context.animationRenderer,
placeholderColor: .clear,
blurredBadgeColor: .clear,
accentIconColor: .white,
pointSize: itemNativeFitSize,
onUpdateDisplayPlaceholder: { _, _ in
}
)
itemLayer.onContentsUpdate = { [weak self] in
if let self {
if !self.didAppear {
self.didAppear = true
Queue.mainQueue().after(0.15) {
self.backgroundNode.isHidden = false
}
}
}
}
itemLayer.layerTintColor = UIColor.white.cgColor
itemLayer.isVisibleForAnimations = self.visibility
self.itemLayer = itemLayer
self.backgroundNode.layer.addSublayer(itemLayer)
}
if let (size, cornerRadius) = self.validLayout {
self.updateLayout(size: size, cornerRadius: cornerRadius, transition: .immediate)
}
}
public func update(markup: TelegramMediaImage.EmojiMarkup, size: CGSize, useAnimationNode: Bool = true) {
guard markup != self.emojiMarkup else {
return
}
self.emojiMarkup = markup
self.internalSize = size
//self.useAnimationNode = useAnimationNode
let colors = markup.backgroundColors.map { UInt32(bitPattern: $0) }
if colors.count == 1 {
backgroundNode.backgroundColor = UIColor(rgb: colors.first!)
self.backgroundNode.image = nil
} else if colors.count == 2 {
self.backgroundNode.image = generateGradientImage(size: size, colors: colors.map { UIColor(rgb: $0) }, locations: [0.0, 1.0])!
} else {
self.backgroundNode.image = GradientBackgroundNode.generatePreview(size: size, colors: colors.map { UIColor(rgb: $0) })
}
self.backgroundNode.isHidden = true
switch markup.content {
case let .emoji(fileId):
self.fileDisposable = (self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|> deliverOnMainQueue).start(next: { [weak self] files in
if let strongSelf = self, let file = files.values.first {
strongSelf.animationFile = file
strongSelf.setupAnimation()
}
})
case let .sticker(packReference, fileId):
self.fileDisposable = (self.context.engine.stickers.loadedStickerPack(reference: packReference, forceActualized: false)
|> map { pack -> TelegramMediaFile? in
if case let .result(_, items, _) = pack, let item = items.first(where: { $0.file.fileId.id == fileId }) {
return item.file
}
return nil
}
|> deliverOnMainQueue).start(next: { [weak self] file in
if let strongSelf = self, let file {
strongSelf.animationFile = file
strongSelf.setupAnimation()
}
})
}
itemLayer.layerTintColor = UIColor.white.cgColor
itemLayer.frame = itemFrame
itemLayer.isVisibleForAnimations = true
self.itemLayer = itemLayer
self.backgroundNode.layer.addSublayer(itemLayer)
}
public func update(peer: EnginePeer, photo: TelegramMediaImage, size: CGSize) {
self.size = size
self.internalSize = size
if let markup = photo.emojiMarkup {
if markup != self.emojiMarkup {
self.emojiMarkup = markup
let colors = markup.backgroundColors.map { UInt32(bitPattern: $0) }
let backgroundImage: UIImage
if colors.count == 1 {
backgroundImage = generateSingleColorImage(size: size, color: UIColor(rgb: colors.first!))!
} else if colors.count == 2 {
backgroundImage = generateGradientImage(size: size, colors: colors.map { UIColor(rgb: $0) }, locations: [0.0, 1.0])!
} else {
backgroundImage = GradientBackgroundNode.generatePreview(size: size, colors: colors.map { UIColor(rgb: $0) })
}
self.backgroundNode.image = backgroundImage
self.backgroundNode.isHidden = true
switch markup.content {
case let .emoji(fileId):
self.fileDisposable = (self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|> deliverOnMainQueue).start(next: { [weak self] files in
if let strongSelf = self, let file = files.values.first {
strongSelf.animationFile = file
strongSelf.setupAnimation()
}
})
case let .sticker(packReference, fileId):
self.fileDisposable = (self.context.engine.stickers.loadedStickerPack(reference: packReference, forceActualized: false)
|> map { pack -> TelegramMediaFile? in
if case let .result(_, items, _) = pack, let item = items.first(where: { $0.file.fileId.id == fileId }) {
return item.file
}
return nil
}
|> deliverOnMainQueue).start(next: { [weak self] file in
if let strongSelf = self, let file {
strongSelf.animationFile = file
strongSelf.setupAnimation()
}
})
}
}
self.update(markup: markup, size: size, useAnimationNode: false)
} else if let video = smallestVideoRepresentation(photo.videoRepresentations), let peerReference = PeerReference(peer._asPeer()) {
self.backgroundNode.image = nil
@ -205,7 +197,17 @@ public final class AvatarVideoNode: ASDisplayNode {
}
}
private var visibility = false
public func updateVisibility(_ isVisible: Bool) {
self.visibility = isVisible
if isVisible, let animationNode = self.animationNode, let file = self.animationFile {
let pathPrefix = self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id)
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 384.0, height: 384.0))
let source = AnimatedStickerResourceSource(account: self.context.account, resource: file.resource, isVideo: file.isVideoSticker || file.mimeType == "video/webm")
animationNode.setup(source: source, width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .direct(cachePathPrefix: pathPrefix))
}
self.animationNode?.visibility = isVisible
if isVisible, let videoContent = self.videoContent, self.videoLoopCount != maxVideoLoopCount {
if self.videoNode == nil {
let context = self.context
@ -262,9 +264,11 @@ public final class AvatarVideoNode: ASDisplayNode {
self.videoNode = nil
videoNode.removeFromSupernode()
}
self.itemLayer?.isVisibleForAnimations = isVisible
}
public func updateLayout(size: CGSize, cornerRadius: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, cornerRadius)
self.layer.cornerRadius = cornerRadius
self.backgroundNode.frame = CGRect(origin: .zero, size: size)
@ -273,6 +277,16 @@ public final class AvatarVideoNode: ASDisplayNode {
videoNode.frame = CGRect(origin: .zero, size: size)
videoNode.updateLayout(size: size, transition: transition)
}
let itemSize = CGSize(width: size.width * 0.67, height: size.height * 0.67)
let itemFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - itemSize.width) / 2.0), y: floorToScreenPixels((size.height - itemSize.height) / 2.0)), size: itemSize)
if let animationNode = self.animationNode {
animationNode.frame = itemFrame
animationNode.updateLayout(size: itemSize)
}
if let itemLayer = self.itemLayer {
itemLayer.frame = itemFrame
}
}
public func resetPlayback() {

View File

@ -1710,7 +1710,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
}
if let hasUsername = groupType.hasUsername {
if hasUsername != (channel.addressName != nil) {
if hasUsername != (!(channel.addressName ?? "").isEmpty) {
return false
}
}
@ -1738,8 +1738,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
return false
}
}
if let hasUsername = channelType.hasUsername, hasUsername {
if hasUsername != (channel.addressName != nil) {
if let hasUsername = channelType.hasUsername {
if hasUsername != (!(channel.addressName ?? "").isEmpty) {
return false
}
}

View File

@ -1802,7 +1802,7 @@ public final class ChatListNode: ListView {
}
}
if let hasUsername = groupType.hasUsername {
if hasUsername != (channel.addressName != nil) {
if hasUsername != (!(channel.addressName ?? "").isEmpty) {
return false
}
}
@ -1831,8 +1831,8 @@ public final class ChatListNode: ListView {
return false
}
}
if let hasUsername = channelType.hasUsername, hasUsername {
if hasUsername != (channel.addressName != nil) {
if let hasUsername = channelType.hasUsername {
if hasUsername != (!(channel.addressName ?? "").isEmpty) {
return false
}
}

View File

@ -65,6 +65,11 @@ public struct ContextMenuActionItemIconSource {
}
}
public enum ContextMenuActionItemIconPosition {
case left
case right
}
public enum ContextMenuActionBadgeColor {
case accent
case inactive
@ -102,6 +107,7 @@ public final class ContextMenuActionItem {
public let badge: ContextMenuActionBadge?
public let icon: (PresentationTheme) -> UIImage?
public let iconSource: ContextMenuActionItemIconSource?
public let iconPosition: ContextMenuActionItemIconPosition
public let animationName: String?
public let textIcon: (PresentationTheme) -> UIImage?
public let textLinkAction: () -> Void
@ -117,6 +123,7 @@ public final class ContextMenuActionItem {
badge: ContextMenuActionBadge? = nil,
icon: @escaping (PresentationTheme) -> UIImage?,
iconSource: ContextMenuActionItemIconSource? = nil,
iconPosition: ContextMenuActionItemIconPosition = .right,
animationName: String? = nil,
textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil },
textLinkAction: @escaping () -> Void = {},
@ -132,6 +139,7 @@ public final class ContextMenuActionItem {
badge: badge,
icon: icon,
iconSource: iconSource,
iconPosition: iconPosition,
animationName: animationName,
textIcon: textIcon,
textLinkAction: textLinkAction,
@ -153,6 +161,7 @@ public final class ContextMenuActionItem {
badge: ContextMenuActionBadge? = nil,
icon: @escaping (PresentationTheme) -> UIImage?,
iconSource: ContextMenuActionItemIconSource? = nil,
iconPosition: ContextMenuActionItemIconPosition = .right,
animationName: String? = nil,
textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil },
textLinkAction: @escaping () -> Void = {},
@ -167,6 +176,7 @@ public final class ContextMenuActionItem {
self.badge = badge
self.icon = icon
self.iconSource = iconSource
self.iconPosition = iconPosition
self.animationName = animationName
self.textIcon = textIcon
self.textLinkAction = textLinkAction

View File

@ -549,8 +549,11 @@ public class Window1 {
var keyboardHeight: CGFloat
if keyboardFrame.isEmpty || keyboardFrame.maxY < screenHeight {
if isTablet && screenHeight - keyboardFrame.maxY < 5.0 {
if inPopover || (isTablet && screenHeight - keyboardFrame.maxY < 5.0) {
keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)
if inPopover && !keyboardHeight.isZero {
keyboardHeight = max(0.0, keyboardHeight - popoverDelta)
}
} else {
keyboardHeight = 0.0
}

View File

@ -3013,22 +3013,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
return TGPaintingData(drawing: drawingData, entitiesData: entitiesData, image: image, stillImage: stillImage, hasAnimation: hasAnimatedEntities, stickers: stickers)
}
public func resultImage() -> UIImage! {
let image = generateImage(self.drawingView.imageSize, contextGenerator: { size, context in
let bounds = CGRect(origin: .zero, size: size)
context.clear(bounds)
if let cgImage = self.drawingView.drawingImage?.cgImage {
context.draw(cgImage, in: bounds)
}
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
self.entitiesView.layer.render(in: context)
}, opaque: false, scale: 1.0)
return image
}
public func animateOut(_ completion: @escaping (() -> Void)) {
self.selectionContainerView.alpha = 0.0

View File

@ -27,6 +27,8 @@ typedef enum
@property (nonatomic, readonly) NSTimeInterval trimEndValue;
@property (nonatomic, readonly) TGMediaVideoConversionPreset preset;
@property (nonatomic, readonly) int64_t stickerPackId;
@property (nonatomic, readonly) int64_t stickerPackAccessHash;
@property (nonatomic, readonly) int64_t documentId;
@property (nonatomic, strong, readonly) NSArray *colors;
@ -43,6 +45,7 @@ typedef enum
+ (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize preset:(TGMediaVideoConversionPreset)preset;
+ (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values preset:(TGMediaVideoConversionPreset)preset;
+ (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values preset:(TGMediaVideoConversionPreset)preset documentId:(int64_t)documentId colors:(NSArray *)colors;
+ (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values preset:(TGMediaVideoConversionPreset)preset stickerPackId:(int64_t)stickerPackId stickerPackAccessHash:(int64_t)stickerPackAccessHash documentId:(int64_t)documentId colors:(NSArray *)colors;
+ (instancetype)editAdjustmentsWithDictionary:(NSDictionary *)dictionary;
+ (instancetype)editAdjustmentsWithOriginalSize:(CGSize)originalSize

View File

@ -151,6 +151,29 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
return adjustments;
}
+ (instancetype)editAdjustmentsWithPhotoEditorValues:(PGPhotoEditorValues *)values preset:(TGMediaVideoConversionPreset)preset stickerPackId:(int64_t)stickerPackId stickerPackAccessHash:(int64_t)stickerPackAccessHash documentId:(int64_t)documentId colors:(NSArray *)colors {
TGVideoEditAdjustments *adjustments = [[[self class] alloc] init];
adjustments->_originalSize = values.originalSize;
CGRect cropRect = values.cropRect;
if (CGRectIsEmpty(cropRect)) {
cropRect = CGRectMake(0.0f, 0.0f, values.originalSize.width, values.originalSize.height);
}
adjustments->_cropRect = cropRect;
adjustments->_cropOrientation = values.cropOrientation;
adjustments->_cropRotation = values.cropRotation;
adjustments->_cropLockedAspectRatio = values.cropLockedAspectRatio;
adjustments->_cropMirrored = values.cropMirrored;
adjustments->_paintingData = [values.paintingData dataForAnimation];
adjustments->_sendAsGif = true;
adjustments->_preset = preset;
adjustments->_stickerPackId = stickerPackId;
adjustments->_stickerPackAccessHash = stickerPackAccessHash;
adjustments->_documentId = documentId;
adjustments->_colors = colors;
return adjustments;
}
- (instancetype)editAdjustmentsWithPreset:(TGMediaVideoConversionPreset)preset maxDuration:(NSTimeInterval)maxDuration
{
TGVideoEditAdjustments *adjustments = [[[self class] alloc] init];

View File

@ -25,6 +25,7 @@ swift_library(
"//submodules/GalleryUI:GalleryUI",
"//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/AccountContext:AccountContext",
"//submodules/AvatarVideoNode:AvatarVideoNode",
],
visibility = [
"//visibility:public",

View File

@ -16,6 +16,7 @@ import UniversalMediaPlayer
import RadialStatusNode
import TelegramUIPreferences
import AvatarNode
import AvatarVideoNode
private class PeerInfoAvatarListLoadingStripNode: ASImageNode {
private var currentInHierarchy = false
@ -210,6 +211,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
private let playbackStartDisposable = MetaDisposable()
private var markupNode: AvatarVideoNode?
private let statusDisposable = MetaDisposable()
private let preloadDisposable = MetaDisposable()
private let statusNode: RadialStatusNode
@ -260,6 +262,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
self.preloadDisposable.set(preloadVideoResource(postbox: self.context.account.postbox, userLocation: .other, userContentType: .video, resourceReference: videoContent.fileReference.resourceReference(videoContent.fileReference.media.resource), duration: duration).start())
}
}
self.markupNode?.updateVisibility(isCentral)
}
}
@ -346,6 +349,12 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
}
transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction)
}
if let markupNode = self.markupNode {
if case .immediate = transition, fraction == 1.0 {
return
}
transition.updateAlpha(node: markupNode, alpha: 1.0 - fraction)
}
}
private func setupVideoPlayback() {
@ -443,24 +452,27 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
let videoRepresentations: [VideoRepresentationWithReference]
let immediateThumbnailData: Data?
var id: Int64
let markup: TelegramMediaImage.EmojiMarkup?
switch item {
case let .custom(node):
id = 0
representations = []
videoRepresentations = []
immediateThumbnailData = nil
id = 0
markup = nil
if !synchronous {
self.addSubnode(node)
}
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
id = self.peer.id.id._internalGetInt64Value()
representations = topRepresentations
videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail
id = self.peer.id.id._internalGetInt64Value()
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
id = id &+ resource.photoId
}
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, _):
markup = nil
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, markupValue):
representations = imageRepresentations
videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail
@ -469,10 +481,37 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
} else {
id = self.peer.id.id._internalGetInt64Value()
}
markup = markupValue
}
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, immediateThumbnailData: immediateThumbnailData, autoFetchFullSize: true, attemptSynchronously: synchronous, skipThumbnail: fullSizeOnly, skipBlurIfLarge: isMain), attemptSynchronously: synchronous, dispatchOnDisplayLink: false)
if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer) {
if let markup {
if let videoNode = self.videoNode {
self.videoContent = nil
self.videoStartTimestamp = nil
self.videoNode = nil
videoNode.removeFromSupernode()
}
self.statusPromise.set(.single(nil))
self.statusDisposable.set(nil)
let markupNode: AvatarVideoNode
if let current = self.markupNode {
markupNode = current
} else {
markupNode = AvatarVideoNode(context: self.context)
self.insertSubnode(markupNode, belowSubnode: self.statusNode)
self.markupNode = markupNode
}
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
markupNode.updateVisibility(self.isCentral ?? true)
if !self.didSetReady {
self.didSetReady = true
self.isReady.set(.single(true))
}
} else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer) {
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: fullSizeOnly, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil)
@ -491,7 +530,6 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
}
self.statusPromise.set(.single(nil))
self.statusDisposable.set(nil)
self.imageNode.imageUpdated = { [weak self] _ in
@ -520,6 +558,10 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
videoNode.updateLayout(size: imageSize, transition: .immediate)
videoNode.frame = imageFrame
}
if let markupNode = self.markupNode {
markupNode.updateLayout(size: imageSize, cornerRadius: 0.0, transition: .immediate)
markupNode.frame = imageFrame
}
}
}

View File

@ -0,0 +1,150 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
private let phrases = [
"Вітаю",
"你好",
"Hello",
"سلام",
"Bonjour",
"Guten tag",
"שלום",
"नमस्ते",
"Ciao",
"こんにちは",
"Hei",
"Olá",
"Привет",
"Zdravo",
"Hola",
"Привіт",
"Salom",
"Halo"
]
private var simultaneousDisplayCount = 13
private let referenceWidth: CGFloat = 1180
private let positions: [CGPoint] = [
CGPoint(x: 315.0, y: 83.0),
CGPoint(x: 676.0, y: 18.0),
CGPoint(x: 880.0, y: 130.0),
CGPoint(x: 90.0, y: 214.0),
CGPoint(x: 550.0, y: 150.0),
CGPoint(x: 1130.0, y: 220.0),
CGPoint(x: 220.0, y: 440.0),
CGPoint(x: 1080.0, y: 350.0),
CGPoint(x: 85.0, y: 630.0),
CGPoint(x: 1180.0, y: 550.0),
CGPoint(x: 150.0, y: 810.0),
CGPoint(x: 1010.0, y: 770.0),
CGPoint(x: 40.0, y: 1000.0),
CGPoint(x: 1130.0, y: 1000.0)
]
final class HelloView: UIView, PhoneDemoDecorationView {
private var activePhrases = Set<Int>()
private var activePositions = Set<Int>()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupAnimations() {
guard self.activePhrases.isEmpty else {
return
}
var ids: [Int] = []
for i in 0 ..< phrases.count {
ids.append(i)
}
ids.shuffle()
let phraseIds = Array(self.availablePhraseIds()).shuffled()
let positionIds = Array(self.availablePositionIds()).shuffled()
for i in 0 ..< simultaneousDisplayCount {
let delay: Double = Double.random(in: 0.0 ..< 0.8)
Queue.mainQueue().after(delay) {
self.spawnPhrase(phraseIds[i], positionIndex: positionIds[i])
}
}
}
func availablePhraseIds() -> Set<Int> {
var ids = Set<Int>()
for i in 0 ..< phrases.count {
ids.insert(i)
}
for id in self.activePhrases {
ids.remove(id)
}
return ids
}
func availablePositionIds() -> Set<Int> {
var ids = Set<Int>()
for i in 0 ..< positions.count {
ids.insert(i)
}
for id in self.activePositions {
ids.remove(id)
}
return ids
}
func spawnNextPhrase() {
let phraseIds = Array(self.availablePhraseIds()).shuffled()
let positionIds = Array(self.availablePositionIds()).shuffled()
if let phrase = phraseIds.first, let position = positionIds.first {
self.spawnPhrase(phrase, positionIndex: position)
}
}
func spawnPhrase(_ index: Int, positionIndex: Int) {
let view = UILabel()
view.alpha = 0.0
view.text = phrases[index]
view.font = Font.with(size: 24.0, design: .round, weight: .semibold, traits: [])
view.textColor = UIColor(rgb: 0xffffff, alpha: CGFloat.random(in: 0.4 ... 0.6))
view.layer.compositingFilter = "softLightBlendMode"
view.sizeToFit()
view.center = self.positionForIndex(positionIndex)
self.activePhrases.insert(index)
self.activePositions.insert(positionIndex)
let duration: Double = Double.random(in: 1.75...2.25)
view.layer.animateKeyframes(values: [0.0, 1.0, 0.0] as [NSNumber], duration: duration, keyPath: "opacity", removeOnCompletion: false, completion: { [weak view] _ in
self.activePhrases.remove(index)
self.activePositions.remove(positionIndex)
view?.removeFromSuperview()
self.spawnNextPhrase()
})
view.layer.animateScale(from: CGFloat.random(in: 0.4 ..< 0.6), to: CGFloat.random(in: 0.9 ..< 1.2), duration: duration, removeOnCompletion: false)
self.addSubview(view)
}
func positionForIndex(_ index: Int) -> CGPoint {
var position = positions[index]
let spread: CGPoint = CGPoint(x: 30.0, y: 5.0)
position.x = (self.frame.width - self.frame.height) / 2.0 + position.x / referenceWidth * self.frame.height + CGFloat.random(in: -spread.x ... spread.x)
position.y = position.y / referenceWidth * self.frame.height + CGFloat.random(in: -spread.y ... spread.y)
return position
}
func setVisible(_ visible: Bool) {
self.setupAnimations()
}
func resetAnimation() {
}
}

View File

@ -138,6 +138,21 @@ private final class PhoneView: UIView {
}
}
var model: PhoneDemoComponent.Model = .notch {
didSet {
if self.model != oldValue {
switch self.model {
case .notch:
self.borderView.image = phoneBorderImage
self.shimmerBorderView.image = phoneBorderMaskImage
case .island:
self.borderView.image = phoneBorderImage14
self.shimmerBorderView.image = phoneBorderMaskImage14
}
}
}
}
override init(frame: CGRect) {
self.contentContainerView = UIView()
self.contentContainerView.clipsToBounds = true
@ -355,21 +370,30 @@ final class PhoneDemoComponent: Component {
case fasterStars
case badgeStars
case emoji
case hello
}
enum Model {
case notch
case island
}
let context: AccountContext
let position: Position
let model: Model
let videoFile: TelegramMediaFile?
let decoration: BackgroundDecoration
public init(
context: AccountContext,
position: PhoneDemoComponent.Position,
model: Model = .notch,
videoFile: TelegramMediaFile?,
decoration: BackgroundDecoration = .none
) {
self.context = context
self.position = position
self.model = model
self.videoFile = videoFile
self.decoration = decoration
}
@ -381,6 +405,9 @@ final class PhoneDemoComponent: Component {
if lhs.position != rhs.position {
return false
}
if lhs.model != rhs.model {
return false
}
if lhs.videoFile != rhs.videoFile {
return false
}
@ -452,6 +479,7 @@ final class PhoneDemoComponent: Component {
self.containerView.frame = CGRect(origin: .zero, size: availableSize)
self.decorationContainerView.frame = CGRect(origin: CGPoint(x: -availableSize.width * 0.5, y: 0.0), size: CGSize(width: availableSize.width * 2.0, height: availableSize.height))
self.phoneView.bounds = CGRect(origin: .zero, size: phoneSize)
self.phoneView.model = component.model
switch component.decoration {
case .none:
@ -504,6 +532,13 @@ final class PhoneDemoComponent: Component {
self.decorationView = starsView
self.decorationContainerView.addSubview(starsView)
}
case .hello:
if let _ = self.decorationView as? HelloView {
} else {
let starsView = HelloView(frame: self.decorationContainerView.bounds)
self.decorationView = starsView
self.decorationContainerView.addSubview(starsView)
}
}
self.phoneView.setup(context: component.context, videoFile: component.videoFile, position: component.position)

View File

@ -909,9 +909,12 @@ private final class DemoSheetContent: CombinedComponent {
id: PremiumDemoScreen.Subject.translation,
component: AnyComponent(
PageComponent(
content: AnyComponent(AppIconsDemoComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
appIcons: appIcons
position: .top,
model: .island,
videoFile: configuration.videos["translations"],
decoration: .hello
)),
title: strings.Premium_Translation,
text: isStandalone ? strings.Premium_TranslationStandaloneInfo : strings.Premium_TranslationInfo,

View File

@ -250,7 +250,7 @@ public enum PremiumSource: Equatable {
return "deeplink"
}
case .translation:
return "translation"
return "translations"
}
}
}
@ -329,7 +329,7 @@ enum PremiumPerk: CaseIterable {
case .emojiStatus:
return "emoji_status"
case .translation:
return "translation"
return "translations"
}
}
@ -428,7 +428,7 @@ enum PremiumPerk: CaseIterable {
case .emojiStatus:
return "Premium/Perk/Status"
case .translation:
return "Premium/Perk/Status"
return "Premium/Perk/Translation"
}
}
}
@ -1492,7 +1492,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
UIColor(rgb: 0x5A6EEE),
UIColor(rgb: 0x548DFF),
UIColor(rgb: 0x54A3FF),
UIColor(rgb: 0x54bdff)
UIColor(rgb: 0x54bdff),
UIColor(rgb: 0x71c8ff)
]
let accountContext = context.component.context

View File

@ -1184,6 +1184,26 @@ public class PremiumLimitsListScreen: ViewController {
)
)
availableItems[.translation] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.translation,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
model: .island,
videoFile: configuration.videos["translations"],
decoration: .hello
)),
title: strings.Premium_Translation,
text: isStandalone ? strings.Premium_TranslationStandaloneInfo : strings.Premium_TranslationInfo,
textColor: textColor
)
)
)
)
if let order = controller.order {
var items: [DemoPagerComponent.Item] = order.compactMap { availableItems[$0] }
let index: Int

View File

@ -70,47 +70,8 @@ final class SwirlStarsView: UIView, PhoneDemoDecorationView {
animation.fillMode = .forwards
animation.repeatCount = .infinity
node.addAnimation(animation, forKey: "rotation")
self.setupMovementAnimation()
}
func setupMovementAnimation() {
guard let node = self.sceneView.scene?.rootNode.childNode(withName: "star", recursively: false) else {
return
}
node.position = SCNVector3(3.5, 0.0, -2.0)
let firstPath = UIBezierPath()
firstPath.move(to: CGPoint(x: 3.5, y: -2.0))
firstPath.addLine(to: CGPoint(x: -15.5, y: 15.5))
let firstAction = SCNAction.moveAlong(path: firstPath, duration: 2.0)
SCNTransaction.begin()
SCNTransaction.animationDuration = 2.0
node.runAction(firstAction)
SCNTransaction.completionBlock = { [weak self, weak node] in
Queue.mainQueue().after(2.2, {
node?.position = SCNVector3(0.0, 0.0, -3.0)
let secondPath = UIBezierPath()
secondPath.move(to: CGPoint(x: 0.0, y: -3.0))
secondPath.addLine(to: CGPoint(x: 15.5, y: 20.0))
let secondAction = SCNAction.moveAlong(path: secondPath, duration: 2.0)
SCNTransaction.begin()
SCNTransaction.animationDuration = 2.0
node?.runAction(secondAction)
SCNTransaction.completionBlock = { [weak self] in
Queue.mainQueue().after(2.2, {
self?.setupMovementAnimation()
})
}
SCNTransaction.commit()
})
}
SCNTransaction.commit()
}
func resetAnimation() {
}
@ -120,85 +81,3 @@ final class SwirlStarsView: UIView, PhoneDemoDecorationView {
self.sceneView.frame = CGRect(origin: .zero, size: frame.size)
}
}
extension UIBezierPath {
var elements: [PathElement] {
var pathElements = [PathElement]()
withUnsafeMutablePointer(to: &pathElements) { elementsPointer in
cgPath.apply(info: elementsPointer) { (userInfo, nextElementPointer) in
let nextElement = PathElement(element: nextElementPointer.pointee)
let elementsPointer = userInfo!.assumingMemoryBound(to: [PathElement].self)
elementsPointer.pointee.append(nextElement)
}
}
return pathElements
}
}
enum PathElement {
case moveToPoint(CGPoint)
case addLineToPoint(CGPoint)
case addQuadCurveToPoint(CGPoint, CGPoint)
case addCurveToPoint(CGPoint, CGPoint, CGPoint)
case closeSubpath
init(element: CGPathElement) {
switch element.type {
case .moveToPoint:
self = .moveToPoint(element.points[0])
case .addLineToPoint:
self = .addLineToPoint(element.points[0])
case .addQuadCurveToPoint:
self = .addQuadCurveToPoint(element.points[0], element.points[1])
case .addCurveToPoint:
self = .addCurveToPoint(element.points[0], element.points[1], element.points[2])
case .closeSubpath:
self = .closeSubpath
@unknown default:
self = .closeSubpath
}
}
}
public extension SCNAction {
class func moveAlong(path: UIBezierPath, duration animationDuration: Double) -> SCNAction {
let points = path.elements
var actions = [SCNAction]()
for point in points {
switch point {
case .moveToPoint(let a):
let moveAction = SCNAction.move(to: SCNVector3(a.x, 0, a.y), duration: animationDuration)
actions.append(moveAction)
break
case .addCurveToPoint(let a, let b, let c):
let moveAction1 = SCNAction.move(to: SCNVector3(a.x, 0, a.y), duration: animationDuration)
let moveAction2 = SCNAction.move(to: SCNVector3(b.x, 0, b.y), duration: animationDuration)
let moveAction3 = SCNAction.move(to: SCNVector3(c.x, 0, c.y), duration: animationDuration)
actions.append(moveAction1)
actions.append(moveAction2)
actions.append(moveAction3)
break
case .addLineToPoint(let a):
let moveAction = SCNAction.move(to: SCNVector3(a.x, 0, a.y), duration: animationDuration)
actions.append(moveAction)
break
case .addQuadCurveToPoint(let a, let b):
let moveAction1 = SCNAction.move(to: SCNVector3(a.x, 0, a.y), duration: animationDuration)
let moveAction2 = SCNAction.move(to: SCNVector3(b.x, 0, b.y), duration: animationDuration)
actions.append(moveAction1)
actions.append(moveAction2)
break
default:
let moveAction = SCNAction.move(to: SCNVector3(0, 0, 0), duration: animationDuration)
actions.append(moveAction)
break
}
}
return SCNAction.sequence(actions)
}
}

View File

@ -496,7 +496,9 @@ final class LocalizationListControllerNode: ViewControllerTracingNode {
}
}
entries.append(.doNotTranslate(text: presentationData.strings.Localization_DoNotTranslate, value: value))
if showTranslate || translateChats {
entries.append(.doNotTranslate(text: presentationData.strings.Localization_DoNotTranslate, value: value))
}
if showTranslate {
entries.append(.translateInfo(text: ignoredLanguages.count > 1 ? presentationData.strings.Localization_DoNotTranslateManyInfo : presentationData.strings.Localization_DoNotTranslateInfo))

View File

@ -199,6 +199,9 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate {
@objc internal func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var updated = textField.text ?? ""
updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string)
if updated.hasPrefix("#") {
updated.removeFirst()
}
if updated.count <= 6 && updated.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil {
textField.text = updated.uppercased()
textField.textColor = self.theme.chat.inputPanel.inputTextColor

View File

@ -838,6 +838,34 @@ public extension TelegramEngine.EngineData.Item {
}
}
public struct TranslationHidden: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = Bool
fileprivate var id: EnginePeer.Id
public var mapKey: EnginePeer.Id {
return self.id
}
public init(id: EnginePeer.Id) {
self.id = id
}
var key: PostboxViewKey {
return .cachedPeerData(peerId: self.id)
}
func extract(view: PostboxView) -> Result {
guard let view = view as? CachedPeerDataView else {
preconditionFailure()
}
if let cachedData = view.cachedPeerData as? CachedChannelData {
return cachedData.flags.contains(.translationHidden)
} else {
return false
}
}
}
public struct LegacyGroupParticipants: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
public typealias Result = EnginePeerCachedInfoItem<[EngineLegacyGroupParticipant]>

View File

@ -1291,9 +1291,23 @@ final class AvatarEditorScreenComponent: Component {
entity.position = CGPoint(x: drawingSize.width / 2.0, y: drawingSize.height / 2.0)
entity.scale = 3.3
var documentId: Int64 = 0
if case let .file(file) = entity.content, file.isCustomEmoji {
documentId = file.fileId.id
var fileId: Int64 = 0
var stickerPackId: Int64 = 0
var stickerPackAccessHash: Int64 = 0
if case let .file(file) = entity.content {
if file.isCustomEmoji {
fileId = file.fileId.id
} else if file.isAnimatedSticker {
for attribute in file.attributes {
if case let .Sticker(_, packReference, _) = attribute, let packReference, case let .id(id, accessHash) = packReference {
fileId = file.fileId.id
stickerPackId = id
stickerPackAccessHash = accessHash
break
}
}
}
}
let colors: [NSNumber] = state.selectedBackground.colors.map { Int32(bitPattern: $0) as NSNumber }
@ -1337,9 +1351,15 @@ final class AvatarEditorScreenComponent: Component {
}, opaque: false)!
if entity.isAnimated {
controller.videoCompletion(combinedImage, tempUrl, TGVideoEditAdjustments(photoEditorValues: adjustments, preset: preset, documentId: documentId, colors: colors), { [weak controller] in
controller?.dismiss()
})
if stickerPackId != 0 {
controller.videoCompletion(combinedImage, tempUrl, TGVideoEditAdjustments(photoEditorValues: adjustments, preset: preset, stickerPackId: stickerPackId, stickerPackAccessHash: stickerPackAccessHash, documentId: fileId, colors: colors), { [weak controller] in
controller?.dismiss()
})
} else {
controller.videoCompletion(combinedImage, tempUrl, TGVideoEditAdjustments(photoEditorValues: adjustments, preset: preset, documentId: fileId, colors: colors), { [weak controller] in
controller?.dismiss()
})
}
} else {
controller.imageCompletion(combinedImage, { [weak controller] in
controller?.dismiss()
@ -1436,6 +1456,16 @@ public final class AvatarEditorScreen: ViewControllerComponentContainer {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.scrollToTop = { [weak self] in
if let self {
if let view = self.node.hostView.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View {
view.scrollToTop()
} else if let view = self.node.hostView.findTaggedView(tag: EmojiPagerContentComponent.Tag(id: AnyHashable("emoji"))) as? EmojiPagerContentComponent.View {
view.scrollToTop()
}
}
}
}
required public init(coder aDecoder: NSCoder) {

View File

@ -1140,6 +1140,9 @@ private class ColorInputFieldNode: ASDisplayNode, UITextFieldDelegate {
@objc internal func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var updated = textField.text ?? ""
updated.replaceSubrange(updated.index(updated.startIndex, offsetBy: range.lowerBound) ..< updated.index(updated.startIndex, offsetBy: range.upperBound), with: string)
if updated.hasPrefix("#") {
updated.removeFirst()
}
if updated.count <= 6 && updated.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789abcdefABCDEF").inverted) == nil {
textField.text = updated.uppercased()
textField.textColor = self.theme.chat.inputPanel.inputTextColor

View File

@ -3927,7 +3927,7 @@ public final class EmojiPagerContentComponent: Component {
}
}
private func scrollToTop() {
public func scrollToTop() {
guard let _ = self.component, let _ = self.pagerEnvironment, let itemLayout = self.itemLayout else {
return
}
@ -5116,7 +5116,7 @@ public final class EmojiPagerContentComponent: Component {
self.visibleSearchHeader?.endEditing(true)
}
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.ignoreScrolling {
return

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "translatepremium.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,125 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 10.920044 10.255630 cm
1.000000 1.000000 1.000000 scn
5.750232 15.581270 m
5.426097 15.905405 4.900570 15.905405 4.576435 15.581270 c
4.252300 15.257134 4.252300 14.731607 4.576435 14.407473 c
6.743102 12.240807 l
7.067237 11.916671 7.592764 11.916671 7.916899 12.240807 c
8.241034 12.564941 8.241034 13.090468 7.916899 13.414603 c
5.750232 15.581270 l
h
12.746666 11.491037 m
10.580000 11.491037 l
4.080000 11.491037 l
1.913333 11.491037 l
1.454937 11.491037 1.083333 11.119434 1.083333 10.661037 c
1.083333 10.202641 1.454937 9.831038 1.913333 9.831038 c
3.267690 9.831038 l
3.397668 6.842636 4.247280 4.446305 5.928415 2.763829 c
4.622104 2.096912 2.938897 1.741038 0.830000 1.741038 c
0.371604 1.741038 0.000000 1.369434 0.000000 0.911038 c
0.000000 0.452641 0.371604 0.081038 0.830000 0.081038 c
3.431095 0.081038 5.621857 0.582098 7.330002 1.650631 c
9.038148 0.582098 11.228905 0.081038 13.830000 0.081038 c
14.288396 0.081038 14.660000 0.452641 14.660000 0.911038 c
14.660000 1.369434 14.288396 1.741038 13.830000 1.741038 c
11.721103 1.741038 10.037899 2.096912 8.731588 2.763829 c
10.412724 4.446305 11.262332 6.842636 11.392309 9.831038 c
12.746666 9.831038 l
13.205063 9.831038 13.576666 10.202641 13.576666 10.661037 c
13.576666 11.119434 13.205063 11.491037 12.746666 11.491037 c
h
7.104398 3.935437 m
5.818785 5.221051 5.057345 7.146626 4.929302 9.831038 c
9.730699 9.831038 l
9.602655 7.146626 8.841215 5.221051 7.555602 3.935437 c
7.482480 3.862315 7.407287 3.790889 7.330002 3.721178 c
7.252717 3.790889 7.177521 3.862315 7.104398 3.935437 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 4.419800 5.891495 cm
1.000000 1.000000 1.000000 scn
6.473423 13.172696 m
6.345813 13.484631 6.042246 13.688431 5.705219 13.688431 c
5.368191 13.688431 5.064625 13.484631 4.937015 13.172696 c
0.062015 1.256029 l
-0.111549 0.831761 0.091686 0.347124 0.515953 0.173560 c
0.940221 -0.000004 1.424859 0.203231 1.598423 0.627499 c
3.160168 4.445098 l
8.250270 4.445098 l
9.812015 0.627499 l
9.985579 0.203231 10.470217 -0.000004 10.894484 0.173560 c
11.318751 0.347124 11.521987 0.831761 11.348423 1.256029 c
6.473423 13.172696 l
h
7.571179 6.105098 m
5.705219 10.666334 l
3.839258 6.105098 l
7.571179 6.105098 l
h
f*
n
Q
endstream
endobj
3 0 obj
2291
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000002381 00000 n
0000002404 00000 n
0000002577 00000 n
0000002651 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2710
%%EOF

View File

@ -6737,15 +6737,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return peer?.isPremium ?? false
} |> distinctUntilChanged
let isHidden = self.chatDisplayNode.historyNode.cachedPeerDataAndMessages
|> map { cachedDataAndMessages -> Bool in
let (cachedData, _) = cachedDataAndMessages
var isHidden = false
if let cachedData = cachedData as? CachedChannelData, cachedData.flags.contains(.translationHidden) {
isHidden = true
}
return isHidden
} |> distinctUntilChanged
let isHidden = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.TranslationHidden(id: self.context.account.peerId))
|> distinctUntilChanged
self.translationStateDisposable = (combineLatest(
queue: .concurrentDefaultQueue(),
isPremium,

View File

@ -1944,9 +1944,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let dismissedTranslationPanelNode = dismissedTranslationPanelNode {
var dismissedPanelFrame = dismissedTranslationPanelNode.frame
dismissedPanelFrame.origin.y = -dismissedPanelFrame.size.height
transition.updateFrame(node: dismissedTranslationPanelNode, frame: dismissedPanelFrame, completion: { [weak dismissedTranslationPanelNode] _ in
transition.updateAlpha(node: dismissedTranslationPanelNode, alpha: 0.0, completion: { [weak dismissedTranslationPanelNode] _ in
dismissedTranslationPanelNode?.removeFromSupernode()
})
dismissedTranslationPanelNode.animateOut()
}
if let dismissedImportStatusPanelNode = dismissedImportStatusPanelNode {

View File

@ -1249,7 +1249,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
parentMessage: item.message,
constrainedSize: CGSize(width: availableContentWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer,
associatedData: item.associatedData
))
}

View File

@ -2011,7 +2011,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
parentMessage: item.message,
constrainedSize: CGSize(width: maximumNodeWidth - layoutConstants.text.bubbleInsets.left - layoutConstants.text.bubbleInsets.right, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer,
associatedData: item.associatedData
))
replyInfoSizeApply = (sizeAndApply.0, { synchronousLoads in sizeAndApply.1(synchronousLoads) })

View File

@ -469,7 +469,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD
parentMessage: item.message,
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer,
associatedData: item.associatedData
))
}
} else if let _ = attribute as? InlineBotMessageAttribute {

View File

@ -350,7 +350,8 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
parentMessage: item.message,
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer,
associatedData: item.associatedData
))
}
}

View File

@ -32,6 +32,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
let constrainedSize: CGSize
let animationCache: AnimationCache?
let animationRenderer: MultiAnimationRenderer?
let associatedData: ChatMessageItemAssociatedData
init(
presentationData: ChatPresentationData,
@ -42,7 +43,8 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
parentMessage: Message,
constrainedSize: CGSize,
animationCache: AnimationCache?,
animationRenderer: MultiAnimationRenderer?
animationRenderer: MultiAnimationRenderer?,
associatedData: ChatMessageItemAssociatedData
) {
self.presentationData = presentationData
self.strings = strings
@ -53,6 +55,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
self.constrainedSize = constrainedSize
self.animationCache = animationCache
self.animationRenderer = animationRenderer
self.associatedData = associatedData
}
}
@ -165,7 +168,20 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
let messageText: NSAttributedString
if isText {
let entities = (arguments.message.textEntitiesAttribute?.entities ?? []).filter { entity in
var text = arguments.message.text
var messageEntities = arguments.message.textEntitiesAttribute?.entities ?? []
if let translateToLanguage = arguments.associatedData.translateToLanguage, !text.isEmpty {
for attribute in arguments.message.attributes {
if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage {
text = attribute.text
messageEntities = attribute.entities
break
}
}
}
let entities = messageEntities.filter { entity in
if case .Spoiler = entity.type {
return true
} else if case .CustomEmoji = entity.type {
@ -175,9 +191,9 @@ class ChatMessageReplyInfoNode: ASDisplayNode {
}
}
if entities.count > 0 {
messageText = stringWithAppliedEntities(trimToLineCount(arguments.message.text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: arguments.message)
messageText = stringWithAppliedEntities(trimToLineCount(text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: arguments.message)
} else {
messageText = NSAttributedString(string: textString.string, font: textFont, textColor: textColor)
messageText = NSAttributedString(string: text, font: textFont, textColor: textColor)
}
} else {
messageText = NSAttributedString(string: textString.string, font: textFont, textColor: textColor)

View File

@ -677,7 +677,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
parentMessage: item.message,
constrainedSize: CGSize(width: availableWidth, height: CGFloat.greatestFiniteMagnitude),
animationCache: item.controllerInteraction.presentationContext.animationCache,
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer
animationRenderer: item.controllerInteraction.presentationContext.animationRenderer,
associatedData: item.associatedData
))
}

View File

@ -48,6 +48,8 @@ final class ChatTranslationPanelNode: ASDisplayNode {
super.init()
self.clipsToBounds = true
self.addSubnode(self.separatorNode)
self.addSubnode(self.button)
self.addSubnode(self.moreButton)
@ -63,6 +65,10 @@ final class ChatTranslationPanelNode: ASDisplayNode {
}
}
func animateOut() {
self.layer.animateBounds(from: self.bounds, to: self.bounds.offsetBy(dx: 0.0, dy: self.bounds.size.height), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
}
func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> CGFloat {
let previousIsEnabled = self.chatInterfaceState?.translationState?.isEnabled
self.chatInterfaceState = interfaceState
@ -123,13 +129,14 @@ final class ChatTranslationPanelNode: ASDisplayNode {
let moreButtonSize = self.moreButton.measure(CGSize(width: 100.0, height: panelHeight))
self.moreButton.frame = CGRect(origin: CGPoint(x: width - contentRightInset - moreButtonSize.width, y: floorToScreenPixels((panelHeight - moreButtonSize.height) / 2.0)), size: moreButtonSize)
let buttonPadding: CGFloat = 10.0
let buttonSpacing: CGFloat = 10.0
let buttonTextSize = self.buttonTextNode.updateLayout(CGSize(width: width - contentRightInset - moreButtonSize.width, height: panelHeight))
if let icon = self.buttonIconNode.image {
let buttonSize = CGSize(width: buttonTextSize.width + icon.size.width + buttonSpacing, height: panelHeight)
let buttonSize = CGSize(width: buttonTextSize.width + icon.size.width + buttonSpacing + buttonPadding * 2.0, height: panelHeight)
self.button.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((width - buttonSize.width) / 2.0), y: 0.0), size: buttonSize)
self.buttonIconNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((buttonSize.height - icon.size.height) / 2.0)), size: icon.size)
self.buttonTextNode.frame = CGRect(origin: CGPoint(x: icon.size.width + buttonSpacing, y: floorToScreenPixels((buttonSize.height - buttonTextSize.height) / 2.0)), size: buttonTextSize)
self.buttonIconNode.frame = CGRect(origin: CGPoint(x: buttonPadding, y: floorToScreenPixels((buttonSize.height - icon.size.height) / 2.0)), size: icon.size)
self.buttonTextNode.frame = CGRect(origin: CGPoint(x: buttonPadding + icon.size.width + buttonSpacing, y: floorToScreenPixels((buttonSize.height - buttonTextSize.height) / 2.0)), size: buttonTextSize)
}
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))

View File

@ -29,6 +29,7 @@ import MultiAnimationRenderer
import ComponentDisplayAdapters
import ChatTitleView
import AppBundle
import AvatarVideoNode
enum PeerInfoHeaderButtonKey: Hashable {
case message
@ -309,6 +310,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
let avatarNode: AvatarNode
fileprivate var videoNode: UniversalVideoNode?
fileprivate var markupNode: AvatarVideoNode?
fileprivate var iconView: ComponentView<Empty>?
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
@ -380,12 +382,23 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
return
}
if fraction > 0.0 {
self.videoNode?.pause()
videoNode.pause()
} else {
self.videoNode?.play()
videoNode.play()
}
transition.updateAlpha(node: videoNode, alpha: 1.0 - fraction)
}
if let markupNode = self.markupNode {
if case .immediate = transition, fraction == 1.0 {
return
}
if fraction > 0.0 {
markupNode.updateVisibility(false)
} else {
markupNode.updateVisibility(true)
}
transition.updateAlpha(node: markupNode, alpha: 1.0 - fraction)
}
}
var removedPhotoResourceIds = Set<String>()
@ -461,9 +474,11 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
}
}
var isForum = false
let avatarCornerRadius: CGFloat
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
avatarCornerRadius = floor(avatarSize * 0.25)
isForum = true
} else {
avatarCornerRadius = avatarSize / 2.0
}
@ -485,12 +500,14 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
let videoRepresentations: [VideoRepresentationWithReference]
let immediateThumbnailData: Data?
var videoId: Int64
let markup: TelegramMediaImage.EmojiMarkup?
switch item {
case .custom:
representations = []
videoRepresentations = []
immediateThumbnailData = nil
videoId = 0
markup = nil
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
representations = topRepresentations
videoRepresentations = videoRepresentationsValue
@ -499,7 +516,8 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
videoId = videoId &+ resource.photoId
}
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, _):
markup = nil
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, markupValue):
representations = imageRepresentations
videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail
@ -508,11 +526,31 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
} else {
videoId = peer.id.id._internalGetInt64Value()
}
markup = markupValue
}
self.containerNode.isGestureEnabled = !isSettings
if threadInfo == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
if let markup {
if let videoNode = self.videoNode {
self.videoContent = nil
self.videoStartTimestamp = nil
self.videoNode = nil
videoNode.removeFromSupernode()
}
let markupNode: AvatarVideoNode
if let current = self.markupNode {
markupNode = current
} else {
markupNode = AvatarVideoNode(context: self.context)
self.containerNode.addSubnode(markupNode)
self.markupNode = markupNode
}
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
markupNode.updateVisibility(true)
} else if threadInfo == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil)
if videoContent.id != self.videoContent?.id {
@ -553,28 +591,51 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
self.videoContent = videoContent
self.videoNode = videoNode
let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
let maskPath: UIBezierPath
if isForum {
maskPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size), cornerRadius: avatarCornerRadius)
} else {
maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
}
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
videoNode.layer.mask = shape
self.containerNode.addSubnode(videoNode)
}
} else if let videoNode = self.videoNode {
} else {
if let markupNode = self.markupNode {
self.markupNode = nil
markupNode.removeFromSupernode()
}
if let videoNode = self.videoNode {
self.videoStartTimestamp = nil
self.videoContent = nil
self.videoNode = nil
videoNode.removeFromSupernode()
}
}
} else {
if let markupNode = self.markupNode {
self.markupNode = nil
markupNode.removeFromSupernode()
}
if let videoNode = self.videoNode {
self.videoStartTimestamp = nil
self.videoContent = nil
self.videoNode = nil
videoNode.removeFromSupernode()
}
} else if let videoNode = self.videoNode {
self.videoContent = nil
self.videoNode = nil
videoNode.removeFromSupernode()
self.containerNode.isGestureEnabled = false
}
if let markupNode = self.markupNode {
markupNode.frame = self.avatarNode.frame
markupNode.updateLayout(size: self.avatarNode.frame.size, cornerRadius: avatarCornerRadius, transition: .immediate)
}
if let videoNode = self.videoNode {
if self.canAttachVideo {
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
@ -730,6 +791,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
private let context: AccountContext
let avatarNode: AvatarNode
fileprivate var videoNode: UniversalVideoNode?
fileprivate var markupNode: AvatarVideoNode?
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
var item: PeerInfoAvatarListItem?
@ -799,8 +861,10 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
self.avatarNode.setPeer(context: self.context, theme: theme, peer: EnginePeer(peer), overrideImage: overrideImage, clipStyle: .none, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize))
var isForum = false
let avatarCornerRadius: CGFloat
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
isForum = true
avatarCornerRadius = floor(avatarSize * 0.25)
} else {
avatarCornerRadius = avatarSize / 2.0
@ -816,35 +880,63 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
let representations: [ImageRepresentationWithReference]
let videoRepresentations: [VideoRepresentationWithReference]
let immediateThumbnailData: Data?
var id: Int64
var videoId: Int64
let markup: TelegramMediaImage.EmojiMarkup?
switch item {
case .custom:
representations = []
videoRepresentations = []
immediateThumbnailData = nil
id = 0
videoId = 0
markup = nil
case let .topImage(topRepresentations, videoRepresentationsValue, immediateThumbnail):
representations = topRepresentations
videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail
id = peer.id.id._internalGetInt64Value()
videoId = peer.id.id._internalGetInt64Value()
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
id = id &+ resource.photoId
videoId = videoId &+ resource.photoId
}
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, _):
markup = nil
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _, markupValue):
representations = imageRepresentations
videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail
if case let .cloud(imageId, _, _) = reference {
id = imageId
videoId = imageId
} else {
id = peer.id.id._internalGetInt64Value()
videoId = peer.id.id._internalGetInt64Value()
}
markup = markupValue
}
if threadData == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
if let markup {
if let videoNode = self.videoNode {
self.videoContent = nil
self.videoStartTimestamp = nil
self.videoNode = nil
videoNode.removeFromSupernode()
}
let markupNode: AvatarVideoNode
if let current = self.markupNode {
markupNode = current
} else {
markupNode = AvatarVideoNode(context: self.context)
self.insertSubnode(markupNode, aboveSubnode: self.avatarNode)
self.markupNode = markupNode
}
markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0))
markupNode.updateVisibility(true)
} else if threadData == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) {
if let markupNode = self.markupNode {
self.markupNode = nil
markupNode.removeFromSupernode()
}
let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil)
let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil)
if videoContent.id != self.videoContent?.id {
self.videoNode?.removeFromSupernode()
@ -855,19 +947,30 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
self.videoContent = videoContent
self.videoNode = videoNode
let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
let maskPath: UIBezierPath
if isForum {
maskPath = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size), cornerRadius: avatarCornerRadius)
} else {
maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
}
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
videoNode.layer.mask = shape
self.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
}
} else if let videoNode = self.videoNode {
self.videoStartTimestamp = nil
self.videoContent = nil
self.videoNode = nil
videoNode.removeFromSupernode()
} else {
if let markupNode = self.markupNode {
self.markupNode = nil
markupNode.removeFromSupernode()
}
if let videoNode = self.videoNode {
self.videoStartTimestamp = nil
self.videoContent = nil
self.videoNode = nil
videoNode.removeFromSupernode()
}
}
} else if let videoNode = self.videoNode {
self.videoStartTimestamp = nil
@ -877,6 +980,11 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
videoNode.removeFromSupernode()
}
if let markupNode = self.markupNode {
markupNode.frame = self.avatarNode.frame
markupNode.updateLayout(size: self.avatarNode.frame.size, cornerRadius: avatarCornerRadius, transition: .immediate)
}
if let videoNode = self.videoNode {
if self.canAttachVideo {
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
@ -897,6 +1005,8 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
}
}
final class PeerInfoAvatarListNode: ASDisplayNode {
private let isSettings: Bool
let pinchSourceNode: PinchSourceContainerNode
@ -1018,6 +1128,8 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
if let currentItemNode = self.listContainerNode.currentItemNode, case .animated = transition {
if let _ = self.avatarContainerNode.videoNode {
} else if let _ = self.avatarContainerNode.markupNode {
} else if let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage {
let avatarCopyView = UIImageView()
avatarCopyView.image = unroundedImage

View File

@ -7092,10 +7092,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let isSettings = self.isSettings
self.updateAvatarDisposable.set((signal
|> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
let markup: UploadPeerPhotoMarkup? = nil
var markup: UploadPeerPhotoMarkup? = nil
if isSettings {
// let fileId = adjustments?.documentId
// let backgroundColors = adjustments?.colors as? [Int32]
if let fileId = adjustments?.documentId, let backgroundColors = adjustments?.colors as? [Int32], fileId != 0 {
if let packId = adjustments?.stickerPackId, let accessHash = adjustments?.stickerPackAccessHash, packId != 0 {
markup = .sticker(packReference: .id(id: packId, accessHash: accessHash), fileId: fileId, backgroundColors: backgroundColors)
} else {
markup = .emoji(fileId: fileId, backgroundColors: backgroundColors)
}
}
if case .fallback = mode {
return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, markup: markup, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)

View File

@ -73,7 +73,7 @@ private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
case visualMediaStoredState = 5
case cachedImageRecognizedContent = 6
case pendingInAppPurchaseState = 7
case translationState = 8
case translationState = 9
}
public struct ApplicationSpecificItemCacheCollectionId {

View File

@ -114,7 +114,17 @@ public func translateMessageIds(context: AccountContext, messageIds: [EngineMess
return context.account.postbox.transaction { transaction -> Signal<Void, NoError> in
var messageIdsToTranslate: [EngineMessage.Id] = []
for messageId in messageIds {
if let message = transaction.getMessage(messageId), !message.text.isEmpty, let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == toLang {
if let message = transaction.getMessage(messageId) {
if let replyAttribute = message.attributes.first(where: { $0 is ReplyMessageAttribute }) as? ReplyMessageAttribute, let replyMessage = message.associatedMessages[replyAttribute.messageId] {
if !replyMessage.text.isEmpty, let translation = replyMessage.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == toLang {
} else {
messageIdsToTranslate.append(replyMessage.id)
}
}
if !message.text.isEmpty, let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == toLang {
} else {
messageIdsToTranslate.append(messageId)
}
} else {
messageIdsToTranslate.append(messageId)
}
@ -151,7 +161,7 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
} else {
return .single(nil)
|> then(
context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId, threadId: nil), index: .upperBound, anchorIndex: .upperBound, count: 16, fixedCombinedReadStates: nil)
context.account.viewTracker.aroundMessageHistoryViewForLocation(.peer(peerId: peerId, threadId: nil), index: .upperBound, anchorIndex: .upperBound, count: 32, fixedCombinedReadStates: nil)
|> filter { messageHistoryView -> Bool in
return messageHistoryView.0.entries.count > 1
}
@ -168,7 +178,7 @@ public func chatTranslationState(context: AccountContext, peerId: EnginePeer.Id)
if message.text.count > 10 {
let text = String(message.text.prefix(100))
languageRecognizer.processString(text)
let hypotheses = languageRecognizer.languageHypotheses(withMaximum: 3)
let hypotheses = languageRecognizer.languageHypotheses(withMaximum: 4)
languageRecognizer.reset()
let filteredLanguages = hypotheses.filter { supportedTranslationLanguages.contains($0.key.rawValue) }.sorted(by: { $0.value > $1.value })