diff --git a/TelegramUI/AuthorizationSequenceSplashController.swift b/TelegramUI/AuthorizationSequenceSplashController.swift index cca29c5f0f..cf46cbfe49 100644 --- a/TelegramUI/AuthorizationSequenceSplashController.swift +++ b/TelegramUI/AuthorizationSequenceSplashController.swift @@ -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 } diff --git a/TelegramUI/ChatBotInfoItem.swift b/TelegramUI/ChatBotInfoItem.swift index 3eddb44573..5797c05411 100644 --- a/TelegramUI/ChatBotInfoItem.swift +++ b/TelegramUI/ChatBotInfoItem.swift @@ -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 } } diff --git a/TelegramUI/ChatItemGalleryFooterContentNode.swift b/TelegramUI/ChatItemGalleryFooterContentNode.swift index e914c1c2cb..b118d5bfc3 100644 --- a/TelegramUI/ChatItemGalleryFooterContentNode.swift +++ b/TelegramUI/ChatItemGalleryFooterContentNode.swift @@ -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 diff --git a/TelegramUI/ChatMessageAnimatedStickerItemNode.swift b/TelegramUI/ChatMessageAnimatedStickerItemNode.swift index d956dc16ae..428fbfa353 100644 --- a/TelegramUI/ChatMessageAnimatedStickerItemNode.swift +++ b/TelegramUI/ChatMessageAnimatedStickerItemNode.swift @@ -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? diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index 3d18ace883..41d49c96cf 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -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 diff --git a/TelegramUI/ChatMessageBubbleContentNode.swift b/TelegramUI/ChatMessageBubbleContentNode.swift index 11f091bb31..f3415aab12 100644 --- a/TelegramUI/ChatMessageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageBubbleContentNode.swift @@ -74,6 +74,7 @@ enum ChatMessageBubbleContentTapAction { case call(PeerId) case openMessage case timecode(Double, String) + case tooltip(String, ASDisplayNode?, CGRect?) case ignore } diff --git a/TelegramUI/ChatMessageBubbleItemNode.swift b/TelegramUI/ChatMessageBubbleItemNode.swift index 7b9cafcf1c..010db0fe4a 100644 --- a/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/TelegramUI/ChatMessageBubbleItemNode.swift @@ -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 { diff --git a/TelegramUI/ChatMessageContactBubbleContentNode.swift b/TelegramUI/ChatMessageContactBubbleContentNode.swift index 3a62f8fbf5..9820243332 100644 --- a/TelegramUI/ChatMessageContactBubbleContentNode.swift +++ b/TelegramUI/ChatMessageContactBubbleContentNode.swift @@ -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 } diff --git a/TelegramUI/ChatMessageDateAndStatusNode.swift b/TelegramUI/ChatMessageDateAndStatusNode.swift index f9562c4a1a..c7b26fb119 100644 --- a/TelegramUI/ChatMessageDateAndStatusNode.swift +++ b/TelegramUI/ChatMessageDateAndStatusNode.swift @@ -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 diff --git a/TelegramUI/ChatMessageInstantVideoItemNode.swift b/TelegramUI/ChatMessageInstantVideoItemNode.swift index 14e3929758..6b91fe8021 100644 --- a/TelegramUI/ChatMessageInstantVideoItemNode.swift +++ b/TelegramUI/ChatMessageInstantVideoItemNode.swift @@ -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 { diff --git a/TelegramUI/ChatMessageInteractiveFileNode.swift b/TelegramUI/ChatMessageInteractiveFileNode.swift index 46a530bca5..3dbe46dde9 100644 --- a/TelegramUI/ChatMessageInteractiveFileNode.swift +++ b/TelegramUI/ChatMessageInteractiveFileNode.swift @@ -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 } diff --git a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift index cc997ae4d6..11295fd98a 100644 --- a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift +++ b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift @@ -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 diff --git a/TelegramUI/ChatMessageMapBubbleContentNode.swift b/TelegramUI/ChatMessageMapBubbleContentNode.swift index f9479c3b5b..098201b2e9 100644 --- a/TelegramUI/ChatMessageMapBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMapBubbleContentNode.swift @@ -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 } diff --git a/TelegramUI/ChatMessageMediaBubbleContentNode.swift b/TelegramUI/ChatMessageMediaBubbleContentNode.swift index ed0f76a20b..04d47dcef2 100644 --- a/TelegramUI/ChatMessageMediaBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMediaBubbleContentNode.swift @@ -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 } diff --git a/TelegramUI/ChatMessagePollBubbleContentNode.swift b/TelegramUI/ChatMessagePollBubbleContentNode.swift index 86a2a1b3ae..8aa13c7272 100644 --- a/TelegramUI/ChatMessagePollBubbleContentNode.swift +++ b/TelegramUI/ChatMessagePollBubbleContentNode.swift @@ -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() - }) - }*/ - } - } } diff --git a/TelegramUI/ChatMessageStickerItemNode.swift b/TelegramUI/ChatMessageStickerItemNode.swift index f404e9d1d7..1a8f956ad3 100644 --- a/TelegramUI/ChatMessageStickerItemNode.swift +++ b/TelegramUI/ChatMessageStickerItemNode.swift @@ -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 { diff --git a/TelegramUI/ChatMessageTextBubbleContentNode.swift b/TelegramUI/ChatMessageTextBubbleContentNode.swift index 9cc37c777b..2491a7d1c0 100644 --- a/TelegramUI/ChatMessageTextBubbleContentNode.swift +++ b/TelegramUI/ChatMessageTextBubbleContentNode.swift @@ -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 } diff --git a/TelegramUI/DefaultPresentationStrings.swift b/TelegramUI/DefaultPresentationStrings.swift index 419dc00f04..2a2ecabdc8 100644 --- a/TelegramUI/DefaultPresentationStrings.swift +++ b/TelegramUI/DefaultPresentationStrings.swift @@ -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: "") diff --git a/TelegramUI/MediaNavigationAccessoryHeaderNode.swift b/TelegramUI/MediaNavigationAccessoryHeaderNode.swift index ae44401bd0..604009c2a3 100644 --- a/TelegramUI/MediaNavigationAccessoryHeaderNode.swift +++ b/TelegramUI/MediaNavigationAccessoryHeaderNode.swift @@ -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)) diff --git a/TelegramUI/MediaNavigationAccessoryPanel.swift b/TelegramUI/MediaNavigationAccessoryPanel.swift index 16beb08089..f4afad5026 100644 --- a/TelegramUI/MediaNavigationAccessoryPanel.swift +++ b/TelegramUI/MediaNavigationAccessoryPanel.swift @@ -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) { diff --git a/TelegramUI/NumericFormat.swift b/TelegramUI/NumericFormat.swift index 62f92eb7f8..0c6a871717 100644 --- a/TelegramUI/NumericFormat.swift +++ b/TelegramUI/NumericFormat.swift @@ -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)) diff --git a/TelegramUI/OpenUrl.swift b/TelegramUI/OpenUrl.swift index 0298fc4f8b..9dae6a884f 100644 --- a/TelegramUI/OpenUrl.swift +++ b/TelegramUI/OpenUrl.swift @@ -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? { diff --git a/TelegramUI/PresentationData.swift b/TelegramUI/PresentationData.swift index edb55243fc..233c91b8d3 100644 --- a/TelegramUI/PresentationData.swift +++ b/TelegramUI/PresentationData.swift @@ -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() diff --git a/TelegramUI/SettingsController.swift b/TelegramUI/SettingsController.swift index 9b95c89fbb..4178478592 100644 --- a/TelegramUI/SettingsController.swift +++ b/TelegramUI/SettingsController.swift @@ -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? diff --git a/TelegramUI/SharedMediaPlayer.swift b/TelegramUI/SharedMediaPlayer.swift index a4443befd0..fc5270a8b1 100644 --- a/TelegramUI/SharedMediaPlayer.swift +++ b/TelegramUI/SharedMediaPlayer.swift @@ -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 diff --git a/TelegramUI/TelegramController.swift b/TelegramUI/TelegramController.swift index 775274308c..c756238399 100644 --- a/TelegramUI/TelegramController.swift +++ b/TelegramUI/TelegramController.swift @@ -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 { diff --git a/TelegramUI/TelegramRootController.swift b/TelegramUI/TelegramRootController.swift index a4fb707bb0..eccb7c61ef 100644 --- a/TelegramUI/TelegramRootController.swift +++ b/TelegramUI/TelegramRootController.swift @@ -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() { diff --git a/TelegramUI/UniversalVideoGalleryItem.swift b/TelegramUI/UniversalVideoGalleryItem.swift index 8a48bd5390..8475a3ee2f 100644 --- a/TelegramUI/UniversalVideoGalleryItem.swift +++ b/TelegramUI/UniversalVideoGalleryItem.swift @@ -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) } }