Merge commit '8fbcfd0e888a5fbd9cc788f13998d67d63731134'

This commit is contained in:
Peter 2019-04-09 16:18:37 +01:00
commit f11b2bc350
28 changed files with 374 additions and 301 deletions

View File

@ -187,7 +187,7 @@ final class AuthorizationSequenceSplashController: ViewController {
}
let stringsValue: PresentationStrings
if let localizationSettings = localizationSettings {
stringsValue = PresentationStrings(primaryComponent: PresentationStringsComponent(languageCode: localizationSettings.primaryComponent.languageCode, localizedName: localizationSettings.primaryComponent.localizedName, pluralizationRulesCode: localizationSettings.primaryComponent.customPluralizationCode, dict: dictFromLocalization(localizationSettings.primaryComponent.localization)), secondaryComponent: localizationSettings.secondaryComponent.flatMap({ PresentationStringsComponent(languageCode: $0.languageCode, localizedName: $0.localizedName, pluralizationRulesCode: $0.customPluralizationCode, dict: dictFromLocalization($0.localization)) }))
stringsValue = PresentationStrings(primaryComponent: PresentationStringsComponent(languageCode: localizationSettings.primaryComponent.languageCode, localizedName: localizationSettings.primaryComponent.localizedName, pluralizationRulesCode: localizationSettings.primaryComponent.customPluralizationCode, dict: dictFromLocalization(localizationSettings.primaryComponent.localization)), secondaryComponent: localizationSettings.secondaryComponent.flatMap({ PresentationStringsComponent(languageCode: $0.languageCode, localizedName: $0.localizedName, pluralizationRulesCode: $0.customPluralizationCode, dict: dictFromLocalization($0.localization)) }), groupingSeparator: "")
} else {
stringsValue = defaultPresentationStrings
}

View File

@ -108,7 +108,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
break
case .ignore:
return .fail
case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .call, .openMessage, .timecode:
case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .call, .openMessage, .timecode, .tooltip:
return .waitForSingleTap
}
}

View File

@ -115,6 +115,17 @@ enum ChatItemGalleryFooterContentTapAction {
case ignore
}
class CaptionScrollWrapperNode: ASDisplayNode {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if result == self.view, let subnode = self.subnodes?.first {
return subnode.hitTest(self.view.convert(point, to: subnode.view), with: event)
} else {
return result
}
}
}
final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScrollViewDelegate {
private let context: AccountContext
private var theme: PresentationTheme
@ -124,7 +135,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
private let deleteButton: UIButton
private let actionButton: UIButton
private let maskNode: ASDisplayNode
private let scrollWrapperNode: ASDisplayNode
private let scrollWrapperNode: CaptionScrollWrapperNode
private let scrollNode: ASScrollNode
private let textNode: ImmediateTextNode
@ -230,7 +241,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
self.deleteButton.setImage(deleteImage, for: [.normal])
self.actionButton.setImage(actionImage, for: [.normal])
self.scrollWrapperNode = ASDisplayNode()
self.scrollWrapperNode = CaptionScrollWrapperNode()
self.scrollWrapperNode.clipsToBounds = true
self.scrollNode = ASScrollNode()
@ -338,16 +349,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
override func didLoad() {
super.didLoad()
self.scrollNode.view.delegate = self
if let maskImage = captionMaskImage {
let mask = CALayer()
mask.contents = maskImage.cgImage
mask.contentsScale = maskImage.scale
//mask.contentsCenter = CGRect(x: max(corners.topLeft.radius, corners.bottomLeft.radius) / maskImage.size.width, y: max(corners.topLeft.radius, corners.topRight.radius) / maskImage.size.height, width: (maskImage.size.width - max(corners.topLeft.radius, corners.bottomLeft.radius) - max(corners.topRight.radius, corners.bottomRight.radius)) / maskImage.size.width, height: (maskImage.size.height - max(corners.topLeft.radius, corners.topRight.radius) - max(corners.bottomLeft.radius, corners.bottomRight.radius)) / maskImage.size.height)
//self.scrollWrapperNode.layer.mask = mask
//self.scrollWrapperNode.layer.mask?.frame = self.scrollWrapperNode.bounds
}
self.scrollNode.view.showsVerticalScrollIndicator = false
}
private func actionForAttributes(_ attributes: [NSAttributedStringKey: Any]) -> GalleryControllerInteractionTapAction? {
@ -488,19 +490,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
self.requestLayout?(.immediate)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if self.scrollWrapperNode.frame.contains(point) {
return self.scrollNode.view
} else {
return result
}
}
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let width = size.width
var bottomInset = bottomInset
if bottomInset < 30.0 {
if !bottomInset.isZero && bottomInset < 30.0 {
bottomInset -= 7.0
}
var panelHeight: CGFloat = 44.0 + bottomInset
@ -546,8 +539,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
panelHeight = max(0.0, panelHeight + visibleTextPanelHeight + textOffset)
if self.scrollNode.view.isScrollEnabled {
if self.scrollWrapperNode.layer.mask == nil {
let maskImage = captionMaskImage!
if self.scrollWrapperNode.layer.mask == nil, let maskImage = captionMaskImage {
let maskLayer = CALayer()
maskLayer.contents = maskImage.cgImage
maskLayer.contentsScale = maskImage.scale

View File

@ -297,7 +297,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .minimal)
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude))
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude))
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
var updatedReplyBackgroundNode: ASImageNode?

View File

@ -531,7 +531,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
}
}
statusSizeAndApply = statusLayout(presentationData.theme, presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, textConstrainedSize)
statusSizeAndApply = statusLayout(presentationData, edited && !sentViaBot, viewCount, dateText, statusType, textConstrainedSize)
}
default:
break

View File

@ -74,6 +74,7 @@ enum ChatMessageBubbleContentTapAction {
case call(PeerId)
case openMessage
case timecode(Double, String)
case tooltip(String, ASDisplayNode?, CGRect?)
case ignore
}

View File

@ -257,7 +257,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
break
case .ignore:
return .fail
case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .call, .openMessage, .timecode:
case .url, .peerMention, .textMention, .botCommand, .hashtag, .instantPage, .wallpaper, .call, .openMessage, .timecode, .tooltip:
return .waitForSingleTap
}
}
@ -751,7 +751,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
}
}
mosaicStatusSizeAndApply = mosaicStatusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude))
mosaicStatusSizeAndApply = mosaicStatusLayout(item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: 200.0, height: CGFloat.greatestFiniteMagnitude))
}
}
@ -1620,7 +1620,6 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
mediaMessage = item.message
}
}
var forceOpen = false
if mediaMessage == nil {
for attribute in item.message.attributes {
if let attribute = attribute as? ReplyMessageAttribute {
@ -1762,6 +1761,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
item.controllerInteraction.seekToTimecode(mediaMessage, timecode, forceOpen)
}
break loop
case let .tooltip(text, node, rect):
foundTapAction = true
if let item = self.item {
let _ = item.controllerInteraction.displayMessageTooltip(item.message.id, text, node, rect)
}
break loop
}
}
if !foundTapAction {
@ -1820,6 +1825,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
item.controllerInteraction.longTap(.timecode(timecode, text), mediaMessage)
}
break loop
case .tooltip:
break
}
}
if !foundTapAction, let tapMessage = tapMessage {

View File

@ -175,7 +175,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
var statusApply: ((Bool) -> Void)?
if let statusType = statusType {
let (size, apply) = statusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude))
let (size, apply) = statusLayout(item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude))
statusSize = size
statusApply = apply
}

View File

@ -125,7 +125,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
self.addSubnode(self.dateNode)
}
func asyncLayout() -> (_ theme: ChatPresentationThemeData, _ strings: PresentationStrings, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize) -> (CGSize, (Bool) -> Void) {
func asyncLayout() -> (_ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize) -> (CGSize, (Bool) -> Void) {
let dateLayout = TextNode.asyncLayout(self.dateNode)
var checkReadNode = self.checkReadNode
@ -139,7 +139,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
let currentType = self.type
let currentTheme = self.theme
return { theme, strings, edited, impressionCount, dateText, type, constrainedSize in
return { presentationData, edited, impressionCount, dateText, type, constrainedSize in
let dateColor: UIColor
var backgroundImage: UIImage?
var outgoingStatus: ChatMessageDateAndStatusOutgoingType?
@ -151,14 +151,14 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
let clockMinImage: UIImage?
var impressionImage: UIImage?
let themeUpdated = theme != currentTheme || type != currentType
let themeUpdated = presentationData.theme != currentTheme || type != currentType
let graphics = PresentationResourcesChat.principalGraphics(theme.theme, wallpaper: theme.wallpaper)
let graphics = PresentationResourcesChat.principalGraphics(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
let offset: CGFloat = -UIScreenPixel
switch type {
case .BubbleIncoming:
dateColor = theme.theme.chat.bubble.incomingSecondaryTextColor
dateColor = presentationData.theme.theme.chat.bubble.incomingSecondaryTextColor
leftInset = 10.0
loadedCheckFullImage = graphics.checkBubbleFullImage
loadedCheckPartialImage = graphics.checkBubblePartialImage
@ -168,7 +168,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
impressionImage = graphics.incomingDateAndStatusImpressionIcon
}
case let .BubbleOutgoing(status):
dateColor = theme.theme.chat.bubble.outgoingSecondaryTextColor
dateColor = presentationData.theme.theme.chat.bubble.outgoingSecondaryTextColor
outgoingStatus = status
leftInset = 10.0
loadedCheckFullImage = graphics.checkBubbleFullImage
@ -179,7 +179,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
impressionImage = graphics.outgoingDateAndStatusImpressionIcon
}
case .ImageIncoming:
dateColor = theme.theme.chat.bubble.mediaDateAndStatusTextColor
dateColor = presentationData.theme.theme.chat.bubble.mediaDateAndStatusTextColor
backgroundImage = graphics.dateAndStatusMediaBackground
leftInset = 0.0
loadedCheckFullImage = graphics.checkMediaFullImage
@ -190,7 +190,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
impressionImage = graphics.mediaImpressionIcon
}
case let .ImageOutgoing(status):
dateColor = theme.theme.chat.bubble.mediaDateAndStatusTextColor
dateColor = presentationData.theme.theme.chat.bubble.mediaDateAndStatusTextColor
outgoingStatus = status
backgroundImage = graphics.dateAndStatusMediaBackground
leftInset = 0.0
@ -202,7 +202,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
impressionImage = graphics.mediaImpressionIcon
}
case .FreeIncoming:
let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper)
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
dateColor = serviceColor.primaryText
backgroundImage = graphics.dateAndStatusFreeBackground
leftInset = 0.0
@ -214,7 +214,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
impressionImage = graphics.freeImpressionIcon
}
case let .FreeOutgoing(status):
let serviceColor = serviceMessageColorComponents(theme: theme.theme, wallpaper: theme.wallpaper)
let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
dateColor = serviceColor.primaryText
outgoingStatus = status
backgroundImage = graphics.dateAndStatusFreeBackground
@ -230,10 +230,10 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
var updatedDateText = dateText
if edited {
updatedDateText = "\(strings.Conversation_MessageEditedLabel) \(updatedDateText)"
updatedDateText = "\(presentationData.strings.Conversation_MessageEditedLabel) \(updatedDateText)"
}
if let impressionCount = impressionCount {
updatedDateText = compactNumericCountString(impressionCount) + " " + updatedDateText
updatedDateText = compactNumericCountString(impressionCount, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator) + " " + updatedDateText
}
let (date, dateApply) = dateLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: updatedDateText, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: constrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -373,7 +373,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
return (layoutSize, { [weak self] animated in
if let strongSelf = self {
strongSelf.theme = theme
strongSelf.theme = presentationData.theme
strongSelf.type = type
if backgroundImage != nil {
@ -504,17 +504,17 @@ class ChatMessageDateAndStatusNode: ASDisplayNode {
}
}
static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ theme: ChatPresentationThemeData, _ strings: PresentationStrings, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) {
static func asyncLayout(_ node: ChatMessageDateAndStatusNode?) -> (_ presentationData: ChatPresentationData, _ edited: Bool, _ impressionCount: Int?, _ dateText: String, _ type: ChatMessageDateAndStatusType, _ constrainedSize: CGSize) -> (CGSize, (Bool) -> ChatMessageDateAndStatusNode) {
let currentLayout = node?.asyncLayout()
return { theme, strings, edited, impressionCount, dateText, type, constrainedSize in
return { presentationData, edited, impressionCount, dateText, type, constrainedSize in
let resultNode: ChatMessageDateAndStatusNode
let resultSizeAndApply: (CGSize, (Bool) -> Void)
if let node = node, let currentLayout = currentLayout {
resultNode = node
resultSizeAndApply = currentLayout(theme, strings, edited, impressionCount, dateText, type, constrainedSize)
resultSizeAndApply = currentLayout(presentationData, edited, impressionCount, dateText, type, constrainedSize)
} else {
resultNode = ChatMessageDateAndStatusNode()
resultSizeAndApply = resultNode.asyncLayout()(theme, strings, edited, impressionCount, dateText, type, constrainedSize)
resultSizeAndApply = resultNode.asyncLayout()(presentationData, edited, impressionCount, dateText, type, constrainedSize)
}
return (resultSizeAndApply.0, { animated in

View File

@ -681,7 +681,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
let offset: CGFloat = incoming ? 42.0 : 0.0
if let selectionNode = self.selectionNode {
selectionNode.updateSelected(selected, animated: false)
selectionNode.updateSelected(selected, animated: animated)
selectionNode.frame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0);
} else {

View File

@ -267,7 +267,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
let dateText = stringForMessageTimestampStatus(message: message, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, strings: presentationData.strings)
let (size, apply) = statusLayout(presentationData.theme, presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, constrainedSize)
let (size, apply) = statusLayout(presentationData, edited && !sentViaBot, viewCount, dateText, statusType, constrainedSize)
statusSize = size
statusApply = apply
}

View File

@ -261,7 +261,7 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode {
} else {
maxDateAndStatusWidth = width - videoFrame.midX - 85.0
}
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude))
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: max(1.0, maxDateAndStatusWidth), height: CGFloat.greatestFiniteMagnitude))
var contentSize = imageSize
var dateAndStatusOverflow = false

View File

@ -226,7 +226,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
var statusApply: ((Bool) -> Void)?
if let statusType = statusType {
let (size, apply) = statusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude))
let (size, apply) = statusLayout(item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: constrainedSize.width, height: CGFloat.greatestFiniteMagnitude))
statusSize = size
statusApply = apply
}

View File

@ -195,7 +195,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
var statusApply: ((Bool) -> Void)?
if let statusType = statusType {
let (size, apply) = statusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude))
let (size, apply) = statusLayout(item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: imageSize.width - 30.0, height: CGFloat.greatestFiniteMagnitude))
statusSize = size
statusApply = apply
}

View File

@ -301,6 +301,7 @@ private func generatePercentageAnimationImages(presentationData: ChatPresentatio
private struct ChatMessagePollOptionResult: Equatable {
let normalized: CGFloat
let percent: Int
let count: Int32
}
private final class ChatMessagePollOptionNode: ASDisplayNode {
@ -314,7 +315,7 @@ private final class ChatMessagePollOptionNode: ASDisplayNode {
private let resultBarNode: ASImageNode
var option: TelegramMediaPollOption?
private var currentResult: ChatMessagePollOptionResult?
public private(set) var currentResult: ChatMessagePollOptionResult?
var pressed: (() -> Void)?
override init() {
@ -617,7 +618,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
var statusApply: ((Bool) -> Void)?
if let statusType = statusType {
let (size, apply) = statusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, textConstrainedSize)
let (size, apply) = statusLayout(item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, textConstrainedSize)
statusSize = size
statusApply = apply
}
@ -727,12 +728,12 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
var optionResult: ChatMessagePollOptionResult?
if let count = optionVoterCount[i] {
if maxOptionVoterCount != 0 && totalVoterCount != 0 {
optionResult = ChatMessagePollOptionResult(normalized: CGFloat(count) / CGFloat(maxOptionVoterCount), percent: optionVoterCounts[i])
optionResult = ChatMessagePollOptionResult(normalized: CGFloat(count) / CGFloat(maxOptionVoterCount), percent: optionVoterCounts[i], count: count)
} else if poll.isClosed {
optionResult = ChatMessagePollOptionResult(normalized: 0, percent: 0)
optionResult = ChatMessagePollOptionResult(normalized: 0, percent: 0, count: 0)
}
} else if poll.isClosed {
optionResult = ChatMessagePollOptionResult(normalized: 0, percent: 0)
optionResult = ChatMessagePollOptionResult(normalized: 0, percent: 0, count: 0)
}
let result = makeLayout(item.context.account.peerId, item.presentationData, item.message, option, optionResult, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0)
boundingSize.width = max(boundingSize.width, result.minimumWidth + layoutConstants.bubble.borderInset * 2.0)
@ -922,55 +923,21 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
} else {
for optionNode in self.optionNodes {
if optionNode.isUserInteractionEnabled {
if optionNode.frame.contains(point) {
if optionNode.frame.contains(point) {
if optionNode.isUserInteractionEnabled {
return .ignore
} else if let result = optionNode.currentResult, let item = self.item {
let string: String
if result.count == 0 {
string = item.presentationData.strings.MessagePoll_NoVotes
} else {
string = item.presentationData.strings.MessagePoll_VotedCount(result.count)
}
return .tooltip(string, optionNode, optionNode.bounds.offsetBy(dx: 0.0, dy: 10.0))
}
}
}
return .none
}
}
override func updateTouchesAtPoint(_ point: CGPoint?) {
if let item = self.item {
/*var rects: [CGRect]?
if let point = point {
let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
let possibleNames: [String] = [
TelegramTextAttributes.URL,
TelegramTextAttributes.PeerMention,
TelegramTextAttributes.PeerTextMention,
TelegramTextAttributes.BotCommand,
TelegramTextAttributes.Hashtag
]
for name in possibleNames {
if let _ = attributes[NSAttributedStringKey(rawValue: name)] {
rects = self.textNode.attributeRects(name: name, at: index)
break
}
}
}
}
if let rects = rects {
let linkHighlightingNode: LinkHighlightingNode
if let current = self.linkHighlightingNode {
linkHighlightingNode = current
} else {
linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingLinkHighlightColor : item.presentationData.theme.theme.chat.bubble.outgoingLinkHighlightColor)
self.linkHighlightingNode = linkHighlightingNode
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
}
linkHighlightingNode.frame = self.textNode.frame
linkHighlightingNode.updateRects(rects)
} else if let linkHighlightingNode = self.linkHighlightingNode {
self.linkHighlightingNode = nil
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
linkHighlightingNode?.removeFromSupernode()
})
}*/
}
}
}

View File

@ -241,7 +241,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular)
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData.theme, item.presentationData.strings, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude))
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData, edited, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude))
var viaBotApply: (TextNodeLayout, () -> TextNode)?
var replyInfoApply: (CGSize, () -> ChatMessageReplyInfoNode)?
@ -674,7 +674,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
let offset: CGFloat = incoming ? 42.0 : 0.0
if let selectionNode = self.selectionNode {
selectionNode.updateSelected(selected, animated: false)
selectionNode.updateSelected(selected, animated: animated)
selectionNode.frame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0);
} else {

View File

@ -130,7 +130,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
var statusApply: ((Bool) -> Void)?
if let statusType = statusType {
let (size, apply) = statusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, textConstrainedSize)
let (size, apply) = statusLayout(item.presentationData, edited && !sentViaBot, viewCount, dateText, statusType, textConstrainedSize)
statusSize = size
statusApply = apply
}

View File

@ -1,3 +1,3 @@
import Foundation
public let defaultPresentationStrings = PresentationStrings(primaryComponent: PresentationStringsComponent(languageCode: "en", localizedName: "English", pluralizationRulesCode: nil, dict: NSDictionary(contentsOf: URL(fileURLWithPath: Bundle.main.path(forResource: "Localizable", ofType: "strings", inDirectory: nil, forLocalization: "en")!)) as! [String : String]), secondaryComponent: nil)
public let defaultPresentationStrings = PresentationStrings(primaryComponent: PresentationStringsComponent(languageCode: "en", localizedName: "English", pluralizationRulesCode: nil, dict: NSDictionary(contentsOf: URL(fileURLWithPath: Bundle.main.path(forResource: "Localizable", ofType: "strings", inDirectory: nil, forLocalization: "en")!)) as! [String : String]), secondaryComponent: nil, groupingSeparator: "")

View File

@ -7,15 +7,135 @@ import TelegramCore
private let titleFont = Font.regular(12.0)
private let subtitleFont = Font.regular(10.0)
final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
private class MediaHeaderItemNode: ASDisplayNode {
private let titleNode: TextNode
private let subtitleNode: TextNode
override init() {
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false
self.subtitleNode = TextNode()
self.subtitleNode.isUserInteractionEnabled = false
self.subtitleNode.displaysAsynchronously = false
super.init()
self.isUserInteractionEnabled = false
self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode)
}
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, playbackItem: SharedMediaPlaylistItem?, transition: ContainedViewLayoutTransition) -> (NSAttributedString?, NSAttributedString?, Bool) {
var rateButtonHidden = false
var titleString: NSAttributedString?
var subtitleString: NSAttributedString?
if let playbackItem = playbackItem, let displayData = playbackItem.displayData {
switch displayData {
case let .music(title, performer, _):
rateButtonHidden = true
let titleText: String = title ?? "Unknown Track"
let subtitleText: String = performer ?? "Unknown Artist"
titleString = NSAttributedString(string: titleText, font: titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)
subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: theme.rootController.navigationBar.secondaryTextColor)
case let .voice(author, peer):
rateButtonHidden = false
let titleText: String = author?.displayTitle ?? ""
let subtitleText: String
if let peer = peer {
if peer is TelegramGroup || peer is TelegramChannel {
subtitleText = peer.displayTitle
} else {
subtitleText = strings.MusicPlayer_VoiceNote
}
} else {
subtitleText = strings.MusicPlayer_VoiceNote
}
titleString = NSAttributedString(string: titleText, font: titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)
subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: theme.rootController.navigationBar.secondaryTextColor)
case let .instantVideo(author, peer, timestamp):
rateButtonHidden = false
let titleText: String = author?.displayTitle ?? ""
var subtitleText: String
if let peer = peer {
if peer is TelegramGroup || peer is TelegramChannel {
subtitleText = peer.displayTitle
} else {
subtitleText = strings.Message_VideoMessage
}
} else {
subtitleText = strings.Message_VideoMessage
}
if titleText == subtitleText {
subtitleText = humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: timestamp)
}
titleString = NSAttributedString(string: titleText, font: titleFont, textColor: theme.rootController.navigationBar.primaryTextColor)
subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: theme.rootController.navigationBar.secondaryTextColor)
}
}
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
var titleSideInset: CGFloat = 12.0
if !rateButtonHidden {
titleSideInset += 52.0
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - titleSideInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - titleSideInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let _ = titleApply()
let _ = subtitleApply()
let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0
let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 4.0 + minimizedTitleOffset), size: titleLayout.size)
let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 20.0), size: subtitleLayout.size)
transition.updateFrame(node: self.titleNode, frame: minimizedTitleFrame)
transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame)
return (titleString, subtitleString, rateButtonHidden)
}
}
private func generateMaskImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 12.0, height: 2.0), opaque: false, rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let gradientColors = [color.cgColor, color.withAlphaComponent(0.0).cgColor] as CFArray
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 12.0, y: 0.0), options: CGGradientDrawingOptions())
})
}
final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollViewDelegate {
static let minimizedHeight: CGFloat = 37.0
private var theme: PresentationTheme
private var strings: PresentationStrings
private var dateTimeFormat: PresentationDateTimeFormat
private let titleNode: TextNode
private let subtitleNode: TextNode
private let scrollNode: ASScrollNode
private var initialContentOffset: CGFloat?
private let leftMaskNode: ASImageNode
private let rightMaskNode: ASImageNode
private let currentItemNode: MediaHeaderItemNode
private let previousItemNode: MediaHeaderItemNode
private let nextItemNode: MediaHeaderItemNode
private let closeButton: HighlightableButtonNode
private let actionButton: HighlightTrackingButtonNode
@ -42,6 +162,8 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
var close: (() -> Void)?
var toggleRate: (() -> Void)?
var togglePlayPause: (() -> Void)?
var playPrevious: (() -> Void)?
var playNext: (() -> Void)?
var voiceBaseRate: AudioPlaybackRate? = nil {
didSet {
@ -69,9 +191,9 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
}
}
var playbackItem: SharedMediaPlaylistItem? {
var playbackItems: (SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?)? {
didSet {
if !arePlaylistItemsEqual(self.playbackItem, oldValue), let layout = validLayout {
if !arePlaylistItemsEqual(self.playbackItems?.0, oldValue?.0) || !arePlaylistItemsEqual(self.playbackItems?.1, oldValue?.1) || !arePlaylistItemsEqual(self.playbackItems?.2, oldValue?.2), let layout = validLayout {
self.updateLayout(size: layout.0, leftInset: layout.1, rightInset: layout.2, transition: .immediate)
}
}
@ -82,12 +204,20 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
self.strings = presentationData.strings
self.dateTimeFormat = presentationData.dateTimeFormat
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false
self.subtitleNode = TextNode()
self.subtitleNode.isUserInteractionEnabled = false
self.subtitleNode.displaysAsynchronously = false
self.scrollNode = ASScrollNode()
self.currentItemNode = MediaHeaderItemNode()
self.previousItemNode = MediaHeaderItemNode()
self.nextItemNode = MediaHeaderItemNode()
self.leftMaskNode = ASImageNode()
self.leftMaskNode.contentMode = .scaleToFill
self.rightMaskNode = ASImageNode()
self.rightMaskNode.contentMode = .scaleToFill
let maskImage = generateMaskImage(color: self.theme.rootController.navigationBar.backgroundColor)
self.leftMaskNode.image = maskImage
self.rightMaskNode.image = maskImage
self.closeButton = HighlightableButtonNode()
self.closeButton.accessibilityLabel = "Stop playback"
@ -132,8 +262,13 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
self.clipsToBounds = true
self.addSubnode(self.titleNode)
self.addSubnode(self.subtitleNode)
self.addSubnode(self.scrollNode)
self.scrollNode.addSubnode(self.currentItemNode)
self.scrollNode.addSubnode(self.previousItemNode)
self.scrollNode.addSubnode(self.nextItemNode)
self.addSubnode(self.leftMaskNode)
self.addSubnode(self.rightMaskNode)
self.addSubnode(self.closeButton)
self.addSubnode(self.rateButton)
@ -205,6 +340,13 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
override func didLoad() {
super.didLoad()
self.view.disablesInteractiveTransitionGestureRecognizer = true
self.scrollNode.view.alwaysBounceHorizontal = true
self.scrollNode.view.delegate = self
self.scrollNode.view.isPagingEnabled = true
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.showsVerticalScrollIndicator = false
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
self.tapRecognizer = tapRecognizer
self.view.addGestureRecognizer(tapRecognizer)
@ -215,6 +357,10 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
self.strings = presentationData.strings
self.dateTimeFormat = presentationData.dateTimeFormat
let maskImage = generateMaskImage(color: self.theme.rootController.navigationBar.backgroundColor)
self.leftMaskNode.image = maskImage
self.rightMaskNode.image = maskImage
self.closeButton.setImage(PresentationResourcesRootController.navigationPlayerCloseButton(self.theme), for: [])
self.actionPlayNode.image = PresentationResourcesRootController.navigationPlayerPlayIcon(self.theme)
self.actionPauseNode.image = PresentationResourcesRootController.navigationPlayerPauseIcon(self.theme)
@ -234,94 +380,77 @@ final class MediaNavigationAccessoryHeaderNode: ASDisplayNode {
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.changeTrack()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
guard !decelerate else {
return
}
self.changeTrack()
}
private func changeTrack() {
guard let initialContentOffset = self.initialContentOffset else {
return
}
if self.scrollNode.view.contentOffset.x < initialContentOffset {
self.playPrevious?()
} else if self.scrollNode.view.contentOffset.x > initialContentOffset {
self.playNext?()
}
}
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, leftInset, rightInset)
let minHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
var titleString: NSAttributedString?
var subtitleString: NSAttributedString?
if let playbackItem = self.playbackItem, let displayData = playbackItem.displayData {
switch displayData {
case let .music(title, performer, _):
self.rateButton.isHidden = true
let titleText: String = title ?? "Unknown Track"
let subtitleText: String = performer ?? "Unknown Artist"
titleString = NSAttributedString(string: titleText, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor)
subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor)
case let .voice(author, peer):
self.rateButton.isHidden = false
let titleText: String = author?.displayTitle ?? ""
let subtitleText: String
if let peer = peer {
if peer is TelegramGroup || peer is TelegramChannel {
subtitleText = peer.displayTitle
} else {
subtitleText = self.strings.MusicPlayer_VoiceNote
}
} else {
subtitleText = self.strings.MusicPlayer_VoiceNote
}
titleString = NSAttributedString(string: titleText, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor)
subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor)
case let .instantVideo(author, peer, timestamp):
self.rateButton.isHidden = false
let titleText: String = author?.displayTitle ?? ""
var subtitleText: String
if let peer = peer {
if peer is TelegramGroup || peer is TelegramChannel {
subtitleText = peer.displayTitle
} else {
subtitleText = self.strings.Message_VideoMessage
}
} else {
subtitleText = self.strings.Message_VideoMessage
}
if titleText == subtitleText {
subtitleText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: timestamp)
}
titleString = NSAttributedString(string: titleText, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor)
subtitleString = NSAttributedString(string: subtitleText, font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor)
}
}
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
var titleSideInset: CGFloat = 80.0
if !self.rateButton.isHidden {
titleSideInset += 52.0
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - titleSideInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - titleSideInset, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let inset: CGFloat = 40.0 + leftInset
let constrainedSize = CGSize(width: size.width - inset * 2.0, height: size.height)
let (titleString, subtitleString, rateButtonHidden) = self.currentItemNode.updateLayout(size: constrainedSize, leftInset: leftInset, rightInset: rightInset, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, playbackItem: self.playbackItems?.0, transition: transition)
self.accessibilityAreaNode.accessibilityLabel = "\(titleString?.string ?? ""). \(subtitleString?.string ?? "")"
self.rateButton.isHidden = rateButtonHidden
let _ = titleApply()
let _ = subtitleApply()
let _ = self.previousItemNode.updateLayout(size: constrainedSize, leftInset: 0.0, rightInset: 0.0, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, playbackItem: self.playbackItems?.1, transition: transition)
let _ = self.nextItemNode.updateLayout(size: constrainedSize, leftInset: 0.0, rightInset: 0.0, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, playbackItem: self.playbackItems?.2, transition: transition)
let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0
let constrainedBounds = CGRect(origin: CGPoint(), size: constrainedSize)
transition.updateFrame(node: self.scrollNode, frame: constrainedBounds.offsetBy(dx: inset, dy: 0.0))
let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 4.0 + minimizedTitleOffset), size: titleLayout.size)
let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 20.0), size: subtitleLayout.size)
var contentSize = constrainedSize
var contentOffset: CGFloat = 0.0
if self.playbackItems?.1 != nil {
contentSize.width += constrainedSize.width
contentOffset = constrainedSize.width
}
if self.playbackItems?.2 != nil {
contentSize.width += constrainedSize.width
}
transition.updateFrame(node: self.titleNode, frame: minimizedTitleFrame)
transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame)
self.previousItemNode.frame = constrainedBounds.offsetBy(dx: contentOffset - constrainedSize.width, dy: 0.0)
self.currentItemNode.frame = constrainedBounds.offsetBy(dx: contentOffset, dy: 0.0)
self.nextItemNode.frame = constrainedBounds.offsetBy(dx: contentOffset + constrainedSize.width, dy: 0.0)
self.leftMaskNode.frame = CGRect(x: inset, y: 0.0, width: 12.0, height: minHeight)
self.rightMaskNode.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
self.rightMaskNode.frame = CGRect(x: size.width - inset - 12.0, y: 0.0, width: 12.0, height: minHeight)
self.scrollNode.view.contentSize = contentSize
self.scrollNode.view.contentOffset = CGPoint(x: contentOffset, y: 0.0)
self.initialContentOffset = contentOffset
let bounds = CGRect(origin: CGPoint(), size: size)
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 44.0 - rightInset, y: 0.0), size: CGSize(width: 44.0, height: minHeight)))
let rateButtonSize = CGSize(width: 24.0, height: minHeight)
transition.updateFrame(node: self.rateButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - 18.0 - rateButtonSize.width - rightInset, y: 0.0), size: rateButtonSize))
transition.updateFrame(node: self.actionPlayNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
transition.updateFrame(node: self.actionPauseNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: leftInset, y: minimizedTitleFrame.minY - 4.0), size: CGSize(width: 40.0, height: 37.0)))
transition.updateFrame(node: self.actionButton, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 40.0, height: 37.0)))
transition.updateFrame(node: self.scrubbingNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 37.0 - 2.0), size: CGSize(width: size.width, height: 2.0)))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: minHeight - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel)))
self.accessibilityAreaNode.frame = CGRect(origin: CGPoint(x: self.actionButton.frame.maxX, y: 0.0), size: CGSize(width: self.rateButton.frame.minX - self.actionButton.frame.maxX, height: minHeight))

View File

@ -10,6 +10,8 @@ final class MediaNavigationAccessoryPanel: ASDisplayNode {
var toggleRate: (() -> Void)?
var togglePlayPause: (() -> Void)?
var tapAction: (() -> Void)?
var playPrevious: (() -> Void)?
var playNext: (() -> Void)?
init(context: AccountContext) {
self.containerNode = MediaNavigationAccessoryContainerNode(context: context)
@ -18,24 +20,34 @@ final class MediaNavigationAccessoryPanel: ASDisplayNode {
self.addSubnode(self.containerNode)
containerNode.headerNode.close = { [weak self] in
self.containerNode.headerNode.close = { [weak self] in
if let strongSelf = self, let close = strongSelf.close {
close()
}
}
containerNode.headerNode.toggleRate = { [weak self] in
self.containerNode.headerNode.toggleRate = { [weak self] in
self?.toggleRate?()
}
containerNode.headerNode.togglePlayPause = { [weak self] in
self.containerNode.headerNode.togglePlayPause = { [weak self] in
if let strongSelf = self, let togglePlayPause = strongSelf.togglePlayPause {
togglePlayPause()
}
}
containerNode.headerNode.tapAction = { [weak self] in
self.containerNode.headerNode.tapAction = { [weak self] in
if let strongSelf = self, let tapAction = strongSelf.tapAction {
tapAction()
}
}
self.containerNode.headerNode.playPrevious = { [weak self] in
if let strongSelf = self, let playPrevious = strongSelf.playPrevious {
playPrevious()
}
}
self.containerNode.headerNode.playNext = { [weak self] in
if let strongSelf = self, let playNext = strongSelf.playNext {
playNext()
}
}
}
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {

View File

@ -1,17 +1,17 @@
import Foundation
public func compactNumericCountString(_ count: Int) -> String {
public func compactNumericCountString(_ count: Int, decimalSeparator: String = ".") -> String {
if count >= 1000 * 1000 {
let remainder = (count % (1000 * 1000)) / (1000 * 100)
if remainder != 0 {
return "\(count / (1000 * 1000)),\(remainder)M"
return "\(count / (1000 * 1000))\(decimalSeparator)\(remainder)M"
} else {
return "\(count / (1000 * 1000))M"
}
} else if count >= 1000 {
let remainder = (count % (1000)) / (100)
if remainder != 0 {
return "\(count / 1000),\(remainder)K"
return "\(count / 1000)\(decimalSeparator)\(remainder)K"
} else {
return "\(count / 1000)K"
}
@ -20,6 +20,23 @@ public func compactNumericCountString(_ count: Int) -> String {
}
}
public func presentationStringsFormattedNumber(_ count: Int32, _ groupingSeparator: String = "") -> String {
let string = "\(count)"
if groupingSeparator.isEmpty || abs(count) < 1000 {
return string
} else {
var groupedString: String = ""
for i in 0 ..< Int(ceil(Double(string.count) / 3.0)) {
let index = string.count - Int(i + 1) * 3
if !groupedString.isEmpty {
groupedString = groupingSeparator + groupedString
}
groupedString = String(string[string.index(string.startIndex, offsetBy: max(0, index)) ..< string.index(string.startIndex, offsetBy: index + 3)]) + groupedString
}
return groupedString
}
}
func timeIntervalString(strings: PresentationStrings, value: Int32) -> String {
if value < 60 {
return strings.MessageTimer_Seconds(max(1, value))

View File

@ -16,51 +16,14 @@ public struct ParsedSecureIdUrl {
}
public func parseProxyUrl(_ url: URL) -> ProxyServerSettings? {
guard let query = url.query, url.scheme == "tg" else {
guard let proxy = parseProxyUrl(url.absoluteString) else {
return nil
}
if url.host == "socks" || url.host == "proxy" {
if let components = URLComponents(string: "/?" + query) {
var server: String?
var port: String?
var user: String?
var pass: String?
var secret: String?
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "server" || queryItem.name == "proxy" {
server = value
} else if queryItem.name == "port" {
port = value
} else if queryItem.name == "user" {
user = value
} else if queryItem.name == "pass" {
pass = value
} else if queryItem.name == "secret" {
secret = value
}
}
}
}
if let server = server, !server.isEmpty, let port = port, let portValue = Int32(port), let _ = Int32(port) {
let connection: ProxyServerConnection
if let secret = secret {
let data = dataWithHexString(secret)
if data.count == 16 || (data.count == 17 && MTSocksProxySettings.secretSupportsExtendedPadding(data)) {
connection = .mtp(secret: data)
} else {
return nil
}
} else {
connection = .socks5(username: user, password: pass)
}
return ProxyServerSettings(host: server, port: portValue, connection: connection)
}
}
if let secret = proxy.secret, secret.count == 16 || (secret.count == 17 && MTSocksProxySettings.secretSupportsExtendedPadding(secret)) {
return ProxyServerSettings(host: proxy.host, port: proxy.port, connection: .mtp(secret: secret))
} else {
return ProxyServerSettings(host: proxy.host, port: proxy.port, connection: .socks5(username: proxy.username, password: proxy.password))
}
return nil
}
public func parseSecureIdUrl(_ url: URL) -> ParsedSecureIdUrl? {

View File

@ -10,6 +10,7 @@ public struct PresentationDateTimeFormat: Equatable {
let dateFormat: PresentationDateFormat
let dateSeparator: String
let decimalSeparator: String
let groupingSeparator: String
}
public struct PresentationVolumeControlStatusBarIcons: Equatable {
@ -103,7 +104,7 @@ private func volumeControlStatusBarIcons() -> PresentationVolumeControlStatusBar
return PresentationVolumeControlStatusBarIcons(offIcon: UIImage(bundleImageName: "Components/Volume/VolumeOff")!, halfIcon: UIImage(bundleImageName: "Components/Volume/VolumeHalf")!, fullIcon: UIImage(bundleImageName: "Components/Volume/VolumeFull")!)
}
private func currentDateTimeFormat(strings: PresentationStrings) -> PresentationDateTimeFormat {
private func currentDateTimeFormat() -> PresentationDateTimeFormat {
let locale = Locale.current
let dateFormatter = DateFormatter()
dateFormatter.locale = locale
@ -137,7 +138,8 @@ private func currentDateTimeFormat(strings: PresentationStrings) -> Presentation
}
let decimalSeparator = locale.decimalSeparator ?? "."
return PresentationDateTimeFormat(timeFormat: timeFormat, dateFormat: dateFormat, dateSeparator: dateSeparator, decimalSeparator: decimalSeparator)
let groupingSeparator = locale.groupingSeparator ?? ""
return PresentationDateTimeFormat(timeFormat: timeFormat, dateFormat: dateFormat, dateSeparator: dateSeparator, decimalSeparator: decimalSeparator, groupingSeparator: groupingSeparator)
}
private func currentPersonNameSortOrder() -> PresentationPersonNameOrder {
@ -260,13 +262,13 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager) -
themeValue = makeDefaultDayPresentationTheme(accentColor: themeSettings.themeAccentColor ?? defaultDayAccentColor, serviceBackgroundColor: defaultServiceBackgroundColor)
}
}
let dateTimeFormat = currentDateTimeFormat()
let stringsValue: PresentationStrings
if let localizationSettings = localizationSettings {
stringsValue = PresentationStrings(primaryComponent: PresentationStringsComponent(languageCode: localizationSettings.primaryComponent.languageCode, localizedName: localizationSettings.primaryComponent.localizedName, pluralizationRulesCode: localizationSettings.primaryComponent.customPluralizationCode, dict: dictFromLocalization(localizationSettings.primaryComponent.localization)), secondaryComponent: localizationSettings.secondaryComponent.flatMap({ PresentationStringsComponent(languageCode: $0.languageCode, localizedName: $0.localizedName, pluralizationRulesCode: $0.customPluralizationCode, dict: dictFromLocalization($0.localization)) }))
stringsValue = PresentationStrings(primaryComponent: PresentationStringsComponent(languageCode: localizationSettings.primaryComponent.languageCode, localizedName: localizationSettings.primaryComponent.localizedName, pluralizationRulesCode: localizationSettings.primaryComponent.customPluralizationCode, dict: dictFromLocalization(localizationSettings.primaryComponent.localization)), secondaryComponent: localizationSettings.secondaryComponent.flatMap({ PresentationStringsComponent(languageCode: $0.languageCode, localizedName: $0.localizedName, pluralizationRulesCode: $0.customPluralizationCode, dict: dictFromLocalization($0.localization)) }), groupingSeparator: dateTimeFormat.groupingSeparator)
} else {
stringsValue = defaultPresentationStrings
}
let dateTimeFormat = currentDateTimeFormat(strings: stringsValue)
let nameDisplayOrder = contactSettings.nameDisplayOrder
let nameSortOrder = currentPersonNameSortOrder()
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: themeValue, chatWallpaper: effectiveChatWallpaper, volumeControlStatusBarIcons: volumeControlStatusBarIcons(), fontSize: themeSettings.fontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, disableAnimations: themeSettings.disableAnimations), automaticMediaDownloadSettings: automaticMediaDownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, experimentalUISettings: experimentalUISettings)
@ -415,14 +417,13 @@ public func updatedPresentationData(accountManager: AccountManager, applicationB
localizationSettings = nil
}
let dateTimeFormat = currentDateTimeFormat()
let stringsValue: PresentationStrings
if let localizationSettings = localizationSettings {
stringsValue = PresentationStrings(primaryComponent: PresentationStringsComponent(languageCode: localizationSettings.primaryComponent.languageCode, localizedName: localizationSettings.primaryComponent.localizedName, pluralizationRulesCode: localizationSettings.primaryComponent.customPluralizationCode, dict: dictFromLocalization(localizationSettings.primaryComponent.localization)), secondaryComponent: localizationSettings.secondaryComponent.flatMap({ PresentationStringsComponent(languageCode: $0.languageCode, localizedName: $0.localizedName, pluralizationRulesCode: $0.customPluralizationCode, dict: dictFromLocalization($0.localization)) }))
stringsValue = PresentationStrings(primaryComponent: PresentationStringsComponent(languageCode: localizationSettings.primaryComponent.languageCode, localizedName: localizationSettings.primaryComponent.localizedName, pluralizationRulesCode: localizationSettings.primaryComponent.customPluralizationCode, dict: dictFromLocalization(localizationSettings.primaryComponent.localization)), secondaryComponent: localizationSettings.secondaryComponent.flatMap({ PresentationStringsComponent(languageCode: $0.languageCode, localizedName: $0.localizedName, pluralizationRulesCode: $0.customPluralizationCode, dict: dictFromLocalization($0.localization)) }), groupingSeparator: dateTimeFormat.groupingSeparator)
} else {
stringsValue = defaultPresentationStrings
}
let dateTimeFormat = currentDateTimeFormat(strings: stringsValue)
let nameDisplayOrder = contactSettings.nameDisplayOrder
let nameSortOrder = currentPersonNameSortOrder()
@ -437,7 +438,7 @@ public func updatedPresentationData(accountManager: AccountManager, applicationB
}
public func defaultPresentationData() -> PresentationData {
let dateTimeFormat = currentDateTimeFormat(strings: defaultPresentationStrings)
let dateTimeFormat = currentDateTimeFormat()
let nameDisplayOrder: PresentationPersonNameOrder = .firstLast
let nameSortOrder = currentPersonNameSortOrder()

View File

@ -371,7 +371,7 @@ private enum SettingsEntry: ItemListNodeEntry {
label = .badge("\(badgeCount)")
}
}
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .dayFirst, dateSeparator: ".", decimalSeparator: "."), nameDisplayOrder: .firstLast, account: account, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .none, label: label, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: nil, switchValue: nil, enabled: true, sectionId: self.section, action: {
return ItemListPeerItem(theme: theme, strings: strings, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .dayFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: ""), nameDisplayOrder: .firstLast, account: account, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .none, label: label, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: nil, switchValue: nil, enabled: true, sectionId: self.section, action: {
arguments.switchToAccount(account.id)
}, setPeerIdWithRevealedOptions: { lhs, rhs in
var lhsAccountId: AccountRecordId?

View File

@ -207,15 +207,19 @@ final class SharedMediaPlayerItemPlaybackState: Equatable {
let playlistId: SharedMediaPlaylistId
let playlistLocation: SharedMediaPlaylistLocation
let item: SharedMediaPlaylistItem
let previousItem: SharedMediaPlaylistItem?
let nextItem: SharedMediaPlaylistItem?
let status: MediaPlayerStatus
let order: MusicPlaybackSettingsOrder
let looping: MusicPlaybackSettingsLooping
let playerIndex: Int32
init(playlistId: SharedMediaPlaylistId, playlistLocation: SharedMediaPlaylistLocation, item: SharedMediaPlaylistItem, status: MediaPlayerStatus, order: MusicPlaybackSettingsOrder, looping: MusicPlaybackSettingsLooping, playerIndex: Int32) {
init(playlistId: SharedMediaPlaylistId, playlistLocation: SharedMediaPlaylistLocation, item: SharedMediaPlaylistItem, previousItem: SharedMediaPlaylistItem?, nextItem: SharedMediaPlaylistItem?, status: MediaPlayerStatus, order: MusicPlaybackSettingsOrder, looping: MusicPlaybackSettingsLooping, playerIndex: Int32) {
self.playlistId = playlistId
self.playlistLocation = playlistLocation
self.item = item
self.previousItem = previousItem
self.nextItem = nextItem
self.status = status
self.order = order
self.looping = looping
@ -229,6 +233,12 @@ final class SharedMediaPlayerItemPlaybackState: Equatable {
if !arePlaylistItemsEqual(lhs.item, rhs.item) {
return false
}
if !arePlaylistItemsEqual(lhs.previousItem, rhs.previousItem) {
return false
}
if !arePlaylistItemsEqual(lhs.nextItem, rhs.nextItem) {
return false
}
if lhs.status != rhs.status {
return false
}
@ -544,7 +554,6 @@ final class SharedMediaPlayer {
node.setSoundEnabled(false)
}
}
//strongSelf.playbackItem?.seek(0.0)
strongSelf.playedToEnd?()
}
}
@ -559,7 +568,7 @@ final class SharedMediaPlayer {
if let playbackItem = strongSelf.playbackItem, let item = state.item {
strongSelf.playbackStateValue.set(playbackItem.playbackStatus
|> map { itemStatus in
return .item(SharedMediaPlayerItemPlaybackState(playlistId: playlistId, playlistLocation: playlistLocation, item: item, status: itemStatus, order: state.order, looping: state.looping, playerIndex: playerIndex))
return .item(SharedMediaPlayerItemPlaybackState(playlistId: playlistId, playlistLocation: playlistLocation, item: item, previousItem: state.previousItem, nextItem: state.nextItem, status: itemStatus, order: state.order, looping: state.looping, playerIndex: playerIndex))
})
strongSelf.markItemAsPlayedDisposable.set((playbackItem.playbackStatus
|> filter { status in

View File

@ -55,7 +55,7 @@ public class TelegramController: ViewController {
private var mediaStatusDisposable: Disposable?
private var locationBroadcastDisposable: Disposable?
private(set) var playlistStateAndType: (SharedMediaPlaylistItem, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)?
private(set) var playlistStateAndType: (SharedMediaPlaylistItem, SharedMediaPlaylistItem?, SharedMediaPlaylistItem?, MusicPlaybackSettingsOrder, MediaManagerPlayerType, Account)?
var tempVoicePlaylistEnded: (() -> Void)?
var tempVoicePlaylistItemChanged: ((SharedMediaPlaylistItem?, SharedMediaPlaylistItem?) -> Void)?
@ -124,9 +124,11 @@ public class TelegramController: ViewController {
return
}
if !arePlaylistItemsEqual(strongSelf.playlistStateAndType?.0, playlistStateAndType?.1.item) ||
strongSelf.playlistStateAndType?.1 != playlistStateAndType?.1.order || strongSelf.playlistStateAndType?.2 != playlistStateAndType?.2 {
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.1, playlistStateAndType?.1.previousItem) ||
!arePlaylistItemsEqual(strongSelf.playlistStateAndType?.2, playlistStateAndType?.1.nextItem) ||
strongSelf.playlistStateAndType?.3 != playlistStateAndType?.1.order || strongSelf.playlistStateAndType?.4 != playlistStateAndType?.2 {
var previousVoiceItem: SharedMediaPlaylistItem?
if let playlistStateAndType = strongSelf.playlistStateAndType, playlistStateAndType.2 == .voice {
if let playlistStateAndType = strongSelf.playlistStateAndType, playlistStateAndType.4 == .voice {
previousVoiceItem = playlistStateAndType.0
}
@ -137,10 +139,10 @@ public class TelegramController: ViewController {
strongSelf.tempVoicePlaylistItemChanged?(previousVoiceItem, updatedVoiceItem)
if let playlistStateAndType = playlistStateAndType {
strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0)
strongSelf.playlistStateAndType = (playlistStateAndType.1.item, playlistStateAndType.1.previousItem, playlistStateAndType.1.nextItem, playlistStateAndType.1.order, playlistStateAndType.2, playlistStateAndType.0)
} else {
var voiceEnded = false
if strongSelf.playlistStateAndType?.2 == .voice {
if strongSelf.playlistStateAndType?.4 == .voice {
voiceEnded = true
}
strongSelf.playlistStateAndType = nil
@ -417,13 +419,13 @@ public class TelegramController: ViewController {
mediaAccessoryPanelHidden = size != layout.metrics.widthClass
}
if let (item, _, type, _) = self.playlistStateAndType, !mediaAccessoryPanelHidden {
if let (item, previousItem, nextItem, _, type, _) = self.playlistStateAndType, !mediaAccessoryPanelHidden {
let panelHeight = MediaNavigationAccessoryHeaderNode.minimizedHeight
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationHeight.isZero ? -panelHeight : (navigationHeight + additionalHeight + UIScreenPixel)), size: CGSize(width: layout.size.width, height: panelHeight))
if let (mediaAccessoryPanel, mediaType) = self.mediaAccessoryPanel, mediaType == type {
transition.updateFrame(layer: mediaAccessoryPanel.layer, frame: panelFrame)
mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition)
mediaAccessoryPanel.containerNode.headerNode.playbackItem = item
mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem)
let delayedStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState
|> mapToSignal { value -> Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> in
@ -461,7 +463,7 @@ public class TelegramController: ViewController {
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context)
mediaAccessoryPanel.containerNode.headerNode.displayScrubber = type != .voice
mediaAccessoryPanel.close = { [weak self] in
if let strongSelf = self, let (_, _, type, _) = strongSelf.playlistStateAndType {
if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType {
strongSelf.context.sharedContext.mediaManager.setPlaylist(nil, type: type)
}
}
@ -486,19 +488,29 @@ public class TelegramController: ViewController {
return nextRate
}
|> deliverOnMainQueue).start(next: { baseRate in
guard let strongSelf = self, let (_, _, type, _) = strongSelf.playlistStateAndType else {
guard let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType else {
return
}
strongSelf.context.sharedContext.mediaManager.playlistControl(.setBaseRate(baseRate), type: type)
})
}
mediaAccessoryPanel.togglePlayPause = { [weak self] in
if let strongSelf = self, let (_, _, type, _) = strongSelf.playlistStateAndType {
if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType {
strongSelf.context.sharedContext.mediaManager.playlistControl(.playback(.togglePlayPause), type: type)
}
}
mediaAccessoryPanel.playPrevious = { [weak self] in
if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType {
strongSelf.context.sharedContext.mediaManager.playlistControl(.next, type: type)
}
}
mediaAccessoryPanel.playNext = { [weak self] in
if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType {
strongSelf.context.sharedContext.mediaManager.playlistControl(.previous, type: type)
}
}
mediaAccessoryPanel.tapAction = { [weak self] in
guard let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController, let (state, order, type, account) = strongSelf.playlistStateAndType else {
guard let strongSelf = self, let _ = strongSelf.navigationController as? NavigationController, let (state, _, _, order, type, account) = strongSelf.playlistStateAndType else {
return
}
if let id = state.id as? PeerMessagesMediaPlaylistItemId {
@ -580,7 +592,7 @@ public class TelegramController: ViewController {
}
self.mediaAccessoryPanel = (mediaAccessoryPanel, type)
mediaAccessoryPanel.updateLayout(size: panelFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: .immediate)
mediaAccessoryPanel.containerNode.headerNode.playbackItem = item
mediaAccessoryPanel.containerNode.headerNode.playbackItems = (item, previousItem, nextItem)
mediaAccessoryPanel.containerNode.headerNode.playbackStatus = self.context.sharedContext.mediaManager.globalMediaPlayerState
|> map { state -> MediaPlayerStatus in
if let stateOrLoading = state?.1, case let .state(state) = stateOrLoading {

View File

@ -83,44 +83,6 @@ public final class TelegramRootController: NavigationController {
self.accountSettingsController = accountSettingsController
self.rootTabController = tabBarController
self.pushViewController(tabBarController, animated: false)
// guard let controller = self.viewControllers.last as? ViewController else {
// return
// }
//
// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
// let wrapperNode = ASDisplayNode()
// let bounds = controller.displayNode.bounds
// wrapperNode.frame = bounds
// wrapperNode.backgroundColor = .gray
// //controller.displayNode.addSubnode(wrapperNode)
//
// let label = TGMarqLabel(frame: CGRect())
// label.textColor = .white
// label.font = Font.regular(28.0)
// label.scrollDuration = 15.0
// label.fadeLength = 25.0
// label.trailingBuffer = 60.0
// label.animationDelay = 2.0
// label.text = "Lorem ipsum dolor sir amet, consecteur"
// label.sizeToFit()
// label.frame = CGRect(x: bounds.width / 2.0 - 100.0, y: 100.0, width: 200.0, height: label.frame.height)
// //wrapperNode.view.addSubview(label)
//
// let data = testLineChartData()
// let node = LineChartContainerNode(data: data)
// node.frame = CGRect(x: 0.0, y: 100.0, width: bounds.width, height: 280.0)
// node.updateLayout(size: node.frame.size)
// wrapperNode.addSubnode(node)
//
// self.wNode = wrapperNode
//
// let gesture = UITapGestureRecognizer(target: self, action: #selector(self.closeIt))
// wrapperNode.view.addGestureRecognizer(gesture)
// }
}
@objc func closeIt() {

View File

@ -641,6 +641,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
videoNode.seek(0.0)
videoNode.play()
} else {
self.hideStatusNodeUntilCentrality = false
videoNode.playOnceWithSound(playAndRecord: false, seek: seek, actionAtEnd: .stop)
}
}