mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Merge commit 'b6e2e462641f50d2494f1bfd827f86158863b87f'
This commit is contained in:
commit
94b5a8fa56
22
Images.xcassets/Chat/Message/InlineVideoMute.imageset/Contents.json
vendored
Normal file
22
Images.xcassets/Chat/Message/InlineVideoMute.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Muted@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "Muted@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
BIN
Images.xcassets/Chat/Message/InlineVideoMute.imageset/Muted@2x.png
vendored
Normal file
BIN
Images.xcassets/Chat/Message/InlineVideoMute.imageset/Muted@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
Images.xcassets/Chat/Message/InlineVideoMute.imageset/Muted@3x.png
vendored
Normal file
BIN
Images.xcassets/Chat/Message/InlineVideoMute.imageset/Muted@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -99,8 +99,8 @@ public struct AutomaticMediaDownloadSettings: PreferencesEntry, Equatable {
|
||||
|
||||
public static var defaultSettings: AutomaticMediaDownloadSettings {
|
||||
let defaultCategory = AutomaticMediaDownloadCategories(
|
||||
photo: AutomaticMediaDownloadCategory(cellular: true, wifi: true, sizeLimit: 1 * 1024 * 1024),
|
||||
video: AutomaticMediaDownloadCategory(cellular: false, wifi: false, sizeLimit: 1 * 1024 * 1024),
|
||||
photo: AutomaticMediaDownloadCategory(cellular: true, wifi: true, sizeLimit: Int32.max),
|
||||
video: AutomaticMediaDownloadCategory(cellular: true, wifi: true, sizeLimit: 10 * 1024 * 1024),
|
||||
file: AutomaticMediaDownloadCategory(cellular: false, wifi: false, sizeLimit: 1 * 1024 * 1024),
|
||||
voiceMessage: AutomaticMediaDownloadCategory(cellular: true, wifi: true, sizeLimit: 1 * 1024 * 1024),
|
||||
videoMessage: AutomaticMediaDownloadCategory(cellular: true, wifi: true, sizeLimit: 4 * 1024 * 1024),
|
||||
|
@ -172,7 +172,7 @@ public final class CallController: ViewController {
|
||||
|
||||
Queue.mainQueue().after(0.5, {
|
||||
let window = strongSelf.window
|
||||
let controller = callRatingController(sharedContext: strongSelf.sharedContext, account: strongSelf.account, callId: callId, present: { [weak self] c, a in
|
||||
let controller = callRatingController(sharedContext: strongSelf.sharedContext, account: strongSelf.account, callId: callId, present: { c, a in
|
||||
if let window = window {
|
||||
c.presentationArguments = a
|
||||
window.present(c, on: .root, blockInteraction: false, completion: {})
|
||||
|
@ -147,7 +147,7 @@ private enum CallFeedbackControllerEntry: ItemListNodeEntry {
|
||||
case let .reasonsHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .reason(theme, reason, title, value):
|
||||
return ItemListSwitchItem(theme: theme, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
return ItemListSwitchItem(theme: theme, title: title, value: value, maximumNumberOfLines: 2, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleReason(reason, value)
|
||||
})
|
||||
case let .comment(theme, text, placeholder):
|
||||
|
@ -5,6 +5,7 @@ import SwiftSignalKit
|
||||
|
||||
final class ChatBubbleVideoDecoration: UniversalVideoDecoration {
|
||||
private let nativeSize: CGSize
|
||||
private let contentMode: InteractiveMediaNodeContentMode
|
||||
|
||||
let backgroundNode: ASDisplayNode? = nil
|
||||
let contentContainerNode: ASDisplayNode
|
||||
@ -14,11 +15,12 @@ final class ChatBubbleVideoDecoration: UniversalVideoDecoration {
|
||||
|
||||
private var validLayoutSize: CGSize?
|
||||
|
||||
init(cornerRadius: CGFloat, nativeSize: CGSize, backgroudColor: UIColor) {
|
||||
init(cornerRadius: CGFloat, nativeSize: CGSize, contentMode: InteractiveMediaNodeContentMode, backgroundColor: UIColor) {
|
||||
self.nativeSize = nativeSize
|
||||
self.contentMode = contentMode
|
||||
|
||||
self.contentContainerNode = ASDisplayNode()
|
||||
self.contentContainerNode.backgroundColor = backgroudColor
|
||||
self.contentContainerNode.backgroundColor = backgroundColor
|
||||
self.contentContainerNode.clipsToBounds = true
|
||||
self.contentContainerNode.cornerRadius = cornerRadius
|
||||
}
|
||||
@ -38,7 +40,13 @@ final class ChatBubbleVideoDecoration: UniversalVideoDecoration {
|
||||
if contentNode.supernode !== self.contentContainerNode {
|
||||
self.contentContainerNode.addSubnode(contentNode)
|
||||
if let size = self.validLayoutSize {
|
||||
var scaledSize = self.nativeSize.aspectFitted(size)
|
||||
var scaledSize: CGSize
|
||||
switch self.contentMode {
|
||||
case .aspectFit:
|
||||
scaledSize = self.nativeSize.aspectFitted(size)
|
||||
case .aspectFill:
|
||||
scaledSize = self.nativeSize.aspectFilled(size)
|
||||
}
|
||||
if abs(scaledSize.width - size.width) < 2.0 {
|
||||
scaledSize.width = size.width
|
||||
}
|
||||
@ -68,7 +76,13 @@ final class ChatBubbleVideoDecoration: UniversalVideoDecoration {
|
||||
}
|
||||
transition.updateFrame(node: self.contentContainerNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
if let contentNode = self.contentNode {
|
||||
var scaledSize = self.nativeSize.aspectFitted(size)
|
||||
var scaledSize: CGSize
|
||||
switch self.contentMode {
|
||||
case .aspectFit:
|
||||
scaledSize = self.nativeSize.aspectFitted(size)
|
||||
case .aspectFill:
|
||||
scaledSize = self.nativeSize.aspectFilled(size)
|
||||
}
|
||||
if abs(scaledSize.width - size.width) < 2.0 {
|
||||
scaledSize.width = size.width
|
||||
}
|
||||
|
@ -386,12 +386,12 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
contentInstantVideoSizeAndApply = (videoLayout, apply)
|
||||
} else if file.isVideo {
|
||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file)
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||
refineContentImageLayout = refineLayout
|
||||
} else if file.isSticker, let _ = file.dimensions {
|
||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file)
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||
refineContentImageLayout = refineLayout
|
||||
} else {
|
||||
@ -416,7 +416,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
} else if let image = media as? TelegramMediaImage {
|
||||
if !flags.contains(.preferMediaInline) {
|
||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: image)
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, message, image, automaticDownload, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, message, image, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||
refineContentImageLayout = refineLayout
|
||||
} else if let dimensions = largestImageRepresentation(image.representations)?.dimensions {
|
||||
@ -428,11 +428,11 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
}
|
||||
} else if let image = media as? TelegramMediaWebFile {
|
||||
let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: image)
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, message, image, automaticDownload, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, message, image, automaticDownload ? .full : .none, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||
refineContentImageLayout = refineLayout
|
||||
} else if let wallpaper = media as? WallpaperPreviewMedia {
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, message, wallpaper, true, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
let (_, initialImageWidth, refineLayout) = contentImageLayout(context, presentationData.theme.theme, presentationData.strings, message, wallpaper, .full, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants, contentMode)
|
||||
initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right
|
||||
refineContentImageLayout = refineLayout
|
||||
}
|
||||
|
@ -373,6 +373,13 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
var isInlinePlayableVideo = false
|
||||
for media in item.content.firstMessage.media {
|
||||
if let file = media as? TelegramMediaFile, file.isVideo, !file.isAnimated, isMediaStreamable(message: item.content.firstMessage, media: file) {
|
||||
isInlinePlayableVideo = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasAvatar {
|
||||
avatarInset = layoutConstants.avatarDiameter
|
||||
} else {
|
||||
@ -436,7 +443,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
tmpWidth -= 38.0
|
||||
}
|
||||
} else {
|
||||
tmpWidth = layoutConstants.bubble.maximumWidthFill.widthFor(baseWidth)
|
||||
tmpWidth = isInlinePlayableVideo ? baseWidth - 36.0 : layoutConstants.bubble.maximumWidthFill.widthFor(baseWidth)
|
||||
if needShareButton && tmpWidth + 32.0 > baseWidth {
|
||||
tmpWidth = baseWidth - 32.0
|
||||
}
|
||||
|
@ -9,14 +9,14 @@ enum ChatMessageInteractiveMediaBadgeShape: Equatable {
|
||||
|
||||
enum ChatMessageInteractiveMediaDownloadState: Equatable {
|
||||
case remote
|
||||
case fetching(progress: Float)
|
||||
case fetching(progress: Float?)
|
||||
case compactRemote
|
||||
case compactFetching(progress: Float)
|
||||
}
|
||||
|
||||
enum ChatMessageInteractiveMediaBadgeContent: Equatable {
|
||||
case text(inset: CGFloat, backgroundColor: UIColor, foregroundColor: UIColor, shape: ChatMessageInteractiveMediaBadgeShape, text: NSAttributedString)
|
||||
case mediaDownload(backgroundColor: UIColor, foregroundColor: UIColor, duration: String, size: String)
|
||||
case mediaDownload(backgroundColor: UIColor, foregroundColor: UIColor, duration: String, size: String?, muted: Bool, active: Bool)
|
||||
|
||||
static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool {
|
||||
switch lhs {
|
||||
@ -26,8 +26,8 @@ enum ChatMessageInteractiveMediaBadgeContent: Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .mediaDownload(lhsBackgroundColor, lhsForegroundColor, lhsDuration, lhsSize):
|
||||
if case let .mediaDownload(rhsBackgroundColor, rhsForegroundColor, rhsDuration, rhsSize) = rhs, lhsBackgroundColor.isEqual(rhsBackgroundColor), lhsForegroundColor.isEqual(rhsForegroundColor), lhsDuration == rhsDuration, lhsSize == rhsSize {
|
||||
case let .mediaDownload(lhsBackgroundColor, lhsForegroundColor, lhsDuration, lhsSize, lhsMuted, lhsActive):
|
||||
if case let .mediaDownload(rhsBackgroundColor, rhsForegroundColor, rhsDuration, rhsSize, rhsMuted, rhsActive) = rhs, lhsBackgroundColor.isEqual(rhsBackgroundColor), lhsForegroundColor.isEqual(rhsForegroundColor), lhsDuration == rhsDuration, lhsSize == rhsSize, lhsMuted == rhsMuted, lhsActive == rhsActive {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -39,27 +39,28 @@ enum ChatMessageInteractiveMediaBadgeContent: Equatable {
|
||||
private let font = Font.regular(11.0)
|
||||
private let boldFont = Font.semibold(11.0)
|
||||
|
||||
private final class ChatMessageInteractiveMediaBadgeParams: NSObject {
|
||||
let content: ChatMessageInteractiveMediaBadgeContent?
|
||||
|
||||
init(content: ChatMessageInteractiveMediaBadgeContent?) {
|
||||
self.content = content
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
|
||||
private var content: ChatMessageInteractiveMediaBadgeContent?
|
||||
var pressed: (() -> Void)?
|
||||
|
||||
private var content: ChatMessageInteractiveMediaBadgeContent?
|
||||
|
||||
private var mediaDownloadStatusNode: RadialStatusNode?
|
||||
private var mediaDownloadState: ChatMessageInteractiveMediaDownloadState?
|
||||
private var backgroundNodeColor: UIColor?
|
||||
private var foregroundColor: UIColor?
|
||||
|
||||
private let backgroundNode: ASImageNode
|
||||
private let durationNode: ASTextNode
|
||||
private var sizeNode: ASTextNode?
|
||||
private var iconNode: ASImageNode?
|
||||
private var mediaDownloadStatusNode: RadialStatusNode?
|
||||
|
||||
override init() {
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.durationNode = ASTextNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.contentMode = .topLeft
|
||||
self.contentsScale = UIScreenScale
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.durationNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -74,16 +75,6 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let contents = self.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID {
|
||||
let image = contents as! CGImage
|
||||
if CGRect(origin: CGPoint(), size: CGSize(width: CGFloat(image.width) / UIScreenScale, height: CGFloat(image.height) / UIScreenScale)).contains(point) {
|
||||
return self.view
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func update(theme: PresentationTheme, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, animated: Bool) {
|
||||
if self.content != content {
|
||||
self.content = content
|
||||
@ -110,7 +101,11 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
|
||||
state = .none
|
||||
}
|
||||
case let .fetching(progress):
|
||||
state = .cloudProgress(color: .white, strokeBackgroundColor: UIColor(white: 1.0, alpha: 0.3), lineWidth: 2.0, value: CGFloat(progress))
|
||||
var cloudProgress: CGFloat?
|
||||
if let progress = progress {
|
||||
cloudProgress = CGFloat(progress)
|
||||
}
|
||||
state = .cloudProgress(color: .white, strokeBackgroundColor: UIColor(white: 1.0, alpha: 0.3), lineWidth: 2.0 - UIScreenPixel, value: cloudProgress)
|
||||
case .compactRemote:
|
||||
state = .download(.white)
|
||||
isCompact = true
|
||||
@ -131,78 +126,97 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
|
||||
mediaDownloadStatusNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||
return ChatMessageInteractiveMediaBadgeParams(content: self.content)
|
||||
}
|
||||
var contentSize = CGSize()
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
||||
|
||||
@objc override public class func display(withParameters: Any?, isCancelled: () -> Bool) -> UIImage? {
|
||||
if let content = (withParameters as? ChatMessageInteractiveMediaBadgeParams)?.content {
|
||||
if let content = self.content {
|
||||
switch content {
|
||||
case let .text(inset, backgroundColor, foregroundColor, shape, text):
|
||||
if self.backgroundNodeColor != backgroundColor {
|
||||
self.backgroundNodeColor = backgroundColor
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(radius: 9.0, color: backgroundColor)
|
||||
}
|
||||
let convertedText = NSMutableAttributedString(string: text.string, attributes: [.font: font, .foregroundColor: foregroundColor])
|
||||
text.enumerateAttributes(in: NSRange(location: 0, length: text.length), options: []) { attributes, range, _ in
|
||||
if let _ = attributes[ChatTextInputAttributes.bold] {
|
||||
convertedText.addAttribute(.font, value: boldFont, range: range)
|
||||
}
|
||||
}
|
||||
let textRect = convertedText.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
let imageSize = CGSize(width: inset + ceil(textRect.size.width) + 10.0, height: 18.0)
|
||||
return generateImage(imageSize, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
switch shape {
|
||||
case .round:
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.height, height: size.height)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - size.height, y: 0.0), size: CGSize(width: size.height, height: size.height)))
|
||||
context.fill(CGRect(origin: CGPoint(x: size.height / 2.0, y: 0.0), size: CGSize(width: size.width - size.height, height: size.height)))
|
||||
case let .corners(radius):
|
||||
let diameter = radius * 2.0
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: diameter, height: diameter)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: size.height - diameter), size: CGSize(width: diameter, height: diameter)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - diameter, y: 0.0), size: CGSize(width: diameter, height: diameter)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - diameter, y: size.height - diameter), size: CGSize(width: diameter, height: diameter)))
|
||||
context.fill(CGRect(origin: CGPoint(x: 0.0, y: radius), size: CGSize(width: diameter, height: size.height - diameter)))
|
||||
context.fill(CGRect(origin: CGPoint(x: radius, y: 0.0), size: CGSize(width: size.width - diameter, height: size.height)))
|
||||
context.fill(CGRect(origin: CGPoint(x: size.width - diameter, y: radius), size: CGSize(width: diameter, height: size.height - diameter)))
|
||||
self.durationNode.attributedText = convertedText
|
||||
let durationSize = self.durationNode.measure(CGSize(width: 160.0, height: 160.0))
|
||||
self.durationNode.frame = CGRect(x: 7.0, y: 2.0, width: durationSize.width, height: durationSize.height)
|
||||
contentSize = CGSize(width: durationSize.width + 14.0, height: 18.0)
|
||||
case let .mediaDownload(backgroundColor, foregroundColor, duration, size, muted, active):
|
||||
if self.backgroundNodeColor != backgroundColor {
|
||||
self.backgroundNodeColor = backgroundColor
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(radius: 9.0, color: backgroundColor)
|
||||
}
|
||||
context.setBlendMode(.normal)
|
||||
UIGraphicsPushContext(context)
|
||||
convertedText.draw(at: CGPoint(x: inset + floor((size.width - inset - textRect.size.width) / 2.0) + textRect.origin.x, y: 2.0 + textRect.origin.y))
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
case let .mediaDownload(backgroundColor, foregroundColor, duration, size):
|
||||
|
||||
let durationString = NSMutableAttributedString(string: duration, attributes: [.font: font, .foregroundColor: foregroundColor])
|
||||
self.durationNode.attributedText = durationString
|
||||
|
||||
var sizeSize: CGSize = CGSize()
|
||||
if let size = size {
|
||||
let sizeNode: ASTextNode
|
||||
if let current = self.sizeNode {
|
||||
sizeNode = current
|
||||
} else {
|
||||
sizeNode = ASTextNode()
|
||||
self.sizeNode = sizeNode
|
||||
self.addSubnode(sizeNode)
|
||||
}
|
||||
|
||||
let sizeString = NSMutableAttributedString(string: size, attributes: [.font: font, .foregroundColor: foregroundColor])
|
||||
let durationRect = durationString.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
let sizeRect = sizeString.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||
let leftInset: CGFloat = 42.0
|
||||
let imageSize = CGSize(width: leftInset + max(ceil(durationRect.width), ceil(sizeRect.width)) + 10.0, height: 40.0)
|
||||
return generateImage(imageSize, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
sizeNode.attributedText = sizeString
|
||||
sizeSize = sizeNode.measure(CGSize(width: 160.0, height: 160.0))
|
||||
sizeNode.frame = CGRect(x: active ? 42.0 : 7.0, y: active ? 19.0 : 2.0, width: sizeSize.width, height: sizeSize.height)
|
||||
transition.updateAlpha(node: sizeNode, alpha: 1.0)
|
||||
} else if let sizeNode = self.sizeNode {
|
||||
transition.updateAlpha(node: sizeNode, alpha: 0.0)
|
||||
}
|
||||
|
||||
let radius: CGFloat = 12.0
|
||||
let diameter = radius * 2.0
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: diameter, height: diameter)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: size.height - diameter), size: CGSize(width: diameter, height: diameter)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - diameter, y: 0.0), size: CGSize(width: diameter, height: diameter)))
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - diameter, y: size.height - diameter), size: CGSize(width: diameter, height: diameter)))
|
||||
context.fill(CGRect(origin: CGPoint(x: 0.0, y: radius), size: CGSize(width: diameter, height: size.height - diameter)))
|
||||
context.fill(CGRect(origin: CGPoint(x: radius, y: 0.0), size: CGSize(width: size.width - diameter, height: size.height)))
|
||||
context.fill(CGRect(origin: CGPoint(x: size.width - diameter, y: radius), size: CGSize(width: diameter, height: size.height - diameter)))
|
||||
let durationSize = self.durationNode.measure(CGSize(width: 160.0, height: 160.0))
|
||||
if let statusNode = self.mediaDownloadStatusNode {
|
||||
transition.updateAlpha(node: statusNode, alpha: active ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
context.setBlendMode(.normal)
|
||||
UIGraphicsPushContext(context)
|
||||
durationString.draw(at: CGPoint(x: leftInset + durationRect.origin.x, y: 7.0 + durationRect.origin.y))
|
||||
sizeString.draw(at: CGPoint(x: leftInset + sizeRect.origin.x, y: 21.0 + sizeRect.origin.y))
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
self.durationNode.frame = CGRect(x: active ? 42.0 : 7.0, y: active ? 7.0 : 2.0, width: durationSize.width, height: durationSize.height)
|
||||
|
||||
if muted {
|
||||
let iconNode: ASImageNode
|
||||
if let current = self.iconNode {
|
||||
iconNode = current
|
||||
} else {
|
||||
iconNode = ASImageNode()
|
||||
self.iconNode = iconNode
|
||||
self.addSubnode(iconNode)
|
||||
}
|
||||
|
||||
if self.foregroundColor != foregroundColor {
|
||||
self.foregroundColor = foregroundColor
|
||||
iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/InlineVideoMute"), color: foregroundColor)
|
||||
}
|
||||
transition.updateAlpha(node: iconNode, alpha: 1.0)
|
||||
if let icon = iconNode.image {
|
||||
transition.updateFrame(node: iconNode, frame: CGRect(x: (active ? 42.0 : 7.0) + floor(durationSize.width) + 3.0, y: active ? 9.0 + UIScreenPixel : 4.0 + UIScreenPixel, width: icon.size.width, height: icon.size.height))
|
||||
}
|
||||
} else if let iconNode = self.iconNode {
|
||||
transition.updateAlpha(node: iconNode, alpha: 0.0)
|
||||
}
|
||||
|
||||
var contentWidth: CGFloat = max(sizeSize.width, durationSize.width + (muted ? 17.0 : 0.0)) + 14.0
|
||||
if active {
|
||||
contentWidth += 36.0
|
||||
}
|
||||
contentSize = CGSize(width: contentWidth, height: active ? 38.0 : 18.0)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(x: 0.0, y: 0.0, width: contentSize.width, height: contentSize.height))
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
return self.backgroundNode.frame.contains(point)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,12 @@ enum InteractiveMediaNodeActivateContent {
|
||||
case stream
|
||||
}
|
||||
|
||||
enum InteractiveMediaNodeAutodownloadMode {
|
||||
case none
|
||||
case prefetch
|
||||
case full
|
||||
}
|
||||
|
||||
final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
private let imageNode: TransformImageNode
|
||||
private var currentImageArguments: TransformImageArguments?
|
||||
@ -38,7 +44,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
private var media: Media?
|
||||
private var themeAndStrings: (PresentationTheme, PresentationStrings)?
|
||||
private var sizeCalculation: InteractiveMediaNodeSizeCalculation?
|
||||
private var automaticDownload: Bool?
|
||||
private var automaticDownload: InteractiveMediaNodeAutodownloadMode?
|
||||
private var automaticPlayback: Bool?
|
||||
|
||||
private let statusDisposable = MetaDisposable()
|
||||
@ -46,8 +52,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
private var fetchStatus: MediaResourceStatus?
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
|
||||
private let playerStatusDisposable = MetaDisposable()
|
||||
private var playerStatus: MediaPlayerStatus?
|
||||
|
||||
private var secretTimer: SwiftSignalKit.Timer?
|
||||
|
||||
var visibilityPromise = ValuePromise<ListViewItemNodeVisibility>(.none)
|
||||
var visibility: ListViewItemNodeVisibility = .none {
|
||||
didSet {
|
||||
if let videoNode = self.videoNode {
|
||||
@ -61,6 +71,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
videoNode.canAttachContent = false
|
||||
}
|
||||
}
|
||||
self.visibilityPromise.set(self.visibility)
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,6 +89,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
|
||||
deinit {
|
||||
self.statusDisposable.dispose()
|
||||
self.playerStatusDisposable.dispose()
|
||||
self.fetchDisposable.dispose()
|
||||
self.secretTimer?.invalidate()
|
||||
}
|
||||
@ -92,7 +104,13 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
|
||||
private func progressPressed(canActivate: Bool) {
|
||||
if let fetchStatus = self.fetchStatus {
|
||||
if canActivate, let state = self.statusNode?.state, case .play = state {
|
||||
var activateContent = false
|
||||
if let state = self.statusNode?.state, case .play = state {
|
||||
activateContent = true
|
||||
} else if (self.automaticPlayback ?? false) {
|
||||
activateContent = true
|
||||
}
|
||||
if canActivate, activateContent {
|
||||
switch fetchStatus {
|
||||
case .Remote, .Fetching:
|
||||
self.activateLocalContent(.stream)
|
||||
@ -145,7 +163,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: Bool, _ peerType: AutomaticMediaDownloadPeerType, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void))) {
|
||||
func asyncLayout() -> (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: AutomaticMediaDownloadPeerType, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void))) {
|
||||
let currentMessage = self.message
|
||||
let currentMedia = self.media
|
||||
let imageLayout = self.imageNode.asyncLayout()
|
||||
@ -179,6 +197,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
|
||||
var isInlinePlayableVideo = false
|
||||
|
||||
var maxDimensions = layoutConstants.image.maxDimensions
|
||||
|
||||
var unboundSize: CGSize
|
||||
if let image = media as? TelegramMediaImage, let dimensions = largestImageRepresentation(image.representations)?.dimensions {
|
||||
unboundSize = CGSize(width: floor(dimensions.width * 0.5), height: floor(dimensions.height * 0.5))
|
||||
@ -193,10 +213,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
unboundSize = CGSize(width: floor(dimensions.width * 0.5), height: floor(dimensions.height * 0.5))
|
||||
if file.isAnimated {
|
||||
unboundSize = unboundSize.aspectFilled(CGSize(width: 480.0, height: 480.0))
|
||||
} else if file.isVideo && automaticPlayback && !file.isAnimated, case let .constrained(constrainedSize) = sizeCalculation {
|
||||
maxDimensions = constrainedSize
|
||||
} else if file.isSticker {
|
||||
unboundSize = unboundSize.aspectFilled(CGSize(width: 162.0, height: 162.0))
|
||||
}
|
||||
isInlinePlayableVideo = file.isVideo && file.isAnimated && !isSecretMedia && automaticPlayback
|
||||
isInlinePlayableVideo = file.isVideo && !isSecretMedia && automaticPlayback
|
||||
} else if let image = media as? TelegramMediaWebFile, let dimensions = image.dimensions {
|
||||
unboundSize = CGSize(width: floor(dimensions.width * 0.5), height: floor(dimensions.height * 0.5))
|
||||
} else if let wallpaper = media as? WallpaperPreviewMedia {
|
||||
@ -230,7 +252,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
if isSecretMedia {
|
||||
maxWidth = 180.0
|
||||
} else {
|
||||
maxWidth = layoutConstants.image.maxDimensions.width
|
||||
maxWidth = maxDimensions.width
|
||||
}
|
||||
if isSecretMedia {
|
||||
let _ = PresentationResourcesChat.chatBubbleSecretMediaIcon(theme)
|
||||
@ -244,8 +266,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
if isSecretMedia {
|
||||
resultWidth = maxWidth
|
||||
} else {
|
||||
let maxFittedSize = nativeSize.aspectFitted (layoutConstants.image.maxDimensions)
|
||||
resultWidth = min(nativeSize.width, min(maxFittedSize.width, min(constrainedSize.width, layoutConstants.image.maxDimensions.width)))
|
||||
let maxFittedSize = nativeSize.aspectFitted(maxDimensions)
|
||||
resultWidth = min(nativeSize.width, min(maxFittedSize.width, min(constrainedSize.width, maxDimensions.width)))
|
||||
|
||||
resultWidth = max(resultWidth, layoutConstants.image.minDimensions.width)
|
||||
}
|
||||
@ -313,14 +335,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
return chatSecretPhoto(account: context.account, photoReference: .message(message: MessageReference(message), media: image))
|
||||
}
|
||||
} else {
|
||||
let tinyThumbnailData: TinyThumbnailData?
|
||||
if GlobalExperimentalSettings.enableTinyThumbnails {
|
||||
tinyThumbnailData = parseTinyThumbnail(message.text)
|
||||
} else {
|
||||
tinyThumbnailData = nil
|
||||
}
|
||||
updateImageSignal = { synchronousLoad in
|
||||
return chatMessagePhoto(postbox: context.account.postbox, photoReference: .message(message: MessageReference(message), media: image), synchronousLoad: synchronousLoad, tinyThumbnailData: tinyThumbnailData)
|
||||
return chatMessagePhoto(postbox: context.account.postbox, photoReference: .message(message: MessageReference(message), media: image), synchronousLoad: synchronousLoad)
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,7 +386,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
if file.isVideo && file.isAnimated && !isSecretMedia && automaticPlayback {
|
||||
if file.isVideo && !isSecretMedia && automaticPlayback {
|
||||
updateVideoFile = file
|
||||
if hasCurrentVideoNode {
|
||||
if let currentFile = currentMedia as? TelegramMediaFile, currentFile.resource is EmptyMediaResource {
|
||||
@ -507,6 +523,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
statusNode.frame = statusFrame
|
||||
}
|
||||
|
||||
var updatedPlayerStatusSignal: Signal<MediaPlayerStatus?, NoError>?
|
||||
if let replaceVideoNode = replaceVideoNode {
|
||||
if let videoNode = strongSelf.videoNode {
|
||||
videoNode.canAttachContent = false
|
||||
@ -517,11 +534,13 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
if replaceVideoNode, let updatedVideoFile = updateVideoFile {
|
||||
let mediaManager = context.sharedContext.mediaManager
|
||||
let cornerRadius: CGFloat = arguments.corners.topLeft.radius
|
||||
let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: ChatBubbleVideoDecoration(cornerRadius: cornerRadius, nativeSize: nativeSize, backgroudColor: arguments.emptyColor ?? .black), content: NativeVideoContent(id: .message(message.id, message.stableId, updatedVideoFile.fileId), fileReference: .message(message: MessageReference(message), media: updatedVideoFile), enableSound: false, fetchAutomatically: false), priority: .embedded)
|
||||
let videoNode = UniversalVideoNode(postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: ChatBubbleVideoDecoration(cornerRadius: cornerRadius, nativeSize: nativeSize, contentMode: contentMode, backgroundColor: arguments.emptyColor ?? .black), content: NativeVideoContent(id: .message(message.id, message.stableId, updatedVideoFile.fileId), fileReference: .message(message: MessageReference(message), media: updatedVideoFile), streamVideo: true, enableSound: false, fetchAutomatically: false), priority: .embedded)
|
||||
videoNode.isUserInteractionEnabled = false
|
||||
|
||||
strongSelf.videoNode = videoNode
|
||||
strongSelf.insertSubnode(videoNode, aboveSubnode: strongSelf.imageNode)
|
||||
|
||||
updatedPlayerStatusSignal = videoNode.status
|
||||
}
|
||||
}
|
||||
|
||||
@ -563,20 +582,79 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
}))
|
||||
}
|
||||
|
||||
if let updatedPlayerStatusSignal = updatedPlayerStatusSignal {
|
||||
strongSelf.playerStatusDisposable.set((updatedPlayerStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
|
||||
displayLinkDispatcher.dispatch {
|
||||
if let strongSelf = strongSelf {
|
||||
strongSelf.playerStatus = status
|
||||
strongSelf.updateFetchStatus()
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
if let updatedFetchControls = updatedFetchControls {
|
||||
let _ = strongSelf.fetchControls.swap(updatedFetchControls)
|
||||
if automaticDownload {
|
||||
if case .full = automaticDownload {
|
||||
if let _ = media as? TelegramMediaImage {
|
||||
updatedFetchControls.fetch(false)
|
||||
} else if let image = media as? TelegramMediaWebFile {
|
||||
strongSelf.fetchDisposable.set(chatMessageWebFileInteractiveFetched(account: context.account, image: image).start())
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if automaticPlayback || !file.isAnimated {
|
||||
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: false).start())
|
||||
let fetchSignal = messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: false)
|
||||
if !file.isAnimated {
|
||||
let visibilityAwareFetchSignal = strongSelf.visibilityPromise.get()
|
||||
|> mapToSignal { visibility -> Signal<Void, NoError> in
|
||||
switch visibility {
|
||||
case .visible, .nearlyVisible:
|
||||
return fetchSignal
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
case .none:
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
strongSelf.fetchDisposable.set(visibilityAwareFetchSignal.start())
|
||||
} else {
|
||||
strongSelf.fetchDisposable.set(fetchSignal.start())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if previousAutomaticDownload != automaticDownload, automaticDownload {
|
||||
} else if case .prefetch = automaticDownload {
|
||||
if let file = media as? TelegramMediaFile, let fileSize = file.size {
|
||||
let fetchHeadRange: Range<Int> = 0 ..< 2 * 1024 * 1024
|
||||
let fetchTailRange: Range<Int> = fileSize - 64 * 1024 ..< Int(Int32.max)
|
||||
|
||||
let fetchHeadSignal = fetchedMediaResource(postbox: context.account.postbox, reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), range: (fetchHeadRange, .default), statsCategory: statsCategoryForFileWithAttributes(file.attributes))
|
||||
|> ignoreValues
|
||||
|> `catch` { _ -> Signal<Never, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|
||||
let fetchTailSignal = fetchedMediaResource(postbox: context.account.postbox, reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), range: (fetchTailRange, .default), statsCategory: statsCategoryForFileWithAttributes(file.attributes))
|
||||
|> ignoreValues
|
||||
|> `catch` { _ -> Signal<Never, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|
||||
let visibilityAwareFetchSignal = strongSelf.visibilityPromise.get()
|
||||
|> mapToSignal { visibility -> Signal<Void, NoError> in
|
||||
switch visibility {
|
||||
case .visible, .nearlyVisible:
|
||||
return combineLatest(fetchHeadSignal, fetchTailSignal)
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
case .none:
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
strongSelf.fetchDisposable.set(visibilityAwareFetchSignal.start())
|
||||
}
|
||||
}
|
||||
} else if previousAutomaticDownload != automaticDownload, case .full = automaticDownload {
|
||||
strongSelf.fetchControls.with({ $0 })?.fetch(false)
|
||||
}
|
||||
|
||||
@ -636,7 +714,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
case .Remote, .Fetching:
|
||||
if let webpage = webpage, let automaticDownload = self.automaticDownload, automaticDownload, case let .Loaded(content) = webpage.content {
|
||||
if let webpage = webpage, let automaticDownload = self.automaticDownload, case .full = automaticDownload, case let .Loaded(content) = webpage.content {
|
||||
if content.type == "telegram_background" {
|
||||
progressRequired = true
|
||||
} else if content.embedUrl != nil {
|
||||
@ -698,6 +776,15 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: string)
|
||||
}
|
||||
if let fetchStatus = self.fetchStatus {
|
||||
var position: Int32?
|
||||
if let timestamp = self.playerStatus?.timestamp {
|
||||
position = Int32(timestamp)
|
||||
}
|
||||
var active = false
|
||||
if let status = self.playerStatus?.status, case .buffering = status {
|
||||
active = true
|
||||
}
|
||||
|
||||
switch fetchStatus {
|
||||
case let .Fetching(_, progress):
|
||||
let adjustedProgress = max(progress, 0.027)
|
||||
@ -716,11 +803,14 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
if let size = file.size {
|
||||
if let duration = file.duration, !message.flags.contains(.Unsent) {
|
||||
if isMediaStreamable(message: message, media: file) {
|
||||
let durationString = stringForDuration(duration)
|
||||
let durationString = stringForDuration(duration, position: position)
|
||||
let sizeString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true)) / \(dataSizeString(size, forceDecimal: true))"
|
||||
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: sizeString)
|
||||
mediaDownloadState = .fetching(progress: progress)
|
||||
state = .play(bubbleTheme.mediaOverlayControlForegroundColor)
|
||||
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: active ? sizeString : nil, muted: automaticPlayback, active: active)
|
||||
mediaDownloadState = .fetching(progress: automaticPlayback ? nil : progress)
|
||||
if self.playerStatus?.status == .playing {
|
||||
mediaDownloadState = nil
|
||||
}
|
||||
state = automaticPlayback ? .none : .play(bubbleTheme.mediaOverlayControlForegroundColor)
|
||||
} else {
|
||||
badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true)) / \(dataSizeString(size, forceDecimal: true))"))
|
||||
}
|
||||
@ -739,12 +829,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
}
|
||||
} else {
|
||||
if let duration = file.duration, !file.isAnimated {
|
||||
let durationString = stringForDuration(duration)
|
||||
let durationString = stringForDuration(duration, position: position)
|
||||
badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let webpage = webpage, let automaticDownload = self.automaticDownload, automaticDownload, case let .Loaded(content) = webpage.content, content.type != "telegram_background" {
|
||||
} else if let webpage = webpage, let automaticDownload = self.automaticDownload, case .full = automaticDownload, case let .Loaded(content) = webpage.content, content.type != "telegram_background" {
|
||||
state = .play(bubbleTheme.mediaOverlayControlForegroundColor)
|
||||
}
|
||||
case .Local:
|
||||
@ -760,8 +850,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
} else if isSecretMedia, let secretProgressIcon = secretProgressIcon {
|
||||
state = .customIcon(secretProgressIcon)
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
let isInlinePlayableVideo = file.isVideo && file.isAnimated && !isSecretMedia && automaticPlayback
|
||||
|
||||
let isInlinePlayableVideo = file.isVideo && !isSecretMedia && automaticPlayback
|
||||
if !isInlinePlayableVideo && file.isVideo {
|
||||
state = .play(bubbleTheme.mediaOverlayControlForegroundColor)
|
||||
} else {
|
||||
@ -775,17 +864,17 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
if let file = media as? TelegramMediaFile, let duration = file.duration, !file.isAnimated {
|
||||
let durationString = stringForDuration(duration)
|
||||
badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString))
|
||||
let durationString = stringForDuration(duration, position: position)
|
||||
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: nil, muted: true, active: false)
|
||||
}
|
||||
case .Remote:
|
||||
state = .download(bubbleTheme.mediaOverlayControlForegroundColor)
|
||||
if let file = self.media as? TelegramMediaFile, !file.isAnimated {
|
||||
let durationString = stringForDuration(file.duration ?? 0)
|
||||
let durationString = stringForDuration(file.duration ?? 0, position: position)
|
||||
if case .constrained = sizeCalculation {
|
||||
if isMediaStreamable(message: message, media: file) {
|
||||
state = .play(bubbleTheme.mediaOverlayControlForegroundColor)
|
||||
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: dataSizeString(file.size ?? 0))
|
||||
state = automaticPlayback ? .none : .play(bubbleTheme.mediaOverlayControlForegroundColor)
|
||||
badgeContent = .mediaDownload(backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, duration: durationString, size: dataSizeString(file.size ?? 0), muted: automaticPlayback, active: true)
|
||||
mediaDownloadState = .remote
|
||||
} else {
|
||||
badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString))
|
||||
@ -799,7 +888,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
badgeContent = .text(inset: 0.0, backgroundColor: bubbleTheme.mediaDateAndStatusFillColor, foregroundColor: bubbleTheme.mediaDateAndStatusTextColor, shape: .round, text: NSAttributedString(string: durationString))
|
||||
}
|
||||
}
|
||||
} else if let webpage = webpage, let automaticDownload = self.automaticDownload, automaticDownload, case let .Loaded(content) = webpage.content, content.type != "telegram_background" {
|
||||
} else if let webpage = webpage, let automaticDownload = self.automaticDownload, case .full = automaticDownload, case let .Loaded(content) = webpage.content, content.type != "telegram_background" {
|
||||
state = .play(bubbleTheme.mediaOverlayControlForegroundColor)
|
||||
}
|
||||
}
|
||||
@ -845,7 +934,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
self.badgeNode = badgeNode
|
||||
self.addSubnode(badgeNode)
|
||||
}
|
||||
self.badgeNode?.update(theme: theme, content: badgeContent, mediaDownloadState: mediaDownloadState, animated: false)
|
||||
self.badgeNode?.update(theme: theme, content: badgeContent, mediaDownloadState: mediaDownloadState, animated: true)
|
||||
} else if let badgeNode = self.badgeNode {
|
||||
self.badgeNode = nil
|
||||
badgeNode.removeFromSupernode()
|
||||
@ -866,12 +955,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: Bool, _ peerType: AutomaticMediaDownloadPeerType, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode))) {
|
||||
static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: AutomaticMediaDownloadPeerType, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> ChatMessageInteractiveMediaNode))) {
|
||||
let currentAsyncLayout = node?.asyncLayout()
|
||||
|
||||
return { context, theme, strings, message, media, automaticDownload, peerType, automaticPlayback, sizeCalculation, layoutConstants, contentMode in
|
||||
var imageNode: ChatMessageInteractiveMediaNode
|
||||
var imageLayout: (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: Bool, _ peerType: AutomaticMediaDownloadPeerType, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void)))
|
||||
var imageLayout: (_ context: AccountContext, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: AutomaticMediaDownloadPeerType, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition, Bool) -> Void)))
|
||||
|
||||
if let node = node, let currentAsyncLayout = currentAsyncLayout {
|
||||
imageNode = node
|
||||
|
@ -50,14 +50,32 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
return { item, layoutConstants, preparePosition, selection, constrainedSize in
|
||||
var selectedMedia: Media?
|
||||
var automaticDownload: Bool = false
|
||||
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
|
||||
var automaticPlayback: Bool = false
|
||||
var contentMode: InteractiveMediaNodeContentMode = .aspectFit
|
||||
|
||||
for media in item.message.media {
|
||||
if let telegramImage = media as? TelegramMediaImage {
|
||||
selectedMedia = telegramImage
|
||||
automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramImage)
|
||||
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramImage) {
|
||||
automaticDownload = .full
|
||||
}
|
||||
} else if let telegramFile = media as? TelegramMediaFile {
|
||||
selectedMedia = telegramFile
|
||||
automaticDownload = shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramFile)
|
||||
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, media: telegramFile) {
|
||||
automaticDownload = .full
|
||||
}
|
||||
|
||||
if telegramFile.isAnimated {
|
||||
automaticPlayback = item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs
|
||||
} else {
|
||||
if case .full = automaticDownload {
|
||||
automaticPlayback = true
|
||||
contentMode = .aspectFill
|
||||
} else {
|
||||
automaticDownload = .prefetch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,10 +104,10 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
sizeCalculation = .unconstrained
|
||||
}
|
||||
|
||||
let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.context, item.presentationData.theme.theme, item.presentationData.strings, item.message, selectedMedia!, automaticDownload, item.associatedData.automaticDownloadPeerType, item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs, sizeCalculation, layoutConstants, .aspectFit)
|
||||
let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.context, item.presentationData.theme.theme, item.presentationData.strings, item.message, selectedMedia!, automaticDownload, item.associatedData.automaticDownloadPeerType, automaticPlayback, sizeCalculation, layoutConstants, contentMode)
|
||||
|
||||
var forceFullCorners = false
|
||||
if let media = selectedMedia as? TelegramMediaFile, media.isAnimated {
|
||||
if let media = selectedMedia as? TelegramMediaFile, media.isVideo || media.isAnimated {
|
||||
forceFullCorners = true
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
||||
}
|
||||
}
|
||||
return -1
|
||||
//return Int32(bufferPointer)
|
||||
return Int32(bufferPointer)
|
||||
}
|
||||
|
||||
private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whence: Int32) -> Int64 {
|
||||
@ -396,7 +396,7 @@ func streamingVideoThumbnail(postbox: Postbox, fileReference: FileMediaReference
|
||||
let impl = FetchVideoThumbnailSourceThreadImpl()
|
||||
let thread = Thread(target: impl, selector: #selector(impl.entryPoint), object: nil)
|
||||
thread.name = "streamingVideoThumbnail"
|
||||
//impl.perform(#selector(impl.fetch(_:)), on: thread, with: FetchVideoThumbnailSourceParameters(), waitUntilDone: false)
|
||||
impl.perform(#selector(impl.fetch(_:)), on: thread, with: FetchVideoThumbnailSourceParameters(), waitUntilDone: false)
|
||||
thread.start()
|
||||
|
||||
return ActionDisposable {
|
||||
|
@ -3,7 +3,5 @@ import Foundation
|
||||
public struct GlobalExperimentalSettings {
|
||||
public static var isAppStoreBuild: Bool = false
|
||||
public static var enableFeed: Bool = false
|
||||
public static var enableTinyThumbnails: Bool = false
|
||||
public static var forceTinyThumbnailsPreview: Bool = false
|
||||
public static var animatedStickers: Bool = false
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ func imageHasTransparency(_ cgImage: CGImage) -> Bool {
|
||||
transparentCount += histogramBins[alphaBinIndex][i]
|
||||
}
|
||||
let totalCount = opaqueCount + transparentCount
|
||||
return Double(transparentCount) / Double(totalCount) > 0.02
|
||||
return Double(transparentCount) / Double(totalCount) > 0.05
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -15,17 +15,19 @@ class ItemListSwitchItem: ListViewItem, ItemListItem {
|
||||
let type: ItemListSwitchItemNodeType
|
||||
let enableInteractiveChanges: Bool
|
||||
let enabled: Bool
|
||||
let maximumNumberOfLines: Int
|
||||
let sectionId: ItemListSectionId
|
||||
let style: ItemListStyle
|
||||
let updated: (Bool) -> Void
|
||||
|
||||
init(theme: PresentationTheme, title: String, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void) {
|
||||
init(theme: PresentationTheme, title: String, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, maximumNumberOfLines: Int = 1, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.value = value
|
||||
self.type = type
|
||||
self.enableInteractiveChanges = enableInteractiveChanges
|
||||
self.enabled = enabled
|
||||
self.maximumNumberOfLines = maximumNumberOfLines
|
||||
self.sectionId = sectionId
|
||||
self.style = style
|
||||
self.updated = updated
|
||||
@ -154,7 +156,7 @@ class ItemListSwitchItemNode: ListViewItemNode {
|
||||
var currentDisabledOverlayNode = self.disabledOverlayNode
|
||||
|
||||
return { item, params, neighbors in
|
||||
let contentSize: CGSize
|
||||
var contentSize: CGSize
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
let itemBackgroundColor: UIColor
|
||||
@ -179,7 +181,9 @@ class ItemListSwitchItemNode: ListViewItemNode {
|
||||
insets = itemListNeighborsGroupedInsets(neighbors)
|
||||
}
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 80.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: item.maximumNumberOfLines, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 80.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
contentSize.height = max(contentSize.height, titleLayout.size.height + 22.0)
|
||||
|
||||
if !item.enabled {
|
||||
if currentDisabledOverlayNode == nil {
|
||||
|
@ -304,12 +304,6 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa
|
||||
}
|
||||
|
||||
var text = caption ?? ""
|
||||
if text.isEmpty && GlobalExperimentalSettings.enableTinyThumbnails {
|
||||
if let tinyThumbnail = compressTinyThumbnail(scaledImage) {
|
||||
text = serializeTinyThumbnail(tinyThumbnail)
|
||||
}
|
||||
}
|
||||
|
||||
messages.append(.message(text: text, attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId))
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,11 @@ enum MediaPlayerActionAtEnd {
|
||||
case stop
|
||||
}
|
||||
|
||||
enum MediaPlayerPlayOnceWithSoundActionAtEnd {
|
||||
case loopDisablingSound
|
||||
case stop
|
||||
}
|
||||
|
||||
private final class MediaPlayerAudioRendererContext {
|
||||
let renderer: MediaPlayerAudioRenderer
|
||||
var requestedFrames = false
|
||||
|
@ -257,13 +257,19 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
self.player.seek(timestamp: timestamp)
|
||||
}
|
||||
|
||||
func playOnceWithSound(playAndRecord: Bool) {
|
||||
func playOnceWithSound(playAndRecord: Bool, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
self.player.actionAtEnd = .loopDisablingSound({ [weak self] in
|
||||
let action = { [weak self] in
|
||||
Queue.mainQueue().async {
|
||||
self?.performActionAtEnd()
|
||||
}
|
||||
})
|
||||
}
|
||||
switch actionAtEnd {
|
||||
case .loopDisablingSound:
|
||||
self.player.actionAtEnd = .loopDisablingSound(action)
|
||||
case .stop:
|
||||
self.player.actionAtEnd = .action(action)
|
||||
}
|
||||
self.player.playOnceWithSound(playAndRecord: playAndRecord)
|
||||
}
|
||||
|
||||
@ -276,8 +282,19 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
self.player.setBaseRate(baseRate)
|
||||
}
|
||||
|
||||
func continuePlayingWithoutSound() {
|
||||
func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
let action = { [weak self] in
|
||||
Queue.mainQueue().async {
|
||||
self?.performActionAtEnd()
|
||||
}
|
||||
}
|
||||
switch actionAtEnd {
|
||||
case .loopDisablingSound:
|
||||
self.player.actionAtEnd = .loopDisablingSound(action)
|
||||
case .stop:
|
||||
self.player.actionAtEnd = .action(action)
|
||||
}
|
||||
self.player.continuePlayingWithoutSound()
|
||||
}
|
||||
|
||||
|
@ -634,14 +634,14 @@ func rawMessagePhoto(postbox: Postbox, photoReference: ImageMediaReference) -> S
|
||||
}
|
||||
}
|
||||
|
||||
public func chatMessagePhoto(postbox: Postbox, photoReference: ImageMediaReference, synchronousLoad: Bool = false, tinyThumbnailData: TinyThumbnailData? = nil) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
return chatMessagePhotoInternal(photoData: chatMessagePhotoDatas(postbox: postbox, photoReference: photoReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad), synchronousLoad: synchronousLoad, tinyThumbnailData: tinyThumbnailData)
|
||||
public func chatMessagePhoto(postbox: Postbox, photoReference: ImageMediaReference, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
return chatMessagePhotoInternal(photoData: chatMessagePhotoDatas(postbox: postbox, photoReference: photoReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad), synchronousLoad: synchronousLoad)
|
||||
|> map { _, generate in
|
||||
return generate
|
||||
}
|
||||
}
|
||||
|
||||
public func chatMessagePhotoInternal(photoData: Signal<(Data?, Data?, Bool), NoError>, synchronousLoad: Bool = false, tinyThumbnailData: TinyThumbnailData? = nil) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> {
|
||||
public func chatMessagePhotoInternal(photoData: Signal<(Data?, Data?, Bool), NoError>, synchronousLoad: Bool = false) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> {
|
||||
return photoData
|
||||
|> map { (thumbnailData, fullSizeData, fullSizeComplete) in
|
||||
return ({
|
||||
@ -686,21 +686,6 @@ public func chatMessagePhotoInternal(photoData: Signal<(Data?, Data?, Bool), NoE
|
||||
thumbnailImage = image
|
||||
}
|
||||
|
||||
if GlobalExperimentalSettings.forceTinyThumbnailsPreview {
|
||||
if let fullSizeImageValue = fullSizeImage {
|
||||
if let data = compressTinyThumbnail(UIImage(cgImage: fullSizeImageValue)) {
|
||||
if let result = decompressTinyThumbnail(data: data) {
|
||||
fullSizeImage = nil
|
||||
thumbnailImage = result.cgImage
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let tinyThumbnailData = tinyThumbnailData {
|
||||
if let result = decompressTinyThumbnail(data: tinyThumbnailData) {
|
||||
thumbnailImage = result.cgImage
|
||||
}
|
||||
}
|
||||
|
||||
var blurredThumbnailImage: UIImage?
|
||||
if let thumbnailImage = thumbnailImage {
|
||||
let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height)
|
||||
|
@ -305,13 +305,13 @@ private final class PlatformVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
self.player.seek(to: CMTime(seconds: timestamp, preferredTimescale: 30))
|
||||
}
|
||||
|
||||
func playOnceWithSound(playAndRecord: Bool) {
|
||||
func playOnceWithSound(playAndRecord: Bool, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) {
|
||||
}
|
||||
|
||||
func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) {
|
||||
}
|
||||
|
||||
func continuePlayingWithoutSound() {
|
||||
func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) {
|
||||
}
|
||||
|
||||
func setBaseRate(_ baseRate: Double) {
|
||||
|
@ -446,5 +446,5 @@ public func defaultPresentationData() -> PresentationData {
|
||||
let nameSortOrder = currentPersonNameSortOrder()
|
||||
|
||||
let themeSettings = PresentationThemeSettings.defaultSettings
|
||||
return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, chatWallpaper: .builtin, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
||||
return PresentationData(strings: defaultPresentationStrings, theme: defaultPresentationTheme, chatWallpaper: .builtin(WallpaperSettings()), volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations)
|
||||
}
|
||||
|
@ -452,10 +452,11 @@ public func bubbleColorComponents(theme: PresentationTheme, incoming: Bool, wall
|
||||
}
|
||||
|
||||
public func bubbleVariableColor(variableColor: PresentationThemeVariableColor, wallpaper: TelegramWallpaper) -> UIColor {
|
||||
if wallpaper != .builtin && wallpaper != .color(0xffffff) {
|
||||
return variableColor.withWallpaper
|
||||
} else {
|
||||
switch wallpaper {
|
||||
case .builtin, .color(0xffffff):
|
||||
return variableColor.withoutWallpaper
|
||||
default:
|
||||
return variableColor.withWallpaper
|
||||
}
|
||||
}
|
||||
|
||||
@ -649,10 +650,11 @@ public func serviceMessageColorComponents(theme: PresentationTheme, wallpaper: T
|
||||
}
|
||||
|
||||
public func serviceMessageColorComponents(chatTheme: PresentationThemeChat, wallpaper: TelegramWallpaper) -> PresentationThemeServiceMessageColorComponents {
|
||||
if wallpaper != .builtin && wallpaper != .color(0xffffff) {
|
||||
return chatTheme.serviceMessage.components.withCustomWallpaper
|
||||
} else {
|
||||
switch wallpaper {
|
||||
case .builtin, .color(0xffffff):
|
||||
return chatTheme.serviceMessage.components.withDefaultWallpaper
|
||||
default:
|
||||
return chatTheme.serviceMessage.components.withCustomWallpaper
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
||||
}
|
||||
|
||||
public static var defaultSettings: PresentationThemeSettings {
|
||||
return PresentationThemeSettings(chatWallpaper: .builtin, theme: .builtin(.dayClassic), themeAccentColor: nil, themeSpecificChatWallpapers: [:], fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .none, theme: .nightAccent), disableAnimations: true)
|
||||
return PresentationThemeSettings(chatWallpaper: .builtin(WallpaperSettings()), theme: .builtin(.dayClassic), themeAccentColor: nil, themeSpecificChatWallpapers: [:], fontSize: .regular, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting(trigger: .none, theme: .nightAccent), disableAnimations: true)
|
||||
}
|
||||
|
||||
public init(chatWallpaper: TelegramWallpaper, theme: PresentationThemeReference, themeAccentColor: Int32?, themeSpecificChatWallpapers: Dictionary<Int64, TelegramWallpaper>, fontSize: PresentationFontSize, automaticThemeSwitchSetting: AutomaticThemeSwitchSetting, disableAnimations: Bool) {
|
||||
@ -213,7 +213,7 @@ public struct PresentationThemeSettings: PreferencesEntry {
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.chatWallpaper = (decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper) ?? .builtin
|
||||
self.chatWallpaper = (decoder.decodeObjectForKey("w", decoder: { TelegramWallpaper(decoder: $0) }) as? TelegramWallpaper) ?? .builtin(WallpaperSettings())
|
||||
self.theme = decoder.decodeObjectForKey("t", decoder: { PresentationThemeReference(decoder: $0) }) as! PresentationThemeReference
|
||||
self.themeAccentColor = decoder.decodeOptionalInt32ForKey("themeAccentColor")
|
||||
self.themeSpecificChatWallpapers = decoder.decodeObjectDictionaryForKey("themeSpecificChatWallpapers", keyDecoder: { decoder in
|
||||
|
@ -214,7 +214,7 @@ private final class RadialCloudProgressContentCancelNode: ASDisplayNode {
|
||||
if let parameters = parameters as? RadialCloudProgressContentCancelNodeParameters {
|
||||
let size: CGFloat = 8.0
|
||||
context.setFillColor(parameters.color.cgColor)
|
||||
let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: floor((bounds.size.width - size) / 2.0), y: floor((bounds.size.height - size) / 2.0)), size: CGSize(width: size, height: size)), cornerRadius: 1.0)
|
||||
let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: floor((bounds.size.width - size) / 2.0), y: floor((bounds.size.height - size) / 2.0)), size: CGSize(width: size, height: size)), cornerRadius: 2.0)
|
||||
path.fill()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
import Foundation
|
||||
|
||||
func stringForDuration(_ duration: Int32) -> String {
|
||||
func stringForDuration(_ duration: Int32, position: Int32? = nil) -> String {
|
||||
var duration = duration
|
||||
if let position = position {
|
||||
duration = duration - position
|
||||
}
|
||||
let hours = duration / 3600
|
||||
let minutes = duration / 60 % 60
|
||||
let seconds = duration % 60
|
||||
@ -14,3 +18,4 @@ func stringForDuration(_ duration: Int32) -> String {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -227,13 +227,13 @@ private final class SystemVideoContentNode: ASDisplayNode, UniversalVideoContent
|
||||
//self.playerView.seek(toPosition: timestamp)
|
||||
}
|
||||
|
||||
func playOnceWithSound(playAndRecord: Bool) {
|
||||
func playOnceWithSound(playAndRecord: Bool, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) {
|
||||
}
|
||||
|
||||
func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) {
|
||||
}
|
||||
|
||||
func continuePlayingWithoutSound() {
|
||||
func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) {
|
||||
}
|
||||
|
||||
func setBaseRate(_ baseRate: Double) {
|
||||
|
@ -195,7 +195,7 @@ final class ThemeGridController: ViewController {
|
||||
for wallpaper in wallpapers {
|
||||
if wallpaper == strongSelf.presentationData.chatWallpaper {
|
||||
let _ = (updatePresentationThemeSettingsInteractively(accountManager: strongSelf.context.sharedContext.accountManager, { current in
|
||||
var fallbackWallpaper: TelegramWallpaper = .builtin
|
||||
var fallbackWallpaper: TelegramWallpaper = .builtin(WallpaperSettings())
|
||||
if case let .builtin(theme) = current.theme {
|
||||
switch theme {
|
||||
case .day:
|
||||
@ -205,7 +205,7 @@ final class ThemeGridController: ViewController {
|
||||
case .nightAccent:
|
||||
fallbackWallpaper = .color(0x18222d)
|
||||
default:
|
||||
fallbackWallpaper = .builtin
|
||||
fallbackWallpaper = .builtin(WallpaperSettings())
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,10 +278,10 @@ final class ThemeGridController: ViewController {
|
||||
case .nightAccent:
|
||||
wallpaper = .color(0x18222d)
|
||||
default:
|
||||
wallpaper = .builtin
|
||||
wallpaper = .builtin(WallpaperSettings())
|
||||
}
|
||||
} else {
|
||||
wallpaper = .builtin
|
||||
wallpaper = .builtin(WallpaperSettings())
|
||||
}
|
||||
return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, themeSpecificChatWallpapers: [:], fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||
})
|
||||
@ -445,10 +445,6 @@ final class ThemeGridController: ViewController {
|
||||
themeSpecificChatWallpapers[current.theme.index] = wallpaper
|
||||
return PresentationThemeSettings(chatWallpaper: wallpaper, theme: current.theme, themeAccentColor: current.themeAccentColor, themeSpecificChatWallpapers: themeSpecificChatWallpapers, fontSize: current.fontSize, automaticThemeSwitchSetting: current.automaticThemeSwitchSetting, disableAnimations: current.disableAnimations)
|
||||
})).start()
|
||||
|
||||
if let strongSelf = self, case .file = wallpaper {
|
||||
strongSelf.controllerNode.updateWallpapers()
|
||||
}
|
||||
}
|
||||
|
||||
let apply: () -> Void = {
|
||||
|
@ -156,7 +156,6 @@ private func selectedWallpapers(entries: [ThemeGridControllerEntry]?, state: The
|
||||
guard let entries = entries, state.editing else {
|
||||
return []
|
||||
}
|
||||
|
||||
var wallpapers: [TelegramWallpaper] = []
|
||||
for entry in entries {
|
||||
if case let .file(file) = entry.wallpaper {
|
||||
|
@ -288,7 +288,7 @@ public func themeSettingsController(context: AccountContext) -> ViewController {
|
||||
case 3:
|
||||
wallpaper = .color(0x18222d)
|
||||
default:
|
||||
wallpaper = .builtin
|
||||
wallpaper = .builtin(WallpaperSettings())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -459,6 +459,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
videoNode.playOnceWithSound(playAndRecord: false, actionAtEnd: .stop)
|
||||
self._ready.set(videoNode.ready)
|
||||
}
|
||||
|
||||
@ -698,6 +699,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
})
|
||||
pictureInPictureNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
videoNode.continuePlayingWithoutSound()
|
||||
}
|
||||
|
||||
func animateOut(toOverlay node: ASDisplayNode, completion: @escaping () -> Void) {
|
||||
|
@ -17,9 +17,9 @@ protocol UniversalVideoContentNode: class {
|
||||
func togglePlayPause()
|
||||
func setSoundEnabled(_ value: Bool)
|
||||
func seek(_ timestamp: Double)
|
||||
func playOnceWithSound(playAndRecord: Bool)
|
||||
func playOnceWithSound(playAndRecord: Bool, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd)
|
||||
func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool)
|
||||
func continuePlayingWithoutSound()
|
||||
func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd)
|
||||
func setBaseRate(_ baseRate: Double)
|
||||
func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int
|
||||
func removePlaybackCompleted(_ index: Int)
|
||||
@ -85,7 +85,6 @@ final class UniversalVideoNode: ASDisplayNode {
|
||||
private let autoplay: Bool
|
||||
private let snapshotContentWhenGone: Bool
|
||||
|
||||
|
||||
private var contentNode: (UniversalVideoContentNode & ASDisplayNode)?
|
||||
private var contentNodeId: Int32?
|
||||
|
||||
@ -270,10 +269,10 @@ final class UniversalVideoNode: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|
||||
func playOnceWithSound(playAndRecord: Bool) {
|
||||
func playOnceWithSound(playAndRecord: Bool, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd = .loopDisablingSound) {
|
||||
self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in
|
||||
if let contentNode = contentNode {
|
||||
contentNode.playOnceWithSound(playAndRecord: playAndRecord)
|
||||
contentNode.playOnceWithSound(playAndRecord: playAndRecord, actionAtEnd: actionAtEnd)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -294,10 +293,10 @@ final class UniversalVideoNode: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|
||||
func continuePlayingWithoutSound() {
|
||||
func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd = .loopDisablingSound) {
|
||||
self.manager.withUniversalVideoContent(id: self.content.id, { contentNode in
|
||||
if let contentNode = contentNode {
|
||||
contentNode.continuePlayingWithoutSound()
|
||||
contentNode.continuePlayingWithoutSound(actionAtEnd: actionAtEnd)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -139,8 +139,16 @@ public func upgradedAccounts(accountManager: AccountManager, rootPath: String) -
|
||||
if let path = mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
|
||||
accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data)
|
||||
let _ = accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start()
|
||||
if file.isPattern {
|
||||
if let color = file.settings.color, let intensity = file.settings.intensity {
|
||||
let _ = accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedPatternWallpaperRepresentation(color: color, intensity: intensity), complete: true, fetch: true).start()
|
||||
}
|
||||
} else {
|
||||
if file.settings.blur {
|
||||
let _ = accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .image(representations, _):
|
||||
for representation in representations {
|
||||
let resource = representation.resource
|
||||
|
@ -96,6 +96,9 @@ final class WallpaperUploadManager {
|
||||
let sharedContext = self.sharedContext
|
||||
let account = self.account
|
||||
disposable.set(uploadWallpaper(account: account, resource: currentResource, settings: currentWallpaper.settings ?? WallpaperSettings()).start(next: { [weak self] status in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if case let .complete(wallpaper) = status {
|
||||
let updateWallpaper: (TelegramWallpaper) -> Void = { wallpaper in
|
||||
if let resource = wallpaper.mainResource {
|
||||
@ -103,7 +106,7 @@ final class WallpaperUploadManager {
|
||||
let _ = sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start(completed: {})
|
||||
}
|
||||
|
||||
if self?.currentPresentationData?.theme.name == presentationData.theme.name {
|
||||
if strongSelf.currentPresentationData?.theme.name == presentationData.theme.name {
|
||||
let _ = (updatePresentationThemeSettingsInteractively(accountManager: sharedContext.accountManager, { current in
|
||||
let updatedWallpaper: TelegramWallpaper
|
||||
if let currentSettings = current.chatWallpaper.settings {
|
||||
|
@ -141,13 +141,13 @@ private final class WebEmbedVideoContentNode: ASDisplayNode, UniversalVideoConte
|
||||
self.playerNode.seek(timestamp: timestamp)
|
||||
}
|
||||
|
||||
func playOnceWithSound(playAndRecord: Bool) {
|
||||
func playOnceWithSound(playAndRecord: Bool, actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) {
|
||||
}
|
||||
|
||||
func setForceAudioToSpeaker(_ forceAudioToSpeaker: Bool) {
|
||||
}
|
||||
|
||||
func continuePlayingWithoutSound() {
|
||||
func continuePlayingWithoutSound(actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd) {
|
||||
}
|
||||
|
||||
func setBaseRate(_ baseRate: Double) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user