mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Stories
This commit is contained in:
parent
a196a1684b
commit
1fa20fa4a4
@ -9394,8 +9394,6 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Notification.Story" = "Story";
|
||||
|
||||
"ChatList.StoryFeedTooltip" = "Tap above to view updates\nfrom %@";
|
||||
|
||||
"StoryFeed.ContextAddStory" = "Add Story";
|
||||
"StoryFeed.ContextSavedStories" = "Saved Stories";
|
||||
"StoryFeed.ContextArchivedStories" = "Archived Stories";
|
||||
@ -9549,7 +9547,6 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Story.ContextDeleteStory" = "Delete Story";
|
||||
|
||||
"Story.TooltipPrivacyCloseFriends" = "You are seeing this story because **%@** added you\nto their list of Close Friends.";
|
||||
"Story.TooltipPrivacyContacts" = "Only **%@'s** contacts can view this story.";
|
||||
"Story.TooltipPrivacySelectedContacts" = "Only some contacts **%@** selected can view this story.";
|
||||
"Story.ToastViewInChat" = "View in Chat";
|
||||
@ -9728,3 +9725,7 @@ Sorry for the inconvenience.";
|
||||
"Story.Editor.TooltipPremiumCaptionEntities" = "Subscribe to [Telegram Premium]() to add links and formatting in captions to your stories.";
|
||||
|
||||
"Story.Context.TooltipPremiumSaveStories" = "Subscribe to [Telegram Premium]() to save other people's unprotected stories to your Gallery.";
|
||||
|
||||
"ChatList.StoryFeedTooltipUsers" = "Tap above to view stories from %@";
|
||||
|
||||
"Story.TooltipPrivacyCloseFriends2" = "You are seeing this story because **%@** added you to their list of Close Friends.";
|
||||
|
@ -2013,15 +2013,19 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
}
|
||||
|
||||
let text: String = self.presentationData.strings.ChatList_StoryFeedTooltip(itemListString).string
|
||||
let text: String = self.presentationData.strings.ChatList_StoryFeedTooltipUsers(itemListString).string
|
||||
|
||||
let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0))
|
||||
self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
|
||||
guard let self else {
|
||||
return nil
|
||||
let tooltipScreen = TooltipScreen(
|
||||
account: self.context.account,
|
||||
sharedContext: self.context.sharedContext,
|
||||
text: .markdown(text: text),
|
||||
balancedTextLayout: true,
|
||||
style: .default,
|
||||
location: TooltipScreen.Location.point(self.displayNode.view.convert(absoluteFrame.insetBy(dx: 0.0, dy: 0.0).offsetBy(dx: 0.0, dy: 4.0), to: nil).offsetBy(dx: 1.0, dy: 2.0), .top), displayDuration: .infinite, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: false)
|
||||
}
|
||||
return (self.displayNode, absoluteFrame.insetBy(dx: 0.0, dy: 0.0).offsetBy(dx: 0.0, dy: 4.0))
|
||||
}))
|
||||
)
|
||||
self.present(tooltipScreen, in: .current)
|
||||
|
||||
#if !DEBUG
|
||||
let _ = ApplicationSpecificNotice.setDisplayChatListStoriesTooltip(accountManager: self.context.sharedContext.accountManager).start()
|
||||
|
20
submodules/Components/BalancedTextComponent/BUILD
Normal file
20
submodules/Components/BalancedTextComponent/BUILD
Normal file
@ -0,0 +1,20 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "BalancedTextComponent",
|
||||
module_name = "BalancedTextComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/Markdown:Markdown",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,209 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import ComponentFlow
|
||||
import Display
|
||||
import Markdown
|
||||
|
||||
public final class BalancedTextComponent: Component {
|
||||
public enum TextContent: Equatable {
|
||||
case plain(NSAttributedString)
|
||||
case markdown(text: String, attributes: MarkdownAttributes)
|
||||
}
|
||||
|
||||
public let text: TextContent
|
||||
public let balanced: Bool
|
||||
public let horizontalAlignment: NSTextAlignment
|
||||
public let verticalAlignment: TextVerticalAlignment
|
||||
public let truncationType: CTLineTruncationType
|
||||
public let maximumNumberOfLines: Int
|
||||
public let lineSpacing: CGFloat
|
||||
public let cutout: TextNodeCutout?
|
||||
public let insets: UIEdgeInsets
|
||||
public let textShadowColor: UIColor?
|
||||
public let textShadowBlur: CGFloat?
|
||||
public let textStroke: (UIColor, CGFloat)?
|
||||
public let highlightColor: UIColor?
|
||||
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
|
||||
public let tapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
|
||||
public let longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)?
|
||||
|
||||
public init(
|
||||
text: TextContent,
|
||||
balanced: Bool = true,
|
||||
horizontalAlignment: NSTextAlignment = .natural,
|
||||
verticalAlignment: TextVerticalAlignment = .top,
|
||||
truncationType: CTLineTruncationType = .end,
|
||||
maximumNumberOfLines: Int = 1,
|
||||
lineSpacing: CGFloat = 0.0,
|
||||
cutout: TextNodeCutout? = nil,
|
||||
insets: UIEdgeInsets = UIEdgeInsets(),
|
||||
textShadowColor: UIColor? = nil,
|
||||
textShadowBlur: CGFloat? = nil,
|
||||
textStroke: (UIColor, CGFloat)? = nil,
|
||||
highlightColor: UIColor? = nil,
|
||||
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
|
||||
tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil,
|
||||
longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil
|
||||
) {
|
||||
self.text = text
|
||||
self.balanced = balanced
|
||||
self.horizontalAlignment = horizontalAlignment
|
||||
self.verticalAlignment = verticalAlignment
|
||||
self.truncationType = truncationType
|
||||
self.maximumNumberOfLines = maximumNumberOfLines
|
||||
self.lineSpacing = lineSpacing
|
||||
self.cutout = cutout
|
||||
self.insets = insets
|
||||
self.textShadowColor = textShadowColor
|
||||
self.textShadowBlur = textShadowBlur
|
||||
self.textStroke = textStroke
|
||||
self.highlightColor = highlightColor
|
||||
self.highlightAction = highlightAction
|
||||
self.tapAction = tapAction
|
||||
self.longTapAction = longTapAction
|
||||
}
|
||||
|
||||
public static func ==(lhs: BalancedTextComponent, rhs: BalancedTextComponent) -> Bool {
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.balanced != rhs.balanced {
|
||||
return false
|
||||
}
|
||||
if lhs.horizontalAlignment != rhs.horizontalAlignment {
|
||||
return false
|
||||
}
|
||||
if lhs.verticalAlignment != rhs.verticalAlignment {
|
||||
return false
|
||||
}
|
||||
if lhs.truncationType != rhs.truncationType {
|
||||
return false
|
||||
}
|
||||
if lhs.maximumNumberOfLines != rhs.maximumNumberOfLines {
|
||||
return false
|
||||
}
|
||||
if lhs.lineSpacing != rhs.lineSpacing {
|
||||
return false
|
||||
}
|
||||
if lhs.cutout != rhs.cutout {
|
||||
return false
|
||||
}
|
||||
if lhs.insets != rhs.insets {
|
||||
return false
|
||||
}
|
||||
|
||||
if let lhsTextShadowColor = lhs.textShadowColor, let rhsTextShadowColor = rhs.textShadowColor {
|
||||
if !lhsTextShadowColor.isEqual(rhsTextShadowColor) {
|
||||
return false
|
||||
}
|
||||
} else if (lhs.textShadowColor != nil) != (rhs.textShadowColor != nil) {
|
||||
return false
|
||||
}
|
||||
if lhs.textShadowBlur != rhs.textShadowBlur {
|
||||
return false
|
||||
}
|
||||
|
||||
if let lhsTextStroke = lhs.textStroke, let rhsTextStroke = rhs.textStroke {
|
||||
if !lhsTextStroke.0.isEqual(rhsTextStroke.0) {
|
||||
return false
|
||||
}
|
||||
if lhsTextStroke.1 != rhsTextStroke.1 {
|
||||
return false
|
||||
}
|
||||
} else if (lhs.textShadowColor != nil) != (rhs.textShadowColor != nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
if let lhsHighlightColor = lhs.highlightColor, let rhsHighlightColor = rhs.highlightColor {
|
||||
if !lhsHighlightColor.isEqual(rhsHighlightColor) {
|
||||
return false
|
||||
}
|
||||
} else if (lhs.highlightColor != nil) != (rhs.highlightColor != nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private let textView: ImmediateTextView
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
self.textView = ImmediateTextView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.textView)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func attributeSubstring(name: String, index: Int) -> (String, String)? {
|
||||
return self.textView.attributeSubstring(name: name, index: index)
|
||||
}
|
||||
|
||||
public func update(component: BalancedTextComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||
let attributedString: NSAttributedString
|
||||
switch component.text {
|
||||
case let .plain(string):
|
||||
attributedString = string
|
||||
case let .markdown(text, attributes):
|
||||
attributedString = parseMarkdownIntoAttributedString(text, attributes: attributes)
|
||||
}
|
||||
|
||||
self.textView.attributedText = attributedString
|
||||
self.textView.maximumNumberOfLines = component.maximumNumberOfLines
|
||||
self.textView.truncationType = component.truncationType
|
||||
self.textView.textAlignment = component.horizontalAlignment
|
||||
self.textView.verticalAlignment = component.verticalAlignment
|
||||
self.textView.lineSpacing = component.lineSpacing
|
||||
self.textView.cutout = component.cutout
|
||||
self.textView.insets = component.insets
|
||||
self.textView.textShadowColor = component.textShadowColor
|
||||
self.textView.textShadowBlur = component.textShadowBlur
|
||||
self.textView.textStroke = component.textStroke
|
||||
self.textView.linkHighlightColor = component.highlightColor
|
||||
self.textView.highlightAttributeAction = component.highlightAction
|
||||
self.textView.tapAttributeAction = component.tapAction
|
||||
self.textView.longTapAttributeAction = component.longTapAction
|
||||
|
||||
var bestSize: (availableWidth: CGFloat, info: TextNodeLayout)
|
||||
|
||||
let info = self.textView.updateLayoutFullInfo(availableSize)
|
||||
bestSize = (availableSize.width, info)
|
||||
|
||||
if component.balanced && info.numberOfLines > 1 {
|
||||
let measureIncrement = 8.0
|
||||
var measureWidth = info.size.width
|
||||
measureWidth -= measureIncrement
|
||||
while measureWidth > 0.0 {
|
||||
let otherInfo = self.textView.updateLayoutFullInfo(CGSize(width: measureWidth, height: availableSize.height))
|
||||
if otherInfo.numberOfLines > bestSize.info.numberOfLines {
|
||||
break
|
||||
}
|
||||
if (otherInfo.size.width - otherInfo.trailingLineWidth) < (bestSize.info.size.width - bestSize.info.trailingLineWidth) {
|
||||
bestSize = (measureWidth, otherInfo)
|
||||
}
|
||||
|
||||
measureWidth -= measureIncrement
|
||||
}
|
||||
|
||||
let bestInfo = self.textView.updateLayoutFullInfo(CGSize(width: bestSize.availableWidth, height: availableSize.height))
|
||||
bestSize = (availableSize.width, bestInfo)
|
||||
}
|
||||
|
||||
self.textView.frame = CGRect(origin: CGPoint(), size: bestSize.info.size)
|
||||
return bestSize.info.size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||
}
|
||||
}
|
@ -223,6 +223,8 @@ open class ViewControllerComponentContainer: ViewController {
|
||||
private var presentationDataDisposable: Disposable?
|
||||
public private(set) var validLayout: ContainerViewLayout?
|
||||
|
||||
public var wasDismissed: (() -> Void)?
|
||||
|
||||
public init<C: Component>(context: AccountContext, component: C, navigationBarAppearance: NavigationBarAppearance, statusBarStyle: StatusBarStyle = .default, presentationMode: PresentationMode = .default, theme: Theme = .default) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
|
||||
self.context = context
|
||||
self.component = AnyComponent(component)
|
||||
@ -304,7 +306,11 @@ open class ViewControllerComponentContainer: ViewController {
|
||||
}
|
||||
|
||||
open override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
super.dismiss(animated: flag, completion: completion)
|
||||
let wasDismissed = self.wasDismissed
|
||||
super.dismiss(animated: flag, completion: {
|
||||
completion?()
|
||||
wasDismissed?()
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate var forceNextUpdate = false
|
||||
|
@ -1279,7 +1279,27 @@ open class TextNode: ASDisplayNode {
|
||||
coreTextLine = originalLine
|
||||
}
|
||||
} else {
|
||||
coreTextLine = CTLineCreateTruncatedLine(originalLine, max(1.0, Double(lineConstrainedSize.width) - truncationTokenWidth), truncationType, truncationToken) ?? truncationToken
|
||||
if customTruncationToken != nil {
|
||||
let coreTextLine1 = CTLineCreateTruncatedLine(originalLine, max(1.0, Double(lineConstrainedSize.width)), truncationType, truncationToken) ?? truncationToken
|
||||
let runs = (CTLineGetGlyphRuns(coreTextLine1) as [AnyObject]) as! [CTRun]
|
||||
var hasTruncationToken = false
|
||||
for run in runs {
|
||||
let runRange = CTRunGetStringRange(run)
|
||||
if runRange.location + runRange.length >= nsString.length {
|
||||
hasTruncationToken = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hasTruncationToken {
|
||||
coreTextLine = coreTextLine1
|
||||
} else {
|
||||
let coreTextLine2 = CTLineCreateTruncatedLine(originalLine, max(1.0, Double(lineConstrainedSize.width) - truncationTokenWidth), truncationType, truncationToken) ?? truncationToken
|
||||
coreTextLine = coreTextLine2
|
||||
}
|
||||
} else {
|
||||
coreTextLine = CTLineCreateTruncatedLine(originalLine, max(1.0, Double(lineConstrainedSize.width)), truncationType, truncationToken) ?? truncationToken
|
||||
}
|
||||
let runs = (CTLineGetGlyphRuns(coreTextLine) as [AnyObject]) as! [CTRun]
|
||||
for run in runs {
|
||||
let runAttributes: NSDictionary = CTRunGetAttributes(run)
|
||||
|
@ -100,6 +100,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
|
||||
|
||||
public private(set) var content: TooltipControllerContent
|
||||
private let baseFontSize: CGFloat
|
||||
private let balancedTextLayout: Bool
|
||||
|
||||
open func updateContent(_ content: TooltipControllerContent, animated: Bool, extendTimer: Bool, arrowOnBottom: Bool = true) {
|
||||
if self.content != content {
|
||||
@ -130,9 +131,10 @@ open class TooltipController: ViewController, StandalonePresentableController {
|
||||
|
||||
public var dismissed: ((Bool) -> Void)?
|
||||
|
||||
public init(content: TooltipControllerContent, baseFontSize: CGFloat, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true, padding: CGFloat = 8.0, innerPadding: UIEdgeInsets = UIEdgeInsets()) {
|
||||
public init(content: TooltipControllerContent, baseFontSize: CGFloat, balancedTextLayout: Bool = false, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true, padding: CGFloat = 8.0, innerPadding: UIEdgeInsets = UIEdgeInsets()) {
|
||||
self.content = content
|
||||
self.baseFontSize = baseFontSize
|
||||
self.balancedTextLayout = balancedTextLayout
|
||||
self.timeout = timeout
|
||||
self.dismissByTapOutside = dismissByTapOutside
|
||||
self.dismissByTapOutsideSource = dismissByTapOutsideSource
|
||||
@ -155,7 +157,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
|
||||
}
|
||||
|
||||
override open func loadDisplayNode() {
|
||||
self.displayNode = TooltipControllerNode(content: self.content, baseFontSize: self.baseFontSize, dismiss: { [weak self] tappedInside in
|
||||
self.displayNode = TooltipControllerNode(content: self.content, baseFontSize: self.baseFontSize, balancedTextLayout: self.balancedTextLayout, dismiss: { [weak self] tappedInside in
|
||||
self?.dismiss(tappedInside: tappedInside)
|
||||
}, dismissByTapOutside: self.dismissByTapOutside, dismissByTapOutsideSource: self.dismissByTapOutsideSource)
|
||||
self.controllerNode.padding = self.padding
|
||||
|
@ -4,6 +4,7 @@ import AsyncDisplayKit
|
||||
|
||||
final class TooltipControllerNode: ASDisplayNode {
|
||||
private let baseFontSize: CGFloat
|
||||
private let balancedTextLayout: Bool
|
||||
|
||||
private let dismiss: (Bool) -> Void
|
||||
|
||||
@ -25,8 +26,9 @@ final class TooltipControllerNode: ASDisplayNode {
|
||||
private var dismissedByTouchOutside = false
|
||||
private var dismissByTapOutsideSource = false
|
||||
|
||||
init(content: TooltipControllerContent, baseFontSize: CGFloat, dismiss: @escaping (Bool) -> Void, dismissByTapOutside: Bool, dismissByTapOutsideSource: Bool) {
|
||||
init(content: TooltipControllerContent, baseFontSize: CGFloat, balancedTextLayout: Bool, dismiss: @escaping (Bool) -> Void, dismissByTapOutside: Bool, dismissByTapOutsideSource: Bool) {
|
||||
self.baseFontSize = baseFontSize
|
||||
self.balancedTextLayout = balancedTextLayout
|
||||
|
||||
self.dismissByTapOutside = dismissByTapOutside
|
||||
self.dismissByTapOutsideSource = dismissByTapOutsideSource
|
||||
|
@ -781,9 +781,19 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
|
||||
private weak var currentGalleryController: TGModernGalleryController?
|
||||
private weak var currentGalleryParentController: ViewController?
|
||||
|
||||
fileprivate var currentAssetDownloadDisposable = MetaDisposable()
|
||||
|
||||
fileprivate func closeGalleryController() {
|
||||
if let _ = self.currentGalleryController, let currentGalleryParentController = self.currentGalleryParentController {
|
||||
self.currentGalleryController = nil
|
||||
self.currentGalleryParentController = nil
|
||||
|
||||
currentGalleryParentController.dismiss(completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func cancelAssetDownloads() {
|
||||
guard let downloadManager = self.controller?.downloadManager else {
|
||||
return
|
||||
@ -868,6 +878,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion)
|
||||
}
|
||||
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
||||
self?.currentGalleryParentController = c
|
||||
self?.controller?.present(c, in: .window(.root), with: a)
|
||||
}, finishedTransitionIn: { [weak self] in
|
||||
self?.openingMedia = false
|
||||
@ -906,6 +917,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
strongSelf.controller?.interaction?.sendSelected(result, silently, scheduleTime, false, completion)
|
||||
}
|
||||
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
|
||||
self?.currentGalleryParentController = c
|
||||
self?.controller?.present(c, in: .window(.root), with: a, blockInteraction: true)
|
||||
}, finishedTransitionIn: { [weak self] in
|
||||
self?.openingMedia = false
|
||||
@ -1686,6 +1698,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
super.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
public func closeGalleryController() {
|
||||
self.controllerNode.closeGalleryController()
|
||||
}
|
||||
|
||||
private weak var undoOverlayController: UndoOverlayController?
|
||||
private func showSelectionUndo(item: TGMediaSelectableItem) {
|
||||
let scale = min(2.0, UIScreenScale)
|
||||
|
@ -27,7 +27,7 @@ private final class FramePreviewContext {
|
||||
|
||||
private func initializedPreviewContext(queue: Queue, postbox: Postbox, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType, fileReference: FileMediaReference) -> Signal<QueueLocalObject<FramePreviewContext>, NoError> {
|
||||
return Signal { subscriber in
|
||||
let source = UniversalSoftwareVideoSource(mediaBox: postbox.mediaBox, userLocation: userLocation, userContentType: userContentType, fileReference: fileReference)
|
||||
let source = UniversalSoftwareVideoSource(mediaBox: postbox.mediaBox, source: .file(userLocation: userLocation, userContentType: userContentType, fileReference: fileReference))
|
||||
let readyDisposable = (source.ready
|
||||
|> filter { $0 }).start(next: { _ in
|
||||
subscriber.putNext(QueueLocalObject(queue: queue, generate: {
|
||||
|
@ -21,15 +21,17 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
||||
context.currentReadBytes += readCount
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
data = context.mediaBox.resourceData(context.fileReference.media.resource, size: context.size, in: requestRange, mode: .partial)
|
||||
|
||||
data = context.mediaBox.resourceData(context.source.resource, size: context.size, in: requestRange, mode: .partial)
|
||||
|
||||
let requiredDataIsNotLocallyAvailable = context.requiredDataIsNotLocallyAvailable
|
||||
var fetchedData: Data?
|
||||
let fetchDisposable = MetaDisposable()
|
||||
let isInitialized = context.videoStream != nil || context.automaticallyFetchHeader
|
||||
let mediaBox = context.mediaBox
|
||||
let userLocation = context.userLocation
|
||||
let userContentType = context.userContentType
|
||||
let reference = context.fileReference.resourceReference(context.fileReference.media.resource)
|
||||
|
||||
let source = context.source
|
||||
|
||||
let disposable = data.start(next: { result in
|
||||
let (data, isComplete) = result
|
||||
if data.count == readCount || isComplete {
|
||||
@ -37,7 +39,12 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
||||
semaphore.signal()
|
||||
} else {
|
||||
if isInitialized {
|
||||
fetchDisposable.set(fetchedMediaResource(mediaBox: mediaBox, userLocation: userLocation, userContentType: userContentType, reference: reference, ranges: [(requestRange, .maximum)]).start())
|
||||
switch source {
|
||||
case let .file(userLocation, userContentType, fileReference):
|
||||
fetchDisposable.set(fetchedMediaResource(mediaBox: mediaBox, userLocation: userLocation, userContentType: userContentType, reference: fileReference.resourceReference(fileReference.media.resource), ranges: [(requestRange, .maximum)]).start())
|
||||
case .direct:
|
||||
break
|
||||
}
|
||||
}
|
||||
requiredDataIsNotLocallyAvailable?()
|
||||
}
|
||||
@ -100,9 +107,7 @@ private final class SoftwareVideoStream {
|
||||
|
||||
private final class UniversalSoftwareVideoSourceImpl {
|
||||
fileprivate let mediaBox: MediaBox
|
||||
fileprivate let userLocation: MediaResourceUserLocation
|
||||
fileprivate let userContentType: MediaResourceUserContentType
|
||||
fileprivate let fileReference: FileMediaReference
|
||||
fileprivate let source: UniversalSoftwareVideoSource.Source
|
||||
fileprivate let size: Int64
|
||||
fileprivate let automaticallyFetchHeader: Bool
|
||||
|
||||
@ -119,16 +124,27 @@ private final class UniversalSoftwareVideoSourceImpl {
|
||||
fileprivate var currentNumberOfReads: Int = 0
|
||||
fileprivate var currentReadBytes: Int64 = 0
|
||||
|
||||
init?(mediaBox: MediaBox, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType, fileReference: FileMediaReference, state: ValuePromise<UniversalSoftwareVideoSourceState>, cancelInitialization: Signal<Bool, NoError>, automaticallyFetchHeader: Bool, hintVP9: Bool = false) {
|
||||
guard let size = fileReference.media.size else {
|
||||
return nil
|
||||
init?(
|
||||
mediaBox: MediaBox,
|
||||
source: UniversalSoftwareVideoSource.Source,
|
||||
state: ValuePromise<UniversalSoftwareVideoSourceState>,
|
||||
cancelInitialization: Signal<Bool, NoError>,
|
||||
automaticallyFetchHeader: Bool,
|
||||
hintVP9: Bool = false
|
||||
) {
|
||||
switch source {
|
||||
case let .file(_, _, fileReference):
|
||||
guard let size = fileReference.media.size else {
|
||||
return nil
|
||||
}
|
||||
self.size = size
|
||||
case let .direct(_, sizeValue):
|
||||
self.size = sizeValue
|
||||
}
|
||||
|
||||
|
||||
self.mediaBox = mediaBox
|
||||
self.userLocation = userLocation
|
||||
self.userContentType = userContentType
|
||||
self.fileReference = fileReference
|
||||
self.size = size
|
||||
self.source = source
|
||||
self.automaticallyFetchHeader = automaticallyFetchHeader
|
||||
|
||||
self.state = state
|
||||
@ -138,7 +154,15 @@ private final class UniversalSoftwareVideoSourceImpl {
|
||||
|
||||
let ioBufferSize = 1 * 1024
|
||||
|
||||
guard let avIoContext = FFMpegAVIOContext(bufferSize: Int32(ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, writePacket: nil, seek: seekCallback, isSeekable: true) else {
|
||||
let isSeekable: Bool
|
||||
switch source {
|
||||
case .file:
|
||||
isSeekable = true
|
||||
case .direct:
|
||||
isSeekable = false
|
||||
}
|
||||
|
||||
guard let avIoContext = FFMpegAVIOContext(bufferSize: Int32(ioBufferSize), opaqueContext: Unmanaged.passUnretained(self).toOpaque(), readPacket: readPacketCallback, writePacket: nil, seek: seekCallback, isSeekable: isSeekable) else {
|
||||
return nil
|
||||
}
|
||||
self.avIoContext = avIoContext
|
||||
@ -295,9 +319,7 @@ private enum UniversalSoftwareVideoSourceState {
|
||||
|
||||
private final class UniversalSoftwareVideoSourceThreadParams: NSObject {
|
||||
let mediaBox: MediaBox
|
||||
let userLocation: MediaResourceUserLocation
|
||||
let userContentType: MediaResourceUserContentType
|
||||
let fileReference: FileMediaReference
|
||||
let source: UniversalSoftwareVideoSource.Source
|
||||
let state: ValuePromise<UniversalSoftwareVideoSourceState>
|
||||
let cancelInitialization: Signal<Bool, NoError>
|
||||
let automaticallyFetchHeader: Bool
|
||||
@ -305,18 +327,14 @@ private final class UniversalSoftwareVideoSourceThreadParams: NSObject {
|
||||
|
||||
init(
|
||||
mediaBox: MediaBox,
|
||||
userLocation: MediaResourceUserLocation,
|
||||
userContentType: MediaResourceUserContentType,
|
||||
fileReference: FileMediaReference,
|
||||
source: UniversalSoftwareVideoSource.Source,
|
||||
state: ValuePromise<UniversalSoftwareVideoSourceState>,
|
||||
cancelInitialization: Signal<Bool, NoError>,
|
||||
automaticallyFetchHeader: Bool,
|
||||
hintVP9: Bool
|
||||
) {
|
||||
self.mediaBox = mediaBox
|
||||
self.userLocation = userLocation
|
||||
self.userContentType = userContentType
|
||||
self.fileReference = fileReference
|
||||
self.source = source
|
||||
self.state = state
|
||||
self.cancelInitialization = cancelInitialization
|
||||
self.automaticallyFetchHeader = automaticallyFetchHeader
|
||||
@ -345,7 +363,7 @@ private final class UniversalSoftwareVideoSourceThread: NSObject {
|
||||
let timer = Timer(fireAt: .distantFuture, interval: 0.0, target: UniversalSoftwareVideoSourceThread.self, selector: #selector(UniversalSoftwareVideoSourceThread.none), userInfo: nil, repeats: false)
|
||||
runLoop.add(timer, forMode: .common)
|
||||
|
||||
let source = UniversalSoftwareVideoSourceImpl(mediaBox: params.mediaBox, userLocation: params.userLocation, userContentType: params.userContentType, fileReference: params.fileReference, state: params.state, cancelInitialization: params.cancelInitialization, automaticallyFetchHeader: params.automaticallyFetchHeader)
|
||||
let source = UniversalSoftwareVideoSourceImpl(mediaBox: params.mediaBox, source: params.source, state: params.state, cancelInitialization: params.cancelInitialization, automaticallyFetchHeader: params.automaticallyFetchHeader)
|
||||
Thread.current.threadDictionary["source"] = source
|
||||
|
||||
while true {
|
||||
@ -387,6 +405,27 @@ public enum UniversalSoftwareVideoSourceTakeFrameResult {
|
||||
}
|
||||
|
||||
public final class UniversalSoftwareVideoSource {
|
||||
public enum Source {
|
||||
case file(
|
||||
userLocation: MediaResourceUserLocation,
|
||||
userContentType: MediaResourceUserContentType,
|
||||
fileReference: FileMediaReference
|
||||
)
|
||||
case direct(
|
||||
resource: MediaResource,
|
||||
size: Int64
|
||||
)
|
||||
|
||||
var resource: MediaResource {
|
||||
switch self {
|
||||
case let .file(_, _, fileReference):
|
||||
return fileReference.media.resource
|
||||
case let .direct(resource, _):
|
||||
return resource
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let thread: Thread
|
||||
private let stateValue: ValuePromise<UniversalSoftwareVideoSourceState> = ValuePromise(.initializing, ignoreRepeated: true)
|
||||
private let cancelInitialization: ValuePromise<Bool> = ValuePromise(false)
|
||||
@ -403,8 +442,8 @@ public final class UniversalSoftwareVideoSource {
|
||||
}
|
||||
}
|
||||
|
||||
public init(mediaBox: MediaBox, userLocation: MediaResourceUserLocation, userContentType: MediaResourceUserContentType, fileReference: FileMediaReference, automaticallyFetchHeader: Bool = false, hintVP9: Bool = false) {
|
||||
self.thread = Thread(target: UniversalSoftwareVideoSourceThread.self, selector: #selector(UniversalSoftwareVideoSourceThread.entryPoint(_:)), object: UniversalSoftwareVideoSourceThreadParams(mediaBox: mediaBox, userLocation: userLocation, userContentType: userContentType, fileReference: fileReference, state: self.stateValue, cancelInitialization: self.cancelInitialization.get(), automaticallyFetchHeader: automaticallyFetchHeader, hintVP9: hintVP9))
|
||||
public init(mediaBox: MediaBox, source: Source, automaticallyFetchHeader: Bool = false, hintVP9: Bool = false) {
|
||||
self.thread = Thread(target: UniversalSoftwareVideoSourceThread.self, selector: #selector(UniversalSoftwareVideoSourceThread.entryPoint(_:)), object: UniversalSoftwareVideoSourceThreadParams(mediaBox: mediaBox, source: source, state: self.stateValue, cancelInitialization: self.cancelInitialization.get(), automaticallyFetchHeader: automaticallyFetchHeader, hintVP9: hintVP9))
|
||||
self.thread.name = "UniversalSoftwareVideoSource"
|
||||
self.thread.start()
|
||||
}
|
||||
|
@ -120,8 +120,8 @@
|
||||
{
|
||||
if (request.requestContext != nil)
|
||||
{
|
||||
//[_dropReponseContexts addObject:[[MTDropResponseContext alloc] initWithDropMessageId:request.requestContext.messageId]];
|
||||
//anyNewDropRequests = true;
|
||||
[_dropReponseContexts addObject:[[MTDropResponseContext alloc] initWithDropMessageId:request.requestContext.messageId]];
|
||||
anyNewDropRequests = true;
|
||||
}
|
||||
|
||||
if (request.requestContext.messageId != 0) {
|
||||
@ -902,7 +902,7 @@
|
||||
|
||||
if (!requestFound) {
|
||||
if (MTLogEnabled()) {
|
||||
MTLog(@"[MTRequestMessageService#%p response %" PRId64 " didn't match any request]", self, message.messageId);
|
||||
MTLog(@"[MTRequestMessageService#%p response %" PRId64 " for % " PRId64 " didn't match any request]", self, message.messageId, rpcResultMessage.requestMessageId);
|
||||
}
|
||||
}
|
||||
else if (_requests.count == 0)
|
||||
|
@ -2792,6 +2792,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
self.dismiss()
|
||||
self.wasDismissed?()
|
||||
}
|
||||
|
||||
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
|
@ -181,7 +181,7 @@ func telegramMediaFileFromApiDocument(_ document: Api.Document) -> TelegramMedia
|
||||
}
|
||||
}
|
||||
|
||||
return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), partialReference: nil, resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: size, fileReference: fileReference.makeData(), fileName: fileNameFromFileAttributes(parsedAttributes)), previewRepresentations: previewRepresentations, videoThumbnails: videoThumbnails, immediateThumbnailData: immediateThumbnail, mimeType: mimeType, size: size, attributes: parsedAttributes)
|
||||
return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), partialReference: nil, resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: size, fileReference: fileReference.makeData(), fileName: fileNameFromFileAttributes(parsedAttributes)), previewRepresentations: previewRepresentations, videoThumbnails: videoThumbnails, immediateThumbnailData: immediateThumbnail, mimeType: mimeType, size: size, attributes: parsedAttributes)
|
||||
case .documentEmpty:
|
||||
return nil
|
||||
}
|
||||
|
@ -2425,7 +2425,18 @@ public final class EntityInputView: UIInputView, AttachmentTextInputPanelInputVi
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
if controller.navigationController != nil {
|
||||
controller.replace(with: c)
|
||||
} else {
|
||||
controller.dismiss()
|
||||
|
||||
if let self {
|
||||
self.presentController?(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
strongSelf.presentController?(controller)
|
||||
}), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }))
|
||||
|
@ -124,9 +124,11 @@ private final class FrameSequenceThumbnailNode: ASDisplayNode {
|
||||
|
||||
let source = UniversalSoftwareVideoSource(
|
||||
mediaBox: self.context.account.postbox.mediaBox,
|
||||
userLocation: userLocation,
|
||||
userContentType: .other,
|
||||
fileReference: self.file,
|
||||
source: .file(
|
||||
userLocation: userLocation,
|
||||
userContentType: .other,
|
||||
fileReference: self.file
|
||||
),
|
||||
automaticallyFetchHeader: true
|
||||
)
|
||||
self.sources.append(source)
|
||||
|
@ -407,7 +407,7 @@ final class StoryItemContentComponent: Component {
|
||||
}
|
||||
progress = min(1.0, progress)
|
||||
|
||||
if actualTimestamp < 0.1 {
|
||||
if actualTimestamp < 0.3 {
|
||||
isBuffering = false
|
||||
}
|
||||
|
||||
|
@ -1392,6 +1392,12 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
|
||||
self.sendMessageContext.animateOut(bounds: self.bounds)
|
||||
|
||||
self.sendMessageContext.tooltipScreen?.dismiss()
|
||||
self.sendMessageContext.tooltipScreen = nil
|
||||
|
||||
self.contextController?.dismiss()
|
||||
self.contextController = nil
|
||||
|
||||
if let inputPanelView = self.inputPanel.view {
|
||||
inputPanelView.layer.animatePosition(
|
||||
from: CGPoint(),
|
||||
@ -2508,7 +2514,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
let tooltipText: String
|
||||
switch storyPrivacyIcon {
|
||||
case .closeFriends:
|
||||
tooltipText = component.strings.Story_TooltipPrivacyCloseFriends(component.slice.peer.compactDisplayTitle).string
|
||||
tooltipText = component.strings.Story_TooltipPrivacyCloseFriends2(component.slice.peer.compactDisplayTitle).string
|
||||
case .contacts:
|
||||
tooltipText = component.strings.Story_TooltipPrivacyContacts(component.slice.peer.compactDisplayTitle).string
|
||||
case .selectedContacts:
|
||||
@ -2520,7 +2526,10 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
let tooltipScreen = TooltipScreen(
|
||||
account: component.context.account,
|
||||
sharedContext: component.context.sharedContext,
|
||||
text: .markdown(text: tooltipText), style: .default, location: TooltipScreen.Location.point(closeFriendIconView.convert(closeFriendIconView.bounds, to: nil).offsetBy(dx: 1.0, dy: 6.0), .top), displayDuration: .infinite, shouldDismissOnTouch: { _, _ in
|
||||
text: .markdown(text: tooltipText),
|
||||
balancedTextLayout: true,
|
||||
style: .default,
|
||||
location: TooltipScreen.Location.point(closeFriendIconView.convert(closeFriendIconView.bounds, to: nil).offsetBy(dx: 1.0, dy: 6.0), .top), displayDuration: .infinite, shouldDismissOnTouch: { _, _ in
|
||||
return .dismiss(consume: true)
|
||||
}
|
||||
)
|
||||
@ -3376,23 +3385,41 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
break
|
||||
}
|
||||
|
||||
if subject != nil || chat {
|
||||
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peer), subject: subject, keepStack: .always, animated: true, pushController: { [weak controller, weak navigationController] chatController, animated, completion in
|
||||
guard let controller, let navigationController else {
|
||||
return
|
||||
}
|
||||
if "".isEmpty {
|
||||
navigationController.pushViewController(chatController)
|
||||
if subject != nil || chat {
|
||||
if let index = navigationController.viewControllers.firstIndex(where: { c in
|
||||
if let c = c as? ChatController, case .peer(peer.id) = c.chatLocation {
|
||||
return true
|
||||
} else {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
if let index = viewControllers.firstIndex(where: { $0 === controller }) {
|
||||
viewControllers.insert(chatController, at: index)
|
||||
} else {
|
||||
viewControllers.append(chatController)
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: animated)
|
||||
return false
|
||||
}
|
||||
}))
|
||||
}) {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
for i in ((index + 1) ..< viewControllers.count).reversed() {
|
||||
if viewControllers[i] !== controller {
|
||||
viewControllers.remove(at: i)
|
||||
}
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: true)
|
||||
|
||||
controller.dismissWithoutTransitionOut()
|
||||
} else {
|
||||
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peer), subject: subject, keepStack: .always, animated: true, pushController: { [weak controller, weak navigationController] chatController, animated, completion in
|
||||
guard let controller, let navigationController else {
|
||||
return
|
||||
}
|
||||
if "".isEmpty {
|
||||
navigationController.pushViewController(chatController)
|
||||
} else {
|
||||
var viewControllers = navigationController.viewControllers
|
||||
if let index = viewControllers.firstIndex(where: { $0 === controller }) {
|
||||
viewControllers.insert(chatController, at: index)
|
||||
} else {
|
||||
viewControllers.append(chatController)
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: animated)
|
||||
}
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
var currentViewControllers = navigationController.viewControllers
|
||||
if let index = currentViewControllers.firstIndex(where: { c in
|
||||
@ -3652,6 +3679,10 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
}
|
||||
|
||||
private func performMoreAction(sourceView: UIView, gesture: ContextGesture?) {
|
||||
if self.isAnimatingOut {
|
||||
return
|
||||
}
|
||||
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
@ -1684,11 +1684,11 @@ final class StoryItemSetContainerSendMessage {
|
||||
done(time)
|
||||
})
|
||||
}
|
||||
controller.getCaptionPanelView = { [weak self, weak view] in
|
||||
guard let self, let view else {
|
||||
controller.getCaptionPanelView = { [weak self, weak controller, weak view] in
|
||||
guard let self, let view, let controller else {
|
||||
return nil
|
||||
}
|
||||
return self.getCaptionPanelView(view: view, peer: peer)
|
||||
return self.getCaptionPanelView(view: view, peer: peer, mediaPicker: controller)
|
||||
}
|
||||
controller.legacyCompletion = { signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion in
|
||||
completion(signals, silently, scheduleTime, getAnimatedTransitionSource, sendCompletion)
|
||||
@ -2067,7 +2067,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
})
|
||||
}
|
||||
|
||||
private func getCaptionPanelView(view: StoryItemSetContainerComponent.View, peer: EnginePeer) -> TGCaptionPanelView? {
|
||||
private func getCaptionPanelView(view: StoryItemSetContainerComponent.View, peer: EnginePeer, mediaPicker: MediaPickerScreen? = nil) -> TGCaptionPanelView? {
|
||||
guard let component = view.component else {
|
||||
return nil
|
||||
}
|
||||
@ -2081,7 +2081,27 @@ final class StoryItemSetContainerSendMessage {
|
||||
guard let view else {
|
||||
return
|
||||
}
|
||||
view.component?.controller()?.presentInGlobalOverlay(c)
|
||||
if let c = c as? PremiumIntroScreen {
|
||||
view.endEditing(true)
|
||||
if let mediaPicker {
|
||||
mediaPicker.closeGalleryController()
|
||||
}
|
||||
if let attachmentController = self.attachmentController {
|
||||
self.attachmentController = nil
|
||||
attachmentController.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
c.wasDismissed = { [weak view] in
|
||||
guard let view else {
|
||||
return
|
||||
}
|
||||
view.updateIsProgressPaused()
|
||||
}
|
||||
view.component?.controller()?.push(c)
|
||||
|
||||
view.updateIsProgressPaused()
|
||||
} else {
|
||||
view.component?.controller()?.presentInGlobalOverlay(c)
|
||||
}
|
||||
}) as? TGCaptionPanelView
|
||||
}
|
||||
|
||||
|
@ -831,7 +831,7 @@ final class StoryItemSetViewListComponent: Component {
|
||||
transition: emptyTransition,
|
||||
component: AnyComponent(AnimatedStickerComponent(
|
||||
account: component.context.account,
|
||||
animation: AnimatedStickerComponent.Animation(source: .bundle(name: "Burn"), loop: true),
|
||||
animation: AnimatedStickerComponent.Animation(source: .bundle(name: "ChatListNoResults"), loop: true),
|
||||
size: CGSize(width: 140.0, height: 140.0)
|
||||
)),
|
||||
environment: {},
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "smoothGradient 0.6.png",
|
||||
"filename" : "smoothGradient 0.4.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
BIN
submodules/TelegramUI/Images.xcassets/Stories/PanelGradient.imageset/smoothGradient 0.4.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Stories/PanelGradient.imageset/smoothGradient 0.4.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
@ -1452,7 +1452,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
let state: SemanticStatusNodeState
|
||||
var state: SemanticStatusNodeState
|
||||
var streamingState: SemanticStatusNodeState = .none
|
||||
|
||||
let isSending = message.flags.isSending
|
||||
@ -1563,6 +1563,20 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
||||
streamingState = .none
|
||||
}
|
||||
|
||||
if isSending {
|
||||
if case .progress = streamingState {
|
||||
} else {
|
||||
let adjustedProgress: CGFloat = 0.027
|
||||
streamingState = .progress(value: CGFloat(adjustedProgress), cancelEnabled: true, appearance: .init(inset: 1.0, lineWidth: 2.0))
|
||||
}
|
||||
|
||||
if case .progress = state {
|
||||
} else {
|
||||
let adjustedProgress: CGFloat = 0.027
|
||||
state = .progress(value: CGFloat(adjustedProgress), cancelEnabled: true, appearance: .init(inset: 1.0, lineWidth: 2.0))
|
||||
}
|
||||
}
|
||||
|
||||
let backgroundNodeColor: UIColor
|
||||
let foregroundNodeColor: UIColor
|
||||
if self.iconNode != nil {
|
||||
|
@ -19,6 +19,7 @@ import GZip
|
||||
import TelegramUniversalVideoContent
|
||||
import GradientBackground
|
||||
import Svg
|
||||
import UniversalMediaPlayer
|
||||
|
||||
public func fetchCachedResourceRepresentation(account: Account, resource: MediaResource, representation: CachedMediaResourceRepresentation) -> Signal<CachedMediaResourceRepresentationResult, NoError> {
|
||||
if let representation = representation as? CachedStickerAJpegRepresentation {
|
||||
@ -38,7 +39,33 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
|
||||
return fetchCachedScaledImageRepresentation(resource: resource, resourceData: data, representation: representation)
|
||||
}
|
||||
} else if let _ = representation as? CachedVideoFirstFrameRepresentation {
|
||||
return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
||||
return Signal { subscriber in
|
||||
if let size = resource.size {
|
||||
let videoSource = UniversalSoftwareVideoSource(mediaBox: account.postbox.mediaBox, source: .direct(resource: resource, size: size), automaticallyFetchHeader: false, hintVP9: false)
|
||||
let disposable = videoSource.takeFrame(at: 0.0).start(next: { value in
|
||||
switch value {
|
||||
case let .image(image):
|
||||
if let image {
|
||||
if let imageData = image.jpegData(compressionQuality: 0.6) {
|
||||
subscriber.putNext(.data(imageData))
|
||||
subscriber.putNext(.done)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
case .waitingForData:
|
||||
break
|
||||
}
|
||||
})
|
||||
return ActionDisposable {
|
||||
// keep the reference
|
||||
let _ = videoSource.takeFrame(at: 0.0)
|
||||
disposable.dispose()
|
||||
}
|
||||
} else {
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
/*return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
||||
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
||||
if data.complete {
|
||||
return fetchCachedVideoFirstFrameRepresentation(account: account, resource: resource, resourceData: data)
|
||||
@ -50,7 +77,7 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}*/
|
||||
} else if let representation = representation as? CachedScaledVideoFirstFrameRepresentation {
|
||||
return account.postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
||||
|> mapToSignal { data -> Signal<CachedMediaResourceRepresentationResult, NoError> in
|
||||
|
@ -65,9 +65,11 @@ private final class FrameSequenceThumbnailNode: ASDisplayNode {
|
||||
|
||||
let source = UniversalSoftwareVideoSource(
|
||||
mediaBox: self.context.account.postbox.mediaBox,
|
||||
userLocation: userLocation,
|
||||
userContentType: .other,
|
||||
fileReference: self.file,
|
||||
source: .file(
|
||||
userLocation: userLocation,
|
||||
userContentType: .other,
|
||||
fileReference: self.file
|
||||
),
|
||||
automaticallyFetchHeader: true
|
||||
)
|
||||
self.sources.append(source)
|
||||
|
@ -24,6 +24,7 @@ swift_library(
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -16,6 +16,7 @@ import ComponentFlow
|
||||
import AvatarStoryIndicatorComponent
|
||||
import AccountContext
|
||||
import Markdown
|
||||
import BalancedTextComponent
|
||||
|
||||
public enum TooltipActiveTextItem {
|
||||
case url(String, Bool)
|
||||
@ -107,6 +108,9 @@ private class DownArrowsIconNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
private let text: TooltipScreen.Text
|
||||
private let textAlignment: TooltipScreen.Alignment
|
||||
private let balancedTextLayout: Bool
|
||||
private let tooltipStyle: TooltipScreen.Style
|
||||
private let icon: TooltipScreen.Icon?
|
||||
private let action: TooltipScreen.Action?
|
||||
@ -136,12 +140,13 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
private var downArrowsNode: DownArrowsIconNode?
|
||||
private var avatarNode: AvatarNode?
|
||||
private var avatarStoryIndicator: ComponentView<Empty>?
|
||||
private let textNode: ImmediateTextNode
|
||||
private let textView = ComponentView<Empty>()
|
||||
private var closeButtonNode: HighlightableButtonNode?
|
||||
private var actionButtonNode: HighlightableButtonNode?
|
||||
|
||||
private var isArrowInverted: Bool = false
|
||||
|
||||
private let fontSize: CGFloat
|
||||
private let inset: CGFloat
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
@ -152,6 +157,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
sharedContext: SharedAccountContext,
|
||||
text: TooltipScreen.Text,
|
||||
textAlignment: TooltipScreen.Alignment,
|
||||
balancedTextLayout: Bool,
|
||||
style: TooltipScreen.Style,
|
||||
icon: TooltipScreen.Icon? = nil,
|
||||
action: TooltipScreen.Action? = nil,
|
||||
@ -337,39 +343,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
self.backgroundMaskNode.layer.rasterizationScale = UIScreen.main.scale
|
||||
}
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
let baseFont = Font.regular(fontSize)
|
||||
let boldFont = Font.semibold(14.0)
|
||||
let italicFont = Font.italic(fontSize)
|
||||
let boldItalicFont = Font.semiboldItalic(fontSize)
|
||||
let fixedFont = Font.monospace(fontSize)
|
||||
|
||||
let textColor: UIColor = .white
|
||||
|
||||
let attributedText: NSAttributedString
|
||||
switch text {
|
||||
case let .plain(text):
|
||||
attributedText = NSAttributedString(string: text, font: baseFont, textColor: textColor)
|
||||
case let .entities(text, entities):
|
||||
attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: baseFont, linkFont: baseFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: fixedFont, blockQuoteFont: baseFont, underlineLinks: true, external: false, message: nil)
|
||||
case let .markdown(text):
|
||||
let linkColor = UIColor(rgb: 0x64d2ff)
|
||||
let markdownAttributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: baseFont, textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: boldFont, textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: boldFont, textColor: linkColor),
|
||||
linkAttribute: { _ in
|
||||
return nil
|
||||
}
|
||||
)
|
||||
attributedText = parseMarkdownIntoAttributedString(text, attributes: markdownAttributes)
|
||||
}
|
||||
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.textAlignment = textAlignment == .center ? .center : .natural
|
||||
self.fontSize = fontSize
|
||||
self.text = text
|
||||
self.textAlignment = textAlignment
|
||||
self.balancedTextLayout = balancedTextLayout
|
||||
|
||||
self.animatedStickerNode = DefaultAnimatedStickerNodeImpl()
|
||||
switch icon {
|
||||
@ -403,7 +380,6 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
self.backgroundContainerNode.addSubnode(effectNode)
|
||||
self.backgroundContainerNode.layer.mask = self.backgroundMaskNode.layer
|
||||
}
|
||||
self.containerNode.addSubnode(self.textNode)
|
||||
self.containerNode.addSubnode(self.animatedStickerNode)
|
||||
|
||||
if let closeButtonNode = self.closeButtonNode {
|
||||
@ -428,65 +404,6 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
self.actionButtonNode = actionButtonNode
|
||||
}
|
||||
|
||||
self.textNode.linkHighlightColor = UIColor.white.withAlphaComponent(0.5)
|
||||
self.textNode.highlightAttributeAction = { attributes in
|
||||
let highlightedAttributes = [
|
||||
TelegramTextAttributes.URL,
|
||||
TelegramTextAttributes.PeerMention,
|
||||
TelegramTextAttributes.PeerTextMention,
|
||||
TelegramTextAttributes.BotCommand,
|
||||
TelegramTextAttributes.Hashtag
|
||||
]
|
||||
|
||||
for attribute in highlightedAttributes {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: attribute)] {
|
||||
return NSAttributedString.Key(rawValue: attribute)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
self.textNode.tapAttributeAction = { [weak self] attributes, index in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let (attributeText, fullText) = strongSelf.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
openActiveTextItem?(.url(url, concealed), .tap)
|
||||
} else if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
openActiveTextItem?(.mention(mention.peerId, mention.mention), .tap)
|
||||
} else if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
openActiveTextItem?(.textMention(mention), .tap)
|
||||
} else if let command = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
openActiveTextItem?(.botCommand(command), .tap)
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
openActiveTextItem?(.hashtag(hashtag.hashtag), .tap)
|
||||
}
|
||||
}
|
||||
|
||||
self.textNode.longTapAttributeAction = { [weak self] attributes, index in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let (attributeText, fullText) = strongSelf.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
openActiveTextItem?(.url(url, concealed), .longTap)
|
||||
} else if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
openActiveTextItem?(.mention(mention.peerId, mention.mention), .longTap)
|
||||
} else if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
openActiveTextItem?(.textMention(mention), .longTap)
|
||||
} else if let command = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
openActiveTextItem?(.botCommand(command), .longTap)
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
openActiveTextItem?(.hashtag(hashtag.hashtag), .longTap)
|
||||
}
|
||||
}
|
||||
|
||||
self.actionButtonNode?.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside)
|
||||
self.closeButtonNode?.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
@ -555,7 +472,105 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
buttonInset += 24.0
|
||||
}
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing - buttonInset, height: .greatestFiniteMagnitude))
|
||||
let baseFont = Font.regular(self.fontSize)
|
||||
let boldFont = Font.semibold(14.0)
|
||||
let italicFont = Font.italic(self.fontSize)
|
||||
let boldItalicFont = Font.semiboldItalic(self.fontSize)
|
||||
let fixedFont = Font.monospace(self.fontSize)
|
||||
|
||||
let textColor: UIColor = .white
|
||||
let attributedText: NSAttributedString
|
||||
switch self.text {
|
||||
case let .plain(text):
|
||||
attributedText = NSAttributedString(string: text, font: baseFont, textColor: textColor)
|
||||
case let .entities(text, entities):
|
||||
attributedText = stringWithAppliedEntities(text, entities: entities, baseColor: textColor, linkColor: textColor, baseFont: baseFont, linkFont: baseFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: fixedFont, blockQuoteFont: baseFont, underlineLinks: true, external: false, message: nil)
|
||||
case let .markdown(text):
|
||||
let linkColor = UIColor(rgb: 0x64d2ff)
|
||||
let markdownAttributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: baseFont, textColor: textColor),
|
||||
bold: MarkdownAttributeSet(font: boldFont, textColor: textColor),
|
||||
link: MarkdownAttributeSet(font: boldFont, textColor: linkColor),
|
||||
linkAttribute: { _ in
|
||||
return nil
|
||||
}
|
||||
)
|
||||
attributedText = parseMarkdownIntoAttributedString(text, attributes: markdownAttributes)
|
||||
}
|
||||
|
||||
let highlightColor: UIColor? = UIColor.white.withAlphaComponent(0.5)
|
||||
let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = { attributes in
|
||||
let highlightedAttributes = [
|
||||
TelegramTextAttributes.URL,
|
||||
TelegramTextAttributes.PeerMention,
|
||||
TelegramTextAttributes.PeerTextMention,
|
||||
TelegramTextAttributes.BotCommand,
|
||||
TelegramTextAttributes.Hashtag
|
||||
]
|
||||
|
||||
for attribute in highlightedAttributes {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: attribute)] {
|
||||
return NSAttributedString.Key(rawValue: attribute)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
let tapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = { [weak self] attributes, index in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let (attributeText, fullText) = (strongSelf.textView.view as? BalancedTextComponent.View)?.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
strongSelf.openActiveTextItem?(.url(url, concealed), .tap)
|
||||
} else if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
strongSelf.openActiveTextItem?(.mention(mention.peerId, mention.mention), .tap)
|
||||
} else if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
strongSelf.openActiveTextItem?(.textMention(mention), .tap)
|
||||
} else if let command = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
strongSelf.openActiveTextItem?(.botCommand(command), .tap)
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
strongSelf.openActiveTextItem?(.hashtag(hashtag.hashtag), .tap)
|
||||
}
|
||||
}
|
||||
let longTapAction: (([NSAttributedString.Key: Any], Int) -> Void)? = { [weak self] attributes, index in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let (attributeText, fullText) = (strongSelf.textView.view as? BalancedTextComponent.View)?.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
concealed = !doesUrlMatchText(url: url, text: attributeText, fullText: fullText)
|
||||
}
|
||||
strongSelf.openActiveTextItem?(.url(url, concealed), .longTap)
|
||||
} else if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerMention)] as? TelegramPeerMention {
|
||||
strongSelf.openActiveTextItem?(.mention(mention.peerId, mention.mention), .longTap)
|
||||
} else if let mention = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
strongSelf.openActiveTextItem?(.textMention(mention), .longTap)
|
||||
} else if let command = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.BotCommand)] as? String {
|
||||
strongSelf.openActiveTextItem?(.botCommand(command), .longTap)
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
strongSelf.openActiveTextItem?(.hashtag(hashtag.hashtag), .longTap)
|
||||
}
|
||||
}
|
||||
|
||||
let textSize = self.textView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BalancedTextComponent(
|
||||
text: .plain(attributedText),
|
||||
balanced: self.balancedTextLayout,
|
||||
horizontalAlignment: self.textAlignment == .center ? .center : .left,
|
||||
maximumNumberOfLines: 0,
|
||||
highlightColor: highlightColor,
|
||||
highlightAction: highlightAction,
|
||||
tapAction: tapAction,
|
||||
longTapAction: longTapAction
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing - buttonInset, height: 1000000.0)
|
||||
)
|
||||
|
||||
var backgroundFrame: CGRect
|
||||
|
||||
@ -668,7 +683,15 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize)
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
|
||||
if let textComponentView = self.textView.view {
|
||||
if textComponentView.superview == nil {
|
||||
textComponentView.layer.anchorPoint = CGPoint()
|
||||
self.containerNode.view.addSubview(textComponentView)
|
||||
}
|
||||
transition.updatePosition(layer: textComponentView.layer, position: textFrame.origin)
|
||||
transition.updateBounds(layer: textComponentView.layer, bounds: CGRect(origin: CGPoint(), size: textFrame.size))
|
||||
}
|
||||
|
||||
if let closeButtonNode = self.closeButtonNode {
|
||||
let closeSize = CGSize(width: 44.0, height: 44.0)
|
||||
@ -746,7 +769,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
private var didRequestDismiss = false
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let event = event {
|
||||
if let _ = self.openActiveTextItem, let result = self.textNode.hitTest(self.view.convert(point, to: self.textNode.view), with: event) {
|
||||
if let _ = self.openActiveTextItem, let textComponentView = self.textView.view, let result = textComponentView.hitTest(self.view.convert(point, to: textComponentView), with: event) {
|
||||
return result
|
||||
}
|
||||
|
||||
@ -940,6 +963,7 @@ public final class TooltipScreen: ViewController {
|
||||
private let sharedContext: SharedAccountContext
|
||||
public let text: TooltipScreen.Text
|
||||
public let textAlignment: TooltipScreen.Alignment
|
||||
private let balancedTextLayout: Bool
|
||||
private let style: TooltipScreen.Style
|
||||
private let icon: TooltipScreen.Icon?
|
||||
private let action: TooltipScreen.Action?
|
||||
@ -976,6 +1000,7 @@ public final class TooltipScreen: ViewController {
|
||||
sharedContext: SharedAccountContext,
|
||||
text: TooltipScreen.Text,
|
||||
textAlignment: TooltipScreen.Alignment = .natural,
|
||||
balancedTextLayout: Bool = false,
|
||||
style: TooltipScreen.Style = .default,
|
||||
icon: TooltipScreen.Icon? = nil,
|
||||
action: TooltipScreen.Action? = nil,
|
||||
@ -991,6 +1016,7 @@ public final class TooltipScreen: ViewController {
|
||||
self.sharedContext = sharedContext
|
||||
self.text = text
|
||||
self.textAlignment = textAlignment
|
||||
self.balancedTextLayout = balancedTextLayout
|
||||
self.style = style
|
||||
self.icon = icon
|
||||
self.action = action
|
||||
@ -1057,7 +1083,7 @@ public final class TooltipScreen: ViewController {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = TooltipScreenNode(context: self.context, account: self.account, sharedContext: self.sharedContext, text: self.text, textAlignment: self.textAlignment, style: self.style, icon: self.icon, action: self.action, location: self.location, displayDuration: self.displayDuration, inset: self.inset, cornerRadius: self.cornerRadius, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in
|
||||
self.displayNode = TooltipScreenNode(context: self.context, account: self.account, sharedContext: self.sharedContext, text: self.text, textAlignment: self.textAlignment, balancedTextLayout: self.balancedTextLayout, style: self.style, icon: self.icon, action: self.action, location: self.location, displayDuration: self.displayDuration, inset: self.inset, cornerRadius: self.cornerRadius, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user