Merge commit 'b6e2e462641f50d2494f1bfd827f86158863b87f'

This commit is contained in:
Peter Iakovlev 2019-02-08 17:56:35 +04:00
commit 94b5a8fa56
35 changed files with 411 additions and 229 deletions

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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),

View File

@ -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: {})

View File

@ -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):

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
@objc override public class func display(withParameters: Any?, isCancelled: () -> Bool) -> UIImage? {
if let content = (withParameters as? ChatMessageInteractiveMediaBadgeParams)?.content {
var contentSize = CGSize()
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
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)))
}
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):
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)
}
let durationString = NSMutableAttributedString(string: duration, attributes: [.font: font, .foregroundColor: foregroundColor])
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)
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 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 sizeString = NSMutableAttributedString(string: size, attributes: [.font: font, .foregroundColor: foregroundColor])
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 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)
}
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)
}
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()
})
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)
}
}

View File

@ -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 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, automaticDownload {
} 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

View File

@ -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
}

View File

@ -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 {
@ -64,14 +64,14 @@ private final class FetchVideoThumbnailSource {
private var videoStream: SoftwareVideoStream?
private var avIoContext: UnsafeMutablePointer<AVIOContext>?
private var avFormatContext: UnsafeMutablePointer<AVFormatContext>?
init(mediaBox: MediaBox, resourceReference: MediaResourceReference, size: Int32) {
let _ = FFMpegMediaFrameSourceContextHelpers.registerFFMpegGlobals
self.mediaBox = mediaBox
self.resourceReference = resourceReference
self.size = size
var avFormatContextRef = avformat_alloc_context()
guard let avFormatContext = avFormatContextRef else {
self.readingError = true
@ -82,7 +82,7 @@ private final class FetchVideoThumbnailSource {
let avIoBuffer = av_malloc(ioBufferSize)!
let avIoContextRef = avio_alloc_context(avIoBuffer.assumingMemoryBound(to: UInt8.self), Int32(ioBufferSize), 0, Unmanaged.passUnretained(self).toOpaque(), readPacketCallback, nil, seekCallback)
self.avIoContext = avIoContextRef
avFormatContext.pointee.pb = self.avIoContext
guard avformat_open_input(&avFormatContextRef, nil, nil, nil) >= 0 else {
@ -94,7 +94,7 @@ private final class FetchVideoThumbnailSource {
self.readingError = true
return
}
self.avFormatContext = avFormatContext
var videoStream: SoftwareVideoStream?
@ -111,7 +111,7 @@ private final class FetchVideoThumbnailSource {
let (fps, timebase) = FFMpegMediaFrameSourceContextHelpers.streamFpsAndTimeBase(stream: avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!, defaultTimeBase: CMTimeMake(1, 24))
let duration = CMTimeMake(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.duration, timebase.timescale)
var rotationAngle: Double = 0.0
if let rotationInfo = av_dict_get(avFormatContext.pointee.streams.advanced(by: streamIndex).pointee!.pointee.metadata, "rotate", nil, 0), let value = rotationInfo.pointee.value {
if strcmp(value, "0") != 0 {
@ -178,10 +178,10 @@ private final class FetchVideoThumbnailSource {
let avNoPtsRawValue: UInt64 = 0x8000000000000000
let avNoPtsValue = Int64(bitPattern: avNoPtsRawValue)
let packetPts = packet.packet.pts == avNoPtsValue ? packet.packet.dts : packet.packet.pts
let pts = CMTimeMake(packetPts, videoStream.timebase.timescale)
let dts = CMTimeMake(packet.packet.dts, videoStream.timebase.timescale)
let duration: CMTime
let frameDuration = packet.packet.duration
@ -198,7 +198,7 @@ private final class FetchVideoThumbnailSource {
self.readingError = true
}
}
return frames.first
}
@ -264,7 +264,7 @@ private final class FetchVideoThumbnailSourceThreadImpl: NSObject {
}
}
}
@objc func fetch(_ parameters: FetchVideoThumbnailSourceParameters) {
let source = FetchVideoThumbnailSource(mediaBox: parameters.mediaBox, resourceReference: parameters.resourceReference, size: parameters.size)
let _ = source.readFrame()
@ -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 {

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -303,13 +303,7 @@ func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) -> Signa
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: Int32(timer), countdownBeginTime: nil))
}
var text = caption ?? ""
if text.isEmpty && GlobalExperimentalSettings.enableTinyThumbnails {
if let tinyThumbnail = compressTinyThumbnail(scaledImage) {
text = serializeTinyThumbnail(tinyThumbnail)
}
}
var text = caption ?? ""
messages.append(.message(text: text, attributes: attributes, mediaReference: .standalone(media: media), replyToMessageId: nil, localGroupingKey: item.groupedId))
}
}

View File

@ -48,6 +48,11 @@ enum MediaPlayerActionAtEnd {
case stop
}
enum MediaPlayerPlayOnceWithSoundActionAtEnd {
case loopDisablingSound
case stop
}
private final class MediaPlayerAudioRendererContext {
let renderer: MediaPlayerAudioRenderer
var requestedFrames = false

View File

@ -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()
}

View File

@ -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 ({
@ -685,22 +685,7 @@ public func chatMessagePhotoInternal(photoData: Signal<(Data?, Data?, Bool), NoE
if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
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)

View File

@ -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) {

View File

@ -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)
}

View File

@ -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 {
return variableColor.withoutWallpaper
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 {
return chatTheme.serviceMessage.components.withDefaultWallpaper
switch wallpaper {
case .builtin, .color(0xffffff):
return chatTheme.serviceMessage.components.withDefaultWallpaper
default:
return chatTheme.serviceMessage.components.withCustomWallpaper
}
}

View File

@ -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

View File

@ -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()
}
}

View File

@ -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 {
}

View File

@ -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) {

View File

@ -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 = {

View File

@ -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 {

View File

@ -288,7 +288,7 @@ public func themeSettingsController(context: AccountContext) -> ViewController {
case 3:
wallpaper = .color(0x18222d)
default:
wallpaper = .builtin
wallpaper = .builtin(WallpaperSettings())
}
}

View File

@ -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) {

View File

@ -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)
}
})
}

View File

@ -139,7 +139,15 @@ 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()
let _ = accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), 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 {

View File

@ -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 {

View File

@ -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) {