mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
Merge commit '8fbcfd0e888a5fbd9cc788f13998d67d63731134'
This commit is contained in:
commit
f11b2bc350
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
@ -74,6 +74,7 @@ enum ChatMessageBubbleContentTapAction {
|
||||
case call(PeerId)
|
||||
case openMessage
|
||||
case timecode(Double, String)
|
||||
case tooltip(String, ASDisplayNode?, CGRect?)
|
||||
case ignore
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
})
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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: "")
|
||||
|
@ -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))
|
||||
|
@ -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) {
|
||||
|
@ -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))
|
||||
|
@ -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? {
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user