mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
92a6a73f6f
commit
1fb93ab298
1
.cursorignore
Normal file
1
.cursorignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
|
@ -32,7 +32,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
private var originalContent: BrowserContent?
|
private var originalContent: BrowserContent?
|
||||||
private let url: String
|
private let url: String
|
||||||
|
|
||||||
private var webPage: TelegramMediaWebpage?
|
private var webPage: (webPage: TelegramMediaWebpage, instantPage: InstantPage?)?
|
||||||
|
|
||||||
let uuid: UUID
|
let uuid: UUID
|
||||||
|
|
||||||
@ -97,7 +97,11 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, webPage: TelegramMediaWebpage, anchor: String?, url: String, sourceLocation: InstantPageSourceLocation, preloadedResouces: [Any]?, originalContent: BrowserContent? = nil) {
|
init(context: AccountContext, presentationData: PresentationData, webPage: TelegramMediaWebpage, anchor: String?, url: String, sourceLocation: InstantPageSourceLocation, preloadedResouces: [Any]?, originalContent: BrowserContent? = nil) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.webPage = webPage
|
var instantPage: InstantPage?
|
||||||
|
if case let .Loaded(content) = webPage.content {
|
||||||
|
instantPage = content.instantPage?._parse()
|
||||||
|
}
|
||||||
|
self.webPage = (webPage, instantPage)
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.theme = instantPageThemeForType(presentationData.theme.overallDarkAppearance ? .dark : .light, settings: .defaultSettings)
|
self.theme = instantPageThemeForType(presentationData.theme.overallDarkAppearance ? .dark : .light, settings: .defaultSettings)
|
||||||
self.sourceLocation = sourceLocation
|
self.sourceLocation = sourceLocation
|
||||||
@ -267,7 +271,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateWebPage(_ webPage: TelegramMediaWebpage?, anchor: String?, state: InstantPageStoredState? = nil) {
|
private func updateWebPage(_ webPage: TelegramMediaWebpage?, anchor: String?, state: InstantPageStoredState? = nil) {
|
||||||
if self.webPage != webPage {
|
if self.webPage?.webPage != webPage {
|
||||||
if self.webPage != nil && self.currentLayout != nil {
|
if self.webPage != nil && self.currentLayout != nil {
|
||||||
if let snapshotView = self.scrollNode.view.snapshotView(afterScreenUpdates: false) {
|
if let snapshotView = self.scrollNode.view.snapshotView(afterScreenUpdates: false) {
|
||||||
snapshotView.frame = self.scrollNode.frame
|
snapshotView.frame = self.scrollNode.frame
|
||||||
@ -279,7 +283,15 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.setupScrollOffsetOnLayout = self.webPage == nil
|
self.setupScrollOffsetOnLayout = self.webPage == nil
|
||||||
self.webPage = webPage
|
if let webPage {
|
||||||
|
var instantPage: InstantPage?
|
||||||
|
if case let .Loaded(content) = webPage.content {
|
||||||
|
instantPage = content.instantPage?._parse()
|
||||||
|
}
|
||||||
|
self.webPage = (webPage, instantPage)
|
||||||
|
} else {
|
||||||
|
self.webPage = nil
|
||||||
|
}
|
||||||
if let anchor = anchor {
|
if let anchor = anchor {
|
||||||
self.initialAnchor = anchor.removingPercentEncoding
|
self.initialAnchor = anchor.removingPercentEncoding
|
||||||
} else if let state = state {
|
} else if let state = state {
|
||||||
@ -455,11 +467,11 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updatePageLayout() {
|
private func updatePageLayout() {
|
||||||
guard let (size, insets, _) = self.containerLayout, let webPage = self.webPage else {
|
guard let (size, insets, _) = self.containerLayout, let (webPage, instantPage) = self.webPage else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentLayout = instantPageLayoutForWebPage(webPage, userLocation: self.sourceLocation.userLocation, boundingWidth: size.width, safeInset: insets.left, strings: self.presentationData.strings, theme: self.theme, dateTimeFormat: self.presentationData.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights)
|
let currentLayout = instantPageLayoutForWebPage(webPage, instantPage: instantPage, userLocation: self.sourceLocation.userLocation, boundingWidth: size.width, safeInset: insets.left, strings: self.presentationData.strings, theme: self.theme, dateTimeFormat: self.presentationData.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights)
|
||||||
|
|
||||||
for (_, tileNode) in self.visibleTiles {
|
for (_, tileNode) in self.visibleTiles {
|
||||||
tileNode.removeFromSupernode()
|
tileNode.removeFromSupernode()
|
||||||
@ -920,7 +932,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let page = content.instantPage, page.url == baseUrl || baseUrl.isEmpty, let anchor = anchor {
|
if let page = self.webPage?.instantPage, page.url == baseUrl || baseUrl.isEmpty, let anchor = anchor {
|
||||||
self.scrollToAnchor(anchor)
|
self.scrollToAnchor(anchor)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1029,7 +1041,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openMedia(_ media: InstantPageMedia) {
|
private func openMedia(_ media: InstantPageMedia) {
|
||||||
guard let items = self.currentLayout?.items, let webPage = self.webPage else {
|
guard let items = self.currentLayout?.items, let (webPage, _) = self.webPage else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1157,7 +1169,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
let _ = saveToCameraRoll(context: self.context, postbox: self.context.account.postbox, userLocation: self.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start()
|
let _ = saveToCameraRoll(context: self.context, postbox: self.context.account.postbox, userLocation: self.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start()
|
||||||
}
|
}
|
||||||
}), ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuShare, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuShare), action: { [weak self] in
|
}), ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuShare, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuShare), action: { [weak self] in
|
||||||
if let self, let webPage = self.webPage, let image = media.media._asMedia() as? TelegramMediaImage {
|
if let self, let (webPage, _) = self.webPage, let image = media.media._asMedia() as? TelegramMediaImage {
|
||||||
self.present(ShareController(context: self.context, subject: .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.media(media: .webPage(webPage: WebpageReference(webPage), media: image), resource: $0.resource)) }))), nil)
|
self.present(ShareController(context: self.context, subject: .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.media(media: .webPage(webPage: WebpageReference(webPage), media: image), resource: $0.resource)) }))), nil)
|
||||||
}
|
}
|
||||||
})], catchTapsOutside: true)
|
})], catchTapsOutside: true)
|
||||||
@ -1300,7 +1312,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||||
}
|
}
|
||||||
}), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in
|
}), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in
|
||||||
if let strongSelf = self, let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content {
|
if let strongSelf = self, let (webPage, _) = strongSelf.webPage, case let .Loaded(content) = webPage.content {
|
||||||
strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil)
|
strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil)
|
||||||
}
|
}
|
||||||
})]
|
})]
|
||||||
@ -1368,7 +1380,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentReferenceView(item: InstantPageTextItem, referenceAnchor: String) {
|
private func presentReferenceView(item: InstantPageTextItem, referenceAnchor: String) {
|
||||||
guard let webPage = self.webPage else {
|
guard let (webPage, instantPage) = self.webPage else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1389,7 +1401,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = InstantPageReferenceController(context: self.context, sourceLocation: self.sourceLocation, theme: theme, webPage: webPage, anchorText: anchorText, openUrl: { [weak self] url in
|
let controller = InstantPageReferenceController(context: self.context, sourceLocation: self.sourceLocation, theme: theme, webPage: webPage, instantPage: instantPage, anchorText: anchorText, openUrl: { [weak self] url in
|
||||||
self?.openUrl(url)
|
self?.openUrl(url)
|
||||||
}, openUrlIn: { [weak self] url in
|
}, openUrlIn: { [weak self] url in
|
||||||
self?.openUrlIn(url)
|
self?.openUrlIn(url)
|
||||||
@ -1444,7 +1456,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: targetY), animated: true)
|
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: targetY), animated: true)
|
||||||
}
|
}
|
||||||
} else if case let .Loaded(content) = self.webPage?.content, let instantPage = content.instantPage, !instantPage.isComplete {
|
} else if let instantPage = self.webPage?.instantPage, !instantPage.isComplete {
|
||||||
// self.loadProgress.set(0.5)
|
// self.loadProgress.set(0.5)
|
||||||
self.pendingAnchor = anchor
|
self.pendingAnchor = anchor
|
||||||
}
|
}
|
||||||
@ -1480,7 +1492,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addToRecentlyVisited() {
|
func addToRecentlyVisited() {
|
||||||
if let webPage = self.webPage {
|
if let (webPage, _) = self.webPage {
|
||||||
let _ = addRecentlyVisitedLink(engine: self.context.engine, webPage: webPage).startStandalone()
|
let _ = addRecentlyVisitedLink(engine: self.context.engine, webPage: webPage).startStandalone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1019,6 +1019,11 @@ public final class ChatPresentationInterfaceState: Equatable {
|
|||||||
public func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState {
|
public func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState {
|
||||||
var inputQueryResults = self.inputQueryResults
|
var inputQueryResults = self.inputQueryResults
|
||||||
let updated = f(inputQueryResults[queryKind])
|
let updated = f(inputQueryResults[queryKind])
|
||||||
|
if case .contextRequest = queryKind {
|
||||||
|
#if DEBUG
|
||||||
|
print("updatedInputQueryResult: \(String(describing: updated))")
|
||||||
|
#endif
|
||||||
|
}
|
||||||
if let updated = updated {
|
if let updated = updated {
|
||||||
inputQueryResults[queryKind] = updated
|
inputQueryResults[queryKind] = updated
|
||||||
} else {
|
} else {
|
||||||
|
@ -1284,7 +1284,7 @@ final class ComposePollScreenComponent: Component {
|
|||||||
component: AnyComponent(EmojiSuggestionsComponent(
|
component: AnyComponent(EmojiSuggestionsComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
userLocation: .other,
|
userLocation: .other,
|
||||||
theme: EmojiSuggestionsComponent.Theme(theme: environment.theme),
|
theme: EmojiSuggestionsComponent.Theme(theme: environment.theme, backgroundColor: environment.theme.list.itemBlocksBackgroundColor),
|
||||||
animationCache: component.context.animationCache,
|
animationCache: component.context.animationCache,
|
||||||
animationRenderer: component.context.animationRenderer,
|
animationRenderer: component.context.animationRenderer,
|
||||||
files: value,
|
files: value,
|
||||||
|
@ -64,10 +64,12 @@ private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, med
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia: Media) -> [InstantPageGalleryEntry] {
|
public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage.Accessor, galleryMedia: Media) -> [InstantPageGalleryEntry] {
|
||||||
var result: [InstantPageGalleryEntry] = []
|
var result: [InstantPageGalleryEntry] = []
|
||||||
var counter: Int = 0
|
var counter: Int = 0
|
||||||
|
|
||||||
|
let page = page._parse()
|
||||||
|
|
||||||
for block in page.blocks {
|
for block in page.blocks {
|
||||||
result.append(contentsOf: instantPageBlockMedia(pageId: webpageId, block: block, media: page.media, counter: &counter))
|
result.append(contentsOf: instantPageBlockMedia(pageId: webpageId, block: block, media: page.media, counter: &counter))
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
private let pushController: (ViewController) -> Void
|
private let pushController: (ViewController) -> Void
|
||||||
private let openPeer: (EnginePeer) -> Void
|
private let openPeer: (EnginePeer) -> Void
|
||||||
|
|
||||||
private var webPage: TelegramMediaWebpage?
|
private var webPage: (webPage: TelegramMediaWebpage, instantPage: InstantPage?)?
|
||||||
private var initialAnchor: String?
|
private var initialAnchor: String?
|
||||||
private var pendingAnchor: String?
|
private var pendingAnchor: String?
|
||||||
private var initialState: InstantPageStoredState?
|
private var initialState: InstantPageStoredState?
|
||||||
@ -141,7 +141,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
|
|
||||||
self.navigationBar.back = navigateBack
|
self.navigationBar.back = navigateBack
|
||||||
self.navigationBar.share = { [weak self] in
|
self.navigationBar.share = { [weak self] in
|
||||||
if let strongSelf = self, let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content {
|
if let strongSelf = self, let (webPage, _) = strongSelf.webPage, case let .Loaded(content) = webPage.content {
|
||||||
let shareController = ShareController(context: context, subject: .url(content.url))
|
let shareController = ShareController(context: context, subject: .url(content.url))
|
||||||
shareController.actionCompleted = { [weak self] in
|
shareController.actionCompleted = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -345,7 +345,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateWebPage(_ webPage: TelegramMediaWebpage?, anchor: String?, state: InstantPageStoredState? = nil) {
|
func updateWebPage(_ webPage: TelegramMediaWebpage?, anchor: String?, state: InstantPageStoredState? = nil) {
|
||||||
if self.webPage != webPage {
|
if self.webPage?.webPage != webPage {
|
||||||
if self.webPage != nil && self.currentLayout != nil {
|
if self.webPage != nil && self.currentLayout != nil {
|
||||||
if let snaphotView = self.scrollNode.view.snapshotView(afterScreenUpdates: false) {
|
if let snaphotView = self.scrollNode.view.snapshotView(afterScreenUpdates: false) {
|
||||||
self.scrollNode.view.superview?.insertSubview(snaphotView, aboveSubview: self.scrollNode.view)
|
self.scrollNode.view.superview?.insertSubview(snaphotView, aboveSubview: self.scrollNode.view)
|
||||||
@ -356,7 +356,15 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.setupScrollOffsetOnLayout = self.webPage == nil
|
self.setupScrollOffsetOnLayout = self.webPage == nil
|
||||||
self.webPage = webPage
|
if let webPage {
|
||||||
|
var instantPage: InstantPage?
|
||||||
|
if case let .Loaded(content) = webPage.content {
|
||||||
|
instantPage = content.instantPage?._parse()
|
||||||
|
}
|
||||||
|
self.webPage = (webPage, instantPage)
|
||||||
|
} else {
|
||||||
|
self.webPage = nil
|
||||||
|
}
|
||||||
if let anchor = anchor {
|
if let anchor = anchor {
|
||||||
self.initialAnchor = anchor.removingPercentEncoding
|
self.initialAnchor = anchor.removingPercentEncoding
|
||||||
} else if let state = state {
|
} else if let state = state {
|
||||||
@ -460,11 +468,11 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateLayout() {
|
private func updateLayout() {
|
||||||
guard let containerLayout = self.containerLayout, let webPage = self.webPage, let theme = self.theme else {
|
guard let containerLayout = self.containerLayout, let (webPage, instantPage) = self.webPage, let theme = self.theme else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentLayout = instantPageLayoutForWebPage(webPage, userLocation: self.sourceLocation.userLocation, boundingWidth: containerLayout.size.width, safeInset: containerLayout.safeInsets.left, strings: self.strings, theme: theme, dateTimeFormat: self.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights)
|
let currentLayout = instantPageLayoutForWebPage(webPage, instantPage: instantPage, userLocation: self.sourceLocation.userLocation, boundingWidth: containerLayout.size.width, safeInset: containerLayout.safeInsets.left, strings: self.strings, theme: theme, dateTimeFormat: self.dateTimeFormat, webEmbedHeights: self.currentWebEmbedHeights)
|
||||||
|
|
||||||
for (_, tileNode) in self.visibleTiles {
|
for (_, tileNode) in self.visibleTiles {
|
||||||
tileNode.removeFromSupernode()
|
tileNode.removeFromSupernode()
|
||||||
@ -863,7 +871,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var title: String?
|
var title: String?
|
||||||
if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
if let (webPage, _) = self.webPage, case let .Loaded(content) = webPage.content {
|
||||||
title = content.websiteName
|
title = content.websiteName
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1027,7 +1035,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
let _ = saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start()
|
let _ = saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, userLocation: strongSelf.sourceLocation.userLocation, mediaReference: .standalone(media: media)).start()
|
||||||
}
|
}
|
||||||
}), ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in
|
}), ContextMenuAction(content: .text(title: self.strings.Conversation_ContextMenuShare, accessibilityLabel: self.strings.Conversation_ContextMenuShare), action: { [weak self] in
|
||||||
if let strongSelf = self, let webPage = strongSelf.webPage, case let .image(image) = media.media {
|
if let strongSelf = self, let (webPage, _) = strongSelf.webPage, case let .image(image) = media.media {
|
||||||
strongSelf.present(ShareController(context: strongSelf.context, subject: .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.media(media: .webPage(webPage: WebpageReference(webPage), media: image), resource: $0.resource)) }))), nil)
|
strongSelf.present(ShareController(context: strongSelf.context, subject: .image(image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.media(media: .webPage(webPage: WebpageReference(webPage), media: image), resource: $0.resource)) }))), nil)
|
||||||
}
|
}
|
||||||
})], catchTapsOutside: true)
|
})], catchTapsOutside: true)
|
||||||
@ -1141,7 +1149,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||||
}
|
}
|
||||||
}), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in
|
}), ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuShare, accessibilityLabel: strings.Conversation_ContextMenuShare), action: { [weak self] in
|
||||||
if let strongSelf = self, let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content {
|
if let strongSelf = self, let (webPage, _) = strongSelf.webPage, case let .Loaded(content) = webPage.content {
|
||||||
strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil)
|
strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil)
|
||||||
}
|
}
|
||||||
})]
|
})]
|
||||||
@ -1209,7 +1217,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentReferenceView(item: InstantPageTextItem, referenceAnchor: String) {
|
private func presentReferenceView(item: InstantPageTextItem, referenceAnchor: String) {
|
||||||
guard let theme = self.theme, let webPage = self.webPage else {
|
guard let theme = self.theme, let (webPage, instantPage) = self.webPage else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1230,7 +1238,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let controller = InstantPageReferenceController(context: self.context, sourceLocation: self.sourceLocation, theme: theme, webPage: webPage, anchorText: anchorText, openUrl: { [weak self] url in
|
let controller = InstantPageReferenceController(context: self.context, sourceLocation: self.sourceLocation, theme: theme, webPage: webPage, instantPage: instantPage, anchorText: anchorText, openUrl: { [weak self] url in
|
||||||
self?.openUrl(url)
|
self?.openUrl(url)
|
||||||
}, openUrlIn: { [weak self] url in
|
}, openUrlIn: { [weak self] url in
|
||||||
self?.openUrlIn(url)
|
self?.openUrlIn(url)
|
||||||
@ -1285,7 +1293,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: targetY), animated: true)
|
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: targetY), animated: true)
|
||||||
}
|
}
|
||||||
} else if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let instantPage = content.instantPage, !instantPage.isComplete {
|
} else if let (_, instantPage) = self.webPage, let instantPage, !instantPage.isComplete {
|
||||||
self.loadProgress.set(0.5)
|
self.loadProgress.set(0.5)
|
||||||
self.pendingAnchor = anchor
|
self.pendingAnchor = anchor
|
||||||
}
|
}
|
||||||
@ -1302,7 +1310,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
baseUrl = String(baseUrl[..<anchorRange.lowerBound])
|
baseUrl = String(baseUrl[..<anchorRange.lowerBound])
|
||||||
}
|
}
|
||||||
|
|
||||||
if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let page = content.instantPage, page.url == baseUrl, let anchor = anchor {
|
if let (_, page) = self.webPage, let page, page.url == baseUrl, let anchor = anchor {
|
||||||
self.scrollToAnchor(anchor)
|
self.scrollToAnchor(anchor)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1418,7 +1426,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func openMedia(_ media: InstantPageMedia) {
|
private func openMedia(_ media: InstantPageMedia) {
|
||||||
guard let items = self.currentLayout?.items, let webPage = self.webPage else {
|
guard let items = self.currentLayout?.items, let (webPage, _) = self.webPage else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1555,7 +1563,7 @@ final class InstantPageControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||||||
}).start()
|
}).start()
|
||||||
}
|
}
|
||||||
}, openInSafari: { [weak self] in
|
}, openInSafari: { [weak self] in
|
||||||
if let strongSelf = self, let webPage = strongSelf.webPage, case let .Loaded(content) = webPage.content {
|
if let strongSelf = self, let (webPage, _) = strongSelf.webPage, case let .Loaded(content) = webPage.content {
|
||||||
strongSelf.context.sharedContext.applicationBindings.openUrl(content.url)
|
strongSelf.context.sharedContext.applicationBindings.openUrl(content.url)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -842,13 +842,13 @@ public func layoutInstantPageBlock(webpage: TelegramMediaWebpage, userLocation:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, userLocation: MediaResourceUserLocation, boundingWidth: CGFloat, safeInset: CGFloat, strings: PresentationStrings, theme: InstantPageTheme, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:]) -> InstantPageLayout {
|
public func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, instantPage: InstantPage?, userLocation: MediaResourceUserLocation, boundingWidth: CGFloat, safeInset: CGFloat, strings: PresentationStrings, theme: InstantPageTheme, dateTimeFormat: PresentationDateTimeFormat, webEmbedHeights: [Int : CGFloat] = [:]) -> InstantPageLayout {
|
||||||
var maybeLoadedContent: TelegramMediaWebpageLoadedContent?
|
var maybeLoadedContent: TelegramMediaWebpageLoadedContent?
|
||||||
if case let .Loaded(content) = webPage.content {
|
if case let .Loaded(content) = webPage.content {
|
||||||
maybeLoadedContent = content
|
maybeLoadedContent = content
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let loadedContent = maybeLoadedContent, let instantPage = loadedContent.instantPage else {
|
guard let loadedContent = maybeLoadedContent, let instantPage else {
|
||||||
return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: [])
|
return InstantPageLayout(origin: CGPoint(), contentSize: CGSize(), items: [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,17 +17,17 @@ public final class InstantPageReferenceController: ViewController {
|
|||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let sourceLocation: InstantPageSourceLocation
|
private let sourceLocation: InstantPageSourceLocation
|
||||||
private let theme: InstantPageTheme
|
private let theme: InstantPageTheme
|
||||||
private let webPage: TelegramMediaWebpage
|
private let webPage: (webPage: TelegramMediaWebpage, instantPage: InstantPage?)
|
||||||
private let anchorText: NSAttributedString
|
private let anchorText: NSAttributedString
|
||||||
private let openUrl: (InstantPageUrlItem) -> Void
|
private let openUrl: (InstantPageUrlItem) -> Void
|
||||||
private let openUrlIn: (InstantPageUrlItem) -> Void
|
private let openUrlIn: (InstantPageUrlItem) -> Void
|
||||||
private let present: (ViewController, Any?) -> Void
|
private let present: (ViewController, Any?) -> Void
|
||||||
|
|
||||||
public init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, anchorText: NSAttributedString, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
public init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, instantPage: InstantPage?, anchorText: NSAttributedString, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.sourceLocation = sourceLocation
|
self.sourceLocation = sourceLocation
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.webPage = webPage
|
self.webPage = (webPage, instantPage)
|
||||||
self.anchorText = anchorText
|
self.anchorText = anchorText
|
||||||
self.openUrl = openUrl
|
self.openUrl = openUrl
|
||||||
self.openUrlIn = openUrlIn
|
self.openUrlIn = openUrlIn
|
||||||
@ -43,7 +43,7 @@ public final class InstantPageReferenceController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
self.displayNode = InstantPageReferenceControllerNode(context: self.context, sourceLocation: self.sourceLocation, theme: self.theme, webPage: self.webPage, anchorText: self.anchorText, openUrl: self.openUrl, openUrlIn: self.openUrlIn, present: self.present)
|
self.displayNode = InstantPageReferenceControllerNode(context: self.context, sourceLocation: self.sourceLocation, theme: self.theme, webPage: self.webPage.webPage, instantPage: self.webPage.instantPage, anchorText: self.anchorText, openUrl: self.openUrl, openUrlIn: self.openUrlIn, present: self.present)
|
||||||
self.controllerNode.dismiss = { [weak self] in
|
self.controllerNode.dismiss = { [weak self] in
|
||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, ASScrollVie
|
|||||||
private let sourceLocation: InstantPageSourceLocation
|
private let sourceLocation: InstantPageSourceLocation
|
||||||
private let theme: InstantPageTheme
|
private let theme: InstantPageTheme
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private let webPage: TelegramMediaWebpage
|
private let webPage: (webPage: TelegramMediaWebpage, instantPage: InstantPage?)
|
||||||
private let anchorText: NSAttributedString
|
private let anchorText: NSAttributedString
|
||||||
|
|
||||||
private let dimNode: ASDisplayNode
|
private let dimNode: ASDisplayNode
|
||||||
@ -38,12 +38,12 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, ASScrollVie
|
|||||||
var dismiss: (() -> Void)?
|
var dismiss: (() -> Void)?
|
||||||
var close: (() -> Void)?
|
var close: (() -> Void)?
|
||||||
|
|
||||||
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, anchorText: NSAttributedString, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
init(context: AccountContext, sourceLocation: InstantPageSourceLocation, theme: InstantPageTheme, webPage: TelegramMediaWebpage, instantPage: InstantPage?, anchorText: NSAttributedString, openUrl: @escaping (InstantPageUrlItem) -> Void, openUrlIn: @escaping (InstantPageUrlItem) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.sourceLocation = sourceLocation
|
self.sourceLocation = sourceLocation
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.webPage = webPage
|
self.webPage = (webPage, instantPage)
|
||||||
self.anchorText = anchorText
|
self.anchorText = anchorText
|
||||||
self.openUrl = openUrl
|
self.openUrl = openUrl
|
||||||
self.openUrlIn = openUrlIn
|
self.openUrlIn = openUrlIn
|
||||||
@ -197,12 +197,12 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, ASScrollVie
|
|||||||
self.contentNode?.removeFromSupernode()
|
self.contentNode?.removeFromSupernode()
|
||||||
|
|
||||||
var media: [EngineMedia.Id: EngineMedia] = [:]
|
var media: [EngineMedia.Id: EngineMedia] = [:]
|
||||||
if case let .Loaded(content) = self.webPage.content, let instantPage = content.instantPage {
|
if let instantPage = self.webPage.instantPage {
|
||||||
media = instantPage.media.mapValues(EngineMedia.init)
|
media = instantPage.media.mapValues(EngineMedia.init)
|
||||||
}
|
}
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
let (_, items, contentSize) = layoutTextItemWithString(self.anchorText, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset), media: media, webpage: self.webPage)
|
let (_, items, contentSize) = layoutTextItemWithString(self.anchorText, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset), media: media, webpage: self.webPage.webPage)
|
||||||
let contentNode = InstantPageContentNode(context: self.context, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, sourceLocation: self.sourceLocation, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, pinchPreviewFinished: nil, openPeer: { _ in }, openUrl: { _ in }, getPreloadedResource: { url in return nil})
|
let contentNode = InstantPageContentNode(context: self.context, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, sourceLocation: self.sourceLocation, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, longPressMedia: { _ in }, activatePinchPreview: nil, pinchPreviewFinished: nil, openPeer: { _ in }, openUrl: { _ in }, getPreloadedResource: { url in return nil})
|
||||||
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: width, height: contentSize.height)))
|
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: width, height: contentSize.height)))
|
||||||
self.contentContainerNode.insertSubnode(contentNode, at: 0)
|
self.contentContainerNode.insertSubnode(contentNode, at: 0)
|
||||||
@ -413,7 +413,7 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, ASScrollVie
|
|||||||
let controller = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: {
|
let controller = makeContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: {
|
||||||
UIPasteboard.general.string = text
|
UIPasteboard.general.string = text
|
||||||
}), ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuShare, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuShare), action: { [weak self] in
|
}), ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuShare, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuShare), action: { [weak self] in
|
||||||
if let strongSelf = self, case let .Loaded(content) = strongSelf.webPage.content {
|
if let strongSelf = self, case let .Loaded(content) = strongSelf.webPage.webPage.content {
|
||||||
strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil)
|
strongSelf.present(ShareController(context: strongSelf.context, subject: .quote(text: text, url: content.url)), nil)
|
||||||
}
|
}
|
||||||
})])
|
})])
|
||||||
|
@ -398,7 +398,7 @@
|
|||||||
_captionMixin.stickersContext = stickersContext;
|
_captionMixin.stickersContext = stickersContext;
|
||||||
[_captionMixin createInputPanelIfNeeded];
|
[_captionMixin createInputPanelIfNeeded];
|
||||||
|
|
||||||
_headerWrapperView = [[UIView alloc] init];
|
_headerWrapperView = [[TGMediaPickerGalleryWrapperView alloc] init];
|
||||||
[_wrapperView addSubview:_headerWrapperView];
|
[_wrapperView addSubview:_headerWrapperView];
|
||||||
|
|
||||||
_photoCounterButton = [[TGMediaPickerPhotoCounterButton alloc] initWithFrame:CGRectMake(0, 0, 64, 38)];
|
_photoCounterButton = [[TGMediaPickerPhotoCounterButton alloc] initWithFrame:CGRectMake(0, 0, 64, 38)];
|
||||||
@ -1625,9 +1625,27 @@
|
|||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
|
|
||||||
|
- (UIView *)hitTestWithSpecialHandling:(UIView *)view point:(CGPoint)point withEvent:(UIEvent *)event {
|
||||||
|
for (UIView *subview in [view.subviews reverseObjectEnumerator]) {
|
||||||
|
if ([subview isKindOfClass:[TGMediaPickerGalleryWrapperView class]]) {
|
||||||
|
UIView *result = [self hitTestWithSpecialHandling:subview point:[view convertPoint:point toView:subview] withEvent:event];
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
UIView *result = [subview hitTest:[view convertPoint:point toView:subview] withEvent:event];
|
||||||
|
if (result) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
||||||
{
|
{
|
||||||
UIView *view = [super hitTest:point withEvent:event];
|
UIView *view = [self hitTestWithSpecialHandling:self point:point withEvent:event];
|
||||||
|
|
||||||
bool editingCover = false;
|
bool editingCover = false;
|
||||||
if (_coverTitleLabel != nil && !_coverTitleLabel.isHidden) {
|
if (_coverTitleLabel != nil && !_coverTitleLabel.isHidden) {
|
||||||
|
@ -756,9 +756,14 @@ public final class ChunkMediaPlayerV2: ChunkMediaPlayer {
|
|||||||
self.pause()
|
self.pause()
|
||||||
f()
|
f()
|
||||||
case let .loopDisablingSound(f):
|
case let .loopDisablingSound(f):
|
||||||
self.stoppedAtEnd = false
|
if duration - 0.1 <= 0.0 {
|
||||||
self.isSoundEnabled = false
|
self.stoppedAtEnd = true
|
||||||
self.seek(timestamp: 0.0, play: true, notify: true)
|
self.pause()
|
||||||
|
} else {
|
||||||
|
self.stoppedAtEnd = false
|
||||||
|
self.isSoundEnabled = false
|
||||||
|
self.seek(timestamp: 0.0, play: true, notify: true)
|
||||||
|
}
|
||||||
f()
|
f()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ func faqSearchableItems(context: AccountContext, resolvedUrl: Signal<ResolvedUrl
|
|||||||
var results: [SettingsSearchableItem] = []
|
var results: [SettingsSearchableItem] = []
|
||||||
var nextIndex: Int32 = 2
|
var nextIndex: Int32 = 2
|
||||||
if let resolvedUrl = resolvedUrl, case let .instantView(webPage, _) = resolvedUrl {
|
if let resolvedUrl = resolvedUrl, case let .instantView(webPage, _) = resolvedUrl {
|
||||||
if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage {
|
if case let .Loaded(content) = webPage.content, let instantPage = content.instantPage?._parse() {
|
||||||
var processingQuestions = false
|
var processingQuestions = false
|
||||||
var currentSection: String?
|
var currentSection: String?
|
||||||
outer: for block in instantPage.blocks {
|
outer: for block in instantPage.blocks {
|
||||||
|
@ -1128,7 +1128,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
|
|
||||||
var sharedAudioContext = sharedAudioContext
|
var sharedAudioContext = sharedAudioContext
|
||||||
if sharedAudioContext == nil {
|
if sharedAudioContext == nil {
|
||||||
var useSharedAudio = true
|
var useSharedAudio = !isStream
|
||||||
if let data = self.accountContext.currentAppConfiguration.with({ $0 }).data, data["ios_killswitch_group_shared_audio"] != nil {
|
if let data = self.accountContext.currentAppConfiguration.with({ $0 }).data, data["ios_killswitch_group_shared_audio"] != nil {
|
||||||
useSharedAudio = false
|
useSharedAudio = false
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import Foundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramApi
|
import TelegramApi
|
||||||
|
|
||||||
|
|
||||||
func parsedTelegramProfilePhoto(_ photo: Api.UserProfilePhoto) -> [TelegramMediaImageRepresentation] {
|
func parsedTelegramProfilePhoto(_ photo: Api.UserProfilePhoto) -> [TelegramMediaImageRepresentation] {
|
||||||
var representations: [TelegramMediaImageRepresentation] = []
|
var representations: [TelegramMediaImageRepresentation] = []
|
||||||
switch photo {
|
switch photo {
|
||||||
|
@ -71,6 +71,10 @@ private final class FetchImpl {
|
|||||||
self.partRange = partRange
|
self.partRange = partRange
|
||||||
self.fetchRange = fetchRange
|
self.fetchRange = fetchRange
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable?.dispose()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class PendingReadyPart {
|
private final class PendingReadyPart {
|
||||||
@ -399,7 +403,7 @@ private final class FetchImpl {
|
|||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
self.requiredRangesDisposable = (intervals
|
self.requiredRangesDisposable = (intervals
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] intervals in
|
|> deliverOn(self.queue)).startStrict(next: { [weak self] intervals in
|
||||||
guard let `self` = self else {
|
guard let `self` = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -412,6 +416,7 @@ private final class FetchImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
self.requiredRangesDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func update() {
|
private func update() {
|
||||||
@ -676,7 +681,7 @@ private final class FetchImpl {
|
|||||||
let cdnData = state.cdnData
|
let cdnData = state.cdnData
|
||||||
|
|
||||||
state.disposable = (reuploadSignal
|
state.disposable = (reuploadSignal
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] result in
|
|> deliverOn(self.queue)).startStrict(next: { [weak self] result in
|
||||||
guard let `self` = self else {
|
guard let `self` = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -713,7 +718,7 @@ private final class FetchImpl {
|
|||||||
info: info,
|
info: info,
|
||||||
resource: self.resource
|
resource: self.resource
|
||||||
)
|
)
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] validationResult in
|
|> deliverOn(self.queue)).startStrict(next: { [weak self] validationResult in
|
||||||
guard let `self` = self else {
|
guard let `self` = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -875,7 +880,7 @@ private final class FetchImpl {
|
|||||||
|
|
||||||
if let filePartRequest {
|
if let filePartRequest {
|
||||||
part.disposable = (filePartRequest
|
part.disposable = (filePartRequest
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self, weak state, weak part] result in
|
|> deliverOn(self.queue)).startStrict(next: { [weak self, weak state, weak part] result in
|
||||||
guard let self, let state, case let .fetching(fetchingState) = self.state, fetchingState === state else {
|
guard let self, let state, case let .fetching(fetchingState) = self.state, fetchingState === state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -969,7 +974,7 @@ private final class FetchImpl {
|
|||||||
|
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
hashRange.disposable = (fetchRequest
|
hashRange.disposable = (fetchRequest
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self, weak state, weak hashRange] result in
|
|> deliverOn(self.queue)).startStrict(next: { [weak self, weak state, weak hashRange] result in
|
||||||
queue.async {
|
queue.async {
|
||||||
guard let self, let state, case let .fetching(fetchingState) = self.state, fetchingState === state else {
|
guard let self, let state, case let .fetching(fetchingState) = self.state, fetchingState === state else {
|
||||||
return
|
return
|
||||||
|
@ -204,8 +204,8 @@ private func findMediaResource(media: Media, previousMedia: Media?, resource: Me
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
if let instantPage = content.instantPage {
|
if let instantPage = content.instantPage {
|
||||||
for pageMedia in instantPage.media.values {
|
for (_, pageMedia) in instantPage.media {
|
||||||
if let result = findMediaResource(media: pageMedia, previousMedia: previousMedia, resource: resource) {
|
if let result = findMediaResource(media: pageMedia._parse(), previousMedia: previousMedia, resource: resource) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,8 +279,8 @@ func findMediaResourceById(media: Media, resourceId: MediaResourceId) -> Telegra
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
if let instantPage = content.instantPage {
|
if let instantPage = content.instantPage {
|
||||||
for pageMedia in instantPage.media.values {
|
for (_, pageMedia) in instantPage.media {
|
||||||
if let result = findMediaResourceById(media: pageMedia, resourceId: resourceId) {
|
if let result = findMediaResourceById(media: pageMedia._parse(), resourceId: resourceId) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1171,3 +1171,54 @@ public func TelegramMediaResource_serialize(resource: TelegramMediaResource, fla
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension TelegramCore_TelegramMediaResource {
|
||||||
|
var id: MediaResourceId {
|
||||||
|
switch self.valueType {
|
||||||
|
case .telegrammediaresourceCloudfilemediaresource:
|
||||||
|
guard let value = self.value(type: TelegramCore_TelegramMediaResource_CloudFileMediaResource.self) else {
|
||||||
|
return MediaResourceId("")
|
||||||
|
}
|
||||||
|
return MediaResourceId(CloudFileMediaResourceId(datacenterId: Int(value.datacenterId), volumeId: value.volumeId, localId: value.localId, secret: value.secret).uniqueId)
|
||||||
|
case .telegrammediaresourceClouddocumentsizemediaresource:
|
||||||
|
guard let value = self.value(type: TelegramCore_TelegramMediaResource_CloudDocumentSizeMediaResource.self) else {
|
||||||
|
return MediaResourceId("")
|
||||||
|
}
|
||||||
|
return MediaResourceId(CloudDocumentSizeMediaResourceId(datacenterId: Int32(value.datacenterId), documentId: value.documentId, sizeSpec: value.sizeSpec).uniqueId)
|
||||||
|
case .telegrammediaresourceCloudphotosizemediaresource:
|
||||||
|
guard let value = self.value(type: TelegramCore_TelegramMediaResource_CloudPhotoSizeMediaResource.self) else {
|
||||||
|
return MediaResourceId("")
|
||||||
|
}
|
||||||
|
return MediaResourceId(CloudPhotoSizeMediaResourceId(datacenterId: Int32(value.datacenterId), photoId: value.photoId, sizeSpec: value.sizeSpec).uniqueId)
|
||||||
|
case .telegrammediaresourceCloudpeerphotosizemediaresource:
|
||||||
|
guard let value = self.value(type: TelegramCore_TelegramMediaResource_CloudPeerPhotoSizeMediaResource.self) else {
|
||||||
|
return MediaResourceId("")
|
||||||
|
}
|
||||||
|
let sizeSpec: CloudPeerPhotoSizeSpec
|
||||||
|
switch value.sizeSpec {
|
||||||
|
case .small:
|
||||||
|
sizeSpec = .small
|
||||||
|
case .fullSize:
|
||||||
|
sizeSpec = .fullSize
|
||||||
|
}
|
||||||
|
return MediaResourceId(CloudPeerPhotoSizeMediaResourceId(datacenterId: Int32(value.datacenterId), photoId: value.photoId, sizeSpec: sizeSpec, volumeId: value.volumeId, localId: value.localId).uniqueId)
|
||||||
|
case .telegrammediaresourceCloudstickerpackthumbnailmediaresource:
|
||||||
|
guard let value = self.value(type: TelegramCore_TelegramMediaResource_CloudStickerPackThumbnailMediaResource.self) else {
|
||||||
|
return MediaResourceId("")
|
||||||
|
}
|
||||||
|
return MediaResourceId(CloudStickerPackThumbnailMediaResourceId(datacenterId: Int32(value.datacenterId), thumbVersion: value.thumbVersion, volumeId: value.volumeId, localId: value.localId).uniqueId)
|
||||||
|
case .telegrammediaresourceClouddocumentmediaresource:
|
||||||
|
guard let value = self.value(type: TelegramCore_TelegramMediaResource_CloudDocumentMediaResource.self) else {
|
||||||
|
return MediaResourceId("")
|
||||||
|
}
|
||||||
|
return MediaResourceId(CloudDocumentMediaResourceId(datacenterId: Int(value.datacenterId), fileId: value.fileId).uniqueId)
|
||||||
|
case .telegrammediaresourceLocalfilemediaresource:
|
||||||
|
guard let value = self.value(type: TelegramCore_TelegramMediaResource_LocalFileMediaResource.self) else {
|
||||||
|
return MediaResourceId("")
|
||||||
|
}
|
||||||
|
return MediaResourceId(LocalFileMediaResourceId(fileId: value.fileId).uniqueId)
|
||||||
|
case .none_:
|
||||||
|
return MediaResourceId("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1473,25 +1473,11 @@ public final class InstantPage: PostboxCoding, Equatable {
|
|||||||
return try InstantPageBlock(flatBuffersObject: flatBuffersObject.blocks(at: i)!)
|
return try InstantPageBlock(flatBuffersObject: flatBuffersObject.blocks(at: i)!)
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:release support other media types
|
|
||||||
var media: [MediaId: Media] = [:]
|
var media: [MediaId: Media] = [:]
|
||||||
for i in 0 ..< flatBuffersObject.mediaCount {
|
for i in 0 ..< flatBuffersObject.mediaCount {
|
||||||
let mediaItem = flatBuffersObject.media(at: i)!
|
let parsedMedia = try TelegramMedia_parse(flatBuffersObject: flatBuffersObject.media(at: i)!)
|
||||||
switch mediaItem.valueType {
|
if let id = parsedMedia.id {
|
||||||
case .mediaTelegrammediafile:
|
media[id] = parsedMedia
|
||||||
guard let value = mediaItem.value(type: TelegramCore_Media_TelegramMediaFile.self) else {
|
|
||||||
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
|
|
||||||
}
|
|
||||||
let parsedMedia = try TelegramMediaFile(flatBuffersObject: value.file)
|
|
||||||
media[parsedMedia.fileId] = parsedMedia
|
|
||||||
case .mediaTelegrammediaimage:
|
|
||||||
guard let value = mediaItem.value(type: TelegramCore_Media_TelegramMediaImage.self) else {
|
|
||||||
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
|
|
||||||
}
|
|
||||||
let parsedMedia = try TelegramMediaImage(flatBuffersObject: value.image)
|
|
||||||
media[parsedMedia.imageId] = parsedMedia
|
|
||||||
case .none_:
|
|
||||||
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.media = media
|
self.media = media
|
||||||
@ -1510,21 +1496,8 @@ public final class InstantPage: PostboxCoding, Equatable {
|
|||||||
|
|
||||||
var mediaOffsets: [Offset] = []
|
var mediaOffsets: [Offset] = []
|
||||||
for (_, media) in self.media.sorted(by: { $0.key < $1.key }) {
|
for (_, media) in self.media.sorted(by: { $0.key < $1.key }) {
|
||||||
switch media {
|
if let offset = TelegramMedia_serialize(media: media, flatBuffersBuilder: &builder) {
|
||||||
case let file as TelegramMediaFile:
|
mediaOffsets.append(offset)
|
||||||
let fileOffset = file.encodeToFlatBuffers(builder: &builder)
|
|
||||||
let start = TelegramCore_Media_TelegramMediaFile.startMedia_TelegramMediaFile(&builder)
|
|
||||||
TelegramCore_Media_TelegramMediaFile.add(file: fileOffset, &builder)
|
|
||||||
let offset = TelegramCore_Media_TelegramMediaFile.endMedia_TelegramMediaFile(&builder, start: start)
|
|
||||||
mediaOffsets.append(TelegramCore_Media.createMedia(&builder, valueType: .mediaTelegrammediafile, valueOffset: offset))
|
|
||||||
case let image as TelegramMediaImage:
|
|
||||||
let imageOffset = image.encodeToFlatBuffers(builder: &builder)
|
|
||||||
let start = TelegramCore_Media_TelegramMediaImage.startMedia_TelegramMediaImage(&builder)
|
|
||||||
TelegramCore_Media_TelegramMediaImage.add(image: imageOffset, &builder)
|
|
||||||
let offset = TelegramCore_Media_TelegramMediaImage.endMedia_TelegramMediaImage(&builder, start: start)
|
|
||||||
mediaOffsets.append(TelegramCore_Media.createMedia(&builder, valueType: .mediaTelegrammediaimage, valueOffset: offset))
|
|
||||||
default:
|
|
||||||
assertionFailure()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1573,13 +1546,88 @@ public extension InstantPage {
|
|||||||
|
|
||||||
public static func ==(lhs: InstantPage.Accessor, rhs: InstantPage.Accessor) -> Bool {
|
public static func ==(lhs: InstantPage.Accessor, rhs: InstantPage.Accessor) -> Bool {
|
||||||
if let lhsWrappedInstantPage = lhs._wrappedInstantPage, let rhsWrappedInstantPage = rhs._wrappedInstantPage {
|
if let lhsWrappedInstantPage = lhs._wrappedInstantPage, let rhsWrappedInstantPage = rhs._wrappedInstantPage {
|
||||||
return lhsWrappedInstantPage === rhsWrappedInstantPage
|
return lhsWrappedInstantPage == rhsWrappedInstantPage
|
||||||
} else if let lhsWrappedData = lhs._wrappedData, let rhsWrappedData = rhs._wrappedData {
|
} else if let lhsWrappedData = lhs._wrappedData, let rhsWrappedData = rhs._wrappedData {
|
||||||
return lhsWrappedData == rhsWrappedData
|
return lhsWrappedData == rhsWrappedData
|
||||||
} else {
|
} else {
|
||||||
assertionFailure()
|
|
||||||
return lhs._parse() == rhs._parse()
|
return lhs._parse() == rhs._parse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension InstantPage.Accessor {
|
||||||
|
struct MediaIterator: Sequence, IteratorProtocol {
|
||||||
|
private let accessor: InstantPage.Accessor
|
||||||
|
private var wrappedInstantPageIterator: Dictionary<MediaId, Media>.Iterator?
|
||||||
|
private var wrappedCurrentIndex: Int32 = 0
|
||||||
|
|
||||||
|
init(_ accessor: InstantPage.Accessor) {
|
||||||
|
self.accessor = accessor
|
||||||
|
|
||||||
|
if let wrappedInstantPage = accessor._wrappedInstantPage {
|
||||||
|
self.wrappedInstantPageIterator = wrappedInstantPage.media.makeIterator()
|
||||||
|
} else {
|
||||||
|
self.wrappedInstantPageIterator = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating public func next() -> (MediaId, TelegramMedia.Accessor)? {
|
||||||
|
if self.wrappedInstantPageIterator != nil {
|
||||||
|
guard let (id, value) = self.wrappedInstantPageIterator!.next() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return (id, TelegramMedia.Accessor(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.wrappedCurrentIndex >= self.accessor._wrapped!.mediaCount {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let index = self.wrappedCurrentIndex
|
||||||
|
self.wrappedCurrentIndex += 1
|
||||||
|
let media = self.accessor._wrapped!.media(at: index)!
|
||||||
|
let parsedMedia = TelegramMedia.Accessor(media)
|
||||||
|
if let id = parsedMedia.id {
|
||||||
|
return (id, parsedMedia)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isComplete: Bool {
|
||||||
|
if let wrappedInstantPage = self._wrappedInstantPage {
|
||||||
|
return wrappedInstantPage.isComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._wrapped!.isComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
var media: MediaIterator {
|
||||||
|
return MediaIterator(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
var views: Int32? {
|
||||||
|
if let wrappedInstantPage = self._wrappedInstantPage {
|
||||||
|
return wrappedInstantPage.views
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._wrapped!.views == Int32.min ? nil : self._wrapped!.views
|
||||||
|
}
|
||||||
|
|
||||||
|
var url: String {
|
||||||
|
if let wrappedInstantPage = self._wrappedInstantPage {
|
||||||
|
return wrappedInstantPage.url
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._wrapped!.url
|
||||||
|
}
|
||||||
|
|
||||||
|
var rtl: Bool {
|
||||||
|
if let wrappedInstantPage = self._wrappedInstantPage {
|
||||||
|
return wrappedInstantPage.rtl
|
||||||
|
}
|
||||||
|
|
||||||
|
return self._wrapped!.rtl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import Postbox
|
import Postbox
|
||||||
|
import FlatBuffers
|
||||||
|
import FlatSerialization
|
||||||
|
|
||||||
private enum TelegramMediaWebpageAttributeTypes: Int32 {
|
private enum TelegramMediaWebpageAttributeTypes: Int32 {
|
||||||
case unsupported
|
case unsupported
|
||||||
@ -177,7 +179,7 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||||||
public let file: TelegramMediaFile?
|
public let file: TelegramMediaFile?
|
||||||
public let story: TelegramMediaStory?
|
public let story: TelegramMediaStory?
|
||||||
public let attributes: [TelegramMediaWebpageAttribute]
|
public let attributes: [TelegramMediaWebpageAttribute]
|
||||||
public let instantPage: InstantPage?
|
public let instantPage: InstantPage.Accessor?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
url: String,
|
url: String,
|
||||||
@ -218,7 +220,7 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||||||
self.file = file
|
self.file = file
|
||||||
self.story = story
|
self.story = story
|
||||||
self.attributes = attributes
|
self.attributes = attributes
|
||||||
self.instantPage = instantPage
|
self.instantPage = instantPage.flatMap(InstantPage.Accessor.init)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
@ -272,8 +274,11 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||||||
}
|
}
|
||||||
self.attributes = effectiveAttributes
|
self.attributes = effectiveAttributes
|
||||||
|
|
||||||
if let instantPage = decoder.decodeObjectForKey("ip", decoder: { InstantPage(decoder: $0) }) as? InstantPage {
|
if let serializedInstantPageData = decoder.decodeDataForKey("ipd") {
|
||||||
self.instantPage = instantPage
|
var byteBuffer = ByteBuffer(data: serializedInstantPageData)
|
||||||
|
self.instantPage = InstantPage.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_InstantPage, serializedInstantPageData)
|
||||||
|
} else if let instantPage = decoder.decodeObjectForKey("ip", decoder: { InstantPage(decoder: $0) }) as? InstantPage {
|
||||||
|
self.instantPage = InstantPage.Accessor(instantPage)
|
||||||
} else {
|
} else {
|
||||||
self.instantPage = nil
|
self.instantPage = nil
|
||||||
}
|
}
|
||||||
@ -355,9 +360,19 @@ public final class TelegramMediaWebpageLoadedContent: PostboxCoding, Equatable {
|
|||||||
encoder.encodeObjectArray(self.attributes, forKey: "attr")
|
encoder.encodeObjectArray(self.attributes, forKey: "attr")
|
||||||
|
|
||||||
if let instantPage = self.instantPage {
|
if let instantPage = self.instantPage {
|
||||||
encoder.encodeObject(instantPage, forKey: "ip")
|
if let instantPageData = instantPage._wrappedData {
|
||||||
|
encoder.encodeData(instantPageData, forKey: "ipd")
|
||||||
|
} else if let instantPage = instantPage._wrappedInstantPage {
|
||||||
|
var builder = FlatBufferBuilder(initialSize: 1024)
|
||||||
|
let value = instantPage.encodeToFlatBuffers(builder: &builder)
|
||||||
|
builder.finish(offset: value)
|
||||||
|
let serializedInstantPage = builder.data
|
||||||
|
encoder.encodeData(serializedInstantPage, forKey: "ipd")
|
||||||
|
} else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
encoder.encodeNil(forKey: "ip")
|
encoder.encodeNil(forKey: "ipd")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
import Foundation
|
||||||
|
import FlatBuffers
|
||||||
|
import FlatSerialization
|
||||||
|
import Postbox
|
||||||
|
|
||||||
|
public func TelegramMedia_parse(flatBuffersObject: TelegramCore_Media) throws -> Media {
|
||||||
|
//TODO:release support other media types
|
||||||
|
switch flatBuffersObject.valueType {
|
||||||
|
case .mediaTelegrammediafile:
|
||||||
|
guard let value = flatBuffersObject.value(type: TelegramCore_Media_TelegramMediaFile.self) else {
|
||||||
|
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
|
||||||
|
}
|
||||||
|
return try TelegramMediaFile(flatBuffersObject: value.file)
|
||||||
|
case .mediaTelegrammediaimage:
|
||||||
|
guard let value = flatBuffersObject.value(type: TelegramCore_Media_TelegramMediaImage.self) else {
|
||||||
|
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
|
||||||
|
}
|
||||||
|
return try TelegramMediaImage(flatBuffersObject: value.image)
|
||||||
|
case .none_:
|
||||||
|
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func TelegramMedia_serialize(media: Media, flatBuffersBuilder builder: inout FlatBufferBuilder) -> Offset? {
|
||||||
|
//TODO:release support other media types
|
||||||
|
switch media {
|
||||||
|
case let file as TelegramMediaFile:
|
||||||
|
let fileOffset = file.encodeToFlatBuffers(builder: &builder)
|
||||||
|
let start = TelegramCore_Media_TelegramMediaFile.startMedia_TelegramMediaFile(&builder)
|
||||||
|
TelegramCore_Media_TelegramMediaFile.add(file: fileOffset, &builder)
|
||||||
|
let offset = TelegramCore_Media_TelegramMediaFile.endMedia_TelegramMediaFile(&builder, start: start)
|
||||||
|
return TelegramCore_Media.createMedia(&builder, valueType: .mediaTelegrammediafile, valueOffset: offset)
|
||||||
|
case let image as TelegramMediaImage:
|
||||||
|
let imageOffset = image.encodeToFlatBuffers(builder: &builder)
|
||||||
|
let start = TelegramCore_Media_TelegramMediaImage.startMedia_TelegramMediaImage(&builder)
|
||||||
|
TelegramCore_Media_TelegramMediaImage.add(image: imageOffset, &builder)
|
||||||
|
let offset = TelegramCore_Media_TelegramMediaImage.endMedia_TelegramMediaImage(&builder, start: start)
|
||||||
|
return TelegramCore_Media.createMedia(&builder, valueType: .mediaTelegrammediaimage, valueOffset: offset)
|
||||||
|
default:
|
||||||
|
assert(false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum TelegramMedia {
|
||||||
|
public struct Accessor {
|
||||||
|
let _wrappedMedia: Media?
|
||||||
|
let _wrapped: TelegramCore_Media?
|
||||||
|
|
||||||
|
public init(_ wrapped: TelegramCore_Media) {
|
||||||
|
self._wrapped = wrapped
|
||||||
|
self._wrappedMedia = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(_ wrapped: Media) {
|
||||||
|
self._wrapped = nil
|
||||||
|
self._wrappedMedia = wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
public func _parse() -> Media {
|
||||||
|
if let _wrappedMedia = self._wrappedMedia {
|
||||||
|
return _wrappedMedia
|
||||||
|
} else {
|
||||||
|
return try! TelegramMedia_parse(flatBuffersObject: self._wrapped!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension TelegramMedia.Accessor {
|
||||||
|
var id: MediaId? {
|
||||||
|
//TODO:release support other media types
|
||||||
|
if let _wrappedMedia = self._wrappedMedia {
|
||||||
|
return _wrappedMedia.id
|
||||||
|
}
|
||||||
|
|
||||||
|
switch self._wrapped!.valueType {
|
||||||
|
case .mediaTelegrammediafile:
|
||||||
|
guard let value = self._wrapped!.value(type: TelegramCore_Media_TelegramMediaFile.self) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return MediaId(value.file.fileId)
|
||||||
|
case .mediaTelegrammediaimage:
|
||||||
|
guard let value = self._wrapped!.value(type: TelegramCore_Media_TelegramMediaImage.self) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return MediaId(value.image.imageId)
|
||||||
|
case .none_:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -253,7 +253,7 @@ public func actualizedWebpage(account: Account, webpage: TelegramMediaWebpage) -
|
|||||||
file: content.file,
|
file: content.file,
|
||||||
story: content.story,
|
story: content.story,
|
||||||
attributes: content.attributes,
|
attributes: content.attributes,
|
||||||
instantPage: content.instantPage.flatMap({ InstantPage(blocks: $0.blocks, media: $0.media, isComplete: $0.isComplete, rtl: $0.rtl, url: $0.url, views: views) })
|
instantPage: (content.instantPage?._parse()).flatMap({ InstantPage(blocks: $0.blocks, media: $0.media, isComplete: $0.isComplete, rtl: $0.rtl, url: $0.url, views: views) })
|
||||||
))
|
))
|
||||||
let updatedWebpage = TelegramMediaWebpage(webpageId: webpage.webpageId, content: updatedContent)
|
let updatedWebpage = TelegramMediaWebpage(webpageId: webpage.webpageId, content: updatedContent)
|
||||||
updateMessageMedia(transaction: transaction, id: webpage.webpageId, media: updatedWebpage)
|
updateMessageMedia(transaction: transaction, id: webpage.webpageId, media: updatedWebpage)
|
||||||
|
@ -103,6 +103,94 @@ public final class EmojiSuggestionsComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func searchData(context: AccountContext, isSavedMessages: Bool, query: String) -> Signal<[TelegramMediaFile], NoError> {
|
||||||
|
let hasPremium: Signal<Bool, NoError>
|
||||||
|
if isSavedMessages {
|
||||||
|
hasPremium = .single(true)
|
||||||
|
} else {
|
||||||
|
hasPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||||
|
|> map { peer -> Bool in
|
||||||
|
guard case let .user(user) = peer else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return user.isPremium
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.isSingleEmoji {
|
||||||
|
return combineLatest(
|
||||||
|
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
|
||||||
|
hasPremium
|
||||||
|
)
|
||||||
|
|> map { view, hasPremium -> [TelegramMediaFile] in
|
||||||
|
var result: [TelegramMediaFile] = []
|
||||||
|
|
||||||
|
for entry in view.entries {
|
||||||
|
guard let item = entry.item as? StickerPackItem, !item.file.isPremiumEmoji || hasPremium else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let stringRepresentations = item.getStringRepresentationsOfIndexKeys()
|
||||||
|
for stringRepresentation in stringRepresentations {
|
||||||
|
if stringRepresentation == query {
|
||||||
|
result.append(item.file._parse())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let languageCode = "en-US"
|
||||||
|
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query, completeMatch: query.count < 2)
|
||||||
|
if !languageCode.lowercased().hasPrefix("en") {
|
||||||
|
signal = signal
|
||||||
|
|> mapToSignal { keywords in
|
||||||
|
return .single(keywords)
|
||||||
|
|> then(
|
||||||
|
context.engine.stickers.searchEmojiKeywords(inputLanguageCode: "en-US", query: query, completeMatch: query.count < 3)
|
||||||
|
|> map { englishKeywords in
|
||||||
|
return keywords + englishKeywords
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signal
|
||||||
|
|> mapToSignal { keywords -> Signal<[TelegramMediaFile], NoError> in
|
||||||
|
return combineLatest(
|
||||||
|
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
|
||||||
|
hasPremium
|
||||||
|
)
|
||||||
|
|> map { view, hasPremium -> [TelegramMediaFile] in
|
||||||
|
var result: [TelegramMediaFile] = []
|
||||||
|
|
||||||
|
var allEmoticons: [String: String] = [:]
|
||||||
|
for keyword in keywords {
|
||||||
|
for emoticon in keyword.emoticons {
|
||||||
|
allEmoticons[emoticon] = keyword.keyword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in view.entries {
|
||||||
|
guard let item = entry.item as? StickerPackItem, !item.file.isPremiumEmoji || hasPremium else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let stringRepresentations = item.getStringRepresentationsOfIndexKeys()
|
||||||
|
for stringRepresentation in stringRepresentations {
|
||||||
|
if let _ = allEmoticons[stringRepresentation] {
|
||||||
|
result.append(item.file._parse())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public let context: AccountContext
|
public let context: AccountContext
|
||||||
public let theme: Theme
|
public let theme: Theme
|
||||||
public let animationCache: AnimationCache
|
public let animationCache: AnimationCache
|
||||||
@ -389,8 +477,7 @@ public final class EmojiSuggestionsComponent: Component {
|
|||||||
let height: CGFloat = 54.0
|
let height: CGFloat = 54.0
|
||||||
|
|
||||||
if self.component?.theme.backgroundColor != component.theme.backgroundColor {
|
if self.component?.theme.backgroundColor != component.theme.backgroundColor {
|
||||||
//self.backgroundLayer.fillColor = component.theme.list.plainBackgroundColor.cgColor
|
self.backgroundLayer.fillColor = component.theme.backgroundColor.cgColor
|
||||||
self.backgroundLayer.fillColor = UIColor.black.cgColor
|
|
||||||
self.blurView.updateColor(color: component.theme.backgroundColor, transition: .immediate)
|
self.blurView.updateColor(color: component.theme.backgroundColor, transition: .immediate)
|
||||||
}
|
}
|
||||||
var resetScrollingPosition = false
|
var resetScrollingPosition = false
|
||||||
@ -435,8 +522,8 @@ public final class EmojiSuggestionsComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public extension EmojiSuggestionsComponent.Theme {
|
public extension EmojiSuggestionsComponent.Theme {
|
||||||
init(theme: PresentationTheme) {
|
init(theme: PresentationTheme, backgroundColor: UIColor? = nil) {
|
||||||
self.backgroundColor = theme.list.plainBackgroundColor.withMultipliedAlpha(0.88)
|
self.backgroundColor = backgroundColor ?? theme.list.plainBackgroundColor.withMultipliedAlpha(0.88)
|
||||||
self.textColor = theme.list.itemPrimaryTextColor
|
self.textColor = theme.list.itemPrimaryTextColor
|
||||||
self.placeholderColor = theme.list.mediaPlaceholderColor
|
self.placeholderColor = theme.list.mediaPlaceholderColor
|
||||||
}
|
}
|
||||||
|
@ -198,11 +198,6 @@ public extension EmojiPagerContentComponent {
|
|||||||
searchCategories = .single(nil)
|
searchCategories = .single(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG || true
|
|
||||||
var isFirstTime = true
|
|
||||||
let measure_startTime = CFAbsoluteTimeGetCurrent()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
let emojiItems: Signal<EmojiPagerContentComponent, NoError> = combineLatest(
|
let emojiItems: Signal<EmojiPagerContentComponent, NoError> = combineLatest(
|
||||||
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: orderedItemListCollectionIds, namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
|
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: orderedItemListCollectionIds, namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000),
|
||||||
forceHasPremium ? .single(true) : hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: premiumIfSavedMessages),
|
forceHasPremium ? .single(true) : hasPremium(context: context, chatPeerId: chatPeerId, premiumIfSavedMessages: premiumIfSavedMessages),
|
||||||
@ -214,54 +209,6 @@ public extension EmojiPagerContentComponent {
|
|||||||
ApplicationSpecificNotice.dismissedTrendingEmojiPacks(accountManager: context.sharedContext.accountManager)
|
ApplicationSpecificNotice.dismissedTrendingEmojiPacks(accountManager: context.sharedContext.accountManager)
|
||||||
)
|
)
|
||||||
|> map { view, hasPremium, featuredEmojiPacks, availableReactions, searchCategories, iconStatusEmoji, peerSpecificPack, dismissedTrendingEmojiPacks -> EmojiPagerContentComponent in
|
|> map { view, hasPremium, featuredEmojiPacks, availableReactions, searchCategories, iconStatusEmoji, peerSpecificPack, dismissedTrendingEmojiPacks -> EmojiPagerContentComponent in
|
||||||
#if DEBUG
|
|
||||||
if isFirstTime {
|
|
||||||
isFirstTime = false
|
|
||||||
|
|
||||||
/*var files: [TelegramMediaFile] = []
|
|
||||||
files.removeAll()
|
|
||||||
|
|
||||||
if "".isEmpty {
|
|
||||||
for entry in view.entries {
|
|
||||||
guard let item = entry.item as? StickerPackItem else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files.append(item.file._parse())
|
|
||||||
}
|
|
||||||
for featuredEmojiPack in featuredEmojiPacks {
|
|
||||||
for item in featuredEmojiPack.topItems {
|
|
||||||
files.append(item.file._parse())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let availableReactions {
|
|
||||||
for reactionItem in availableReactions.reactions {
|
|
||||||
files.append(reactionItem.staticIcon._parse())
|
|
||||||
files.append(reactionItem.appearAnimation._parse())
|
|
||||||
files.append(reactionItem.selectAnimation._parse())
|
|
||||||
files.append(reactionItem.activateAnimation._parse())
|
|
||||||
files.append(reactionItem.effectAnimation._parse())
|
|
||||||
if let aroundAnimation = reactionItem.aroundAnimation {
|
|
||||||
files.append(aroundAnimation._parse())
|
|
||||||
}
|
|
||||||
if let centerAnimation = reactionItem.centerAnimation {
|
|
||||||
files.append(centerAnimation._parse())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Thread.current.threadDictionary["afwefw"] = files
|
|
||||||
}
|
|
||||||
|
|
||||||
for file in files {
|
|
||||||
if file.fileId.id == 123 {
|
|
||||||
print("Interesting")
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
let measuredTime = CFAbsoluteTimeGetCurrent() - measure_startTime
|
|
||||||
print("emojiInputData init isMainThread: \(Thread.isMainThread): \(measuredTime * 1000.0) ms")
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct ItemGroup {
|
struct ItemGroup {
|
||||||
var supergroupId: AnyHashable
|
var supergroupId: AnyHashable
|
||||||
var id: AnyHashable
|
var id: AnyHashable
|
||||||
|
@ -1380,7 +1380,7 @@ final class GiftSetupScreenComponent: Component {
|
|||||||
component: AnyComponent(EmojiSuggestionsComponent(
|
component: AnyComponent(EmojiSuggestionsComponent(
|
||||||
context: component.context,
|
context: component.context,
|
||||||
userLocation: .other,
|
userLocation: .other,
|
||||||
theme: EmojiSuggestionsComponent.Theme(theme: environment.theme),
|
theme: EmojiSuggestionsComponent.Theme(theme: environment.theme, backgroundColor: environment.theme.list.itemBlocksBackgroundColor),
|
||||||
animationCache: component.context.animationCache,
|
animationCache: component.context.animationCache,
|
||||||
animationRenderer: component.context.animationRenderer,
|
animationRenderer: component.context.animationRenderer,
|
||||||
files: value,
|
files: value,
|
||||||
|
@ -499,6 +499,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
|
|
||||||
private var viewForOverlayContent: ViewForOverlayContent?
|
private var viewForOverlayContent: ViewForOverlayContent?
|
||||||
private var currentEmojiSuggestionView: ComponentHostView<Empty>?
|
private var currentEmojiSuggestionView: ComponentHostView<Empty>?
|
||||||
|
private var currentEmojiSearchView: ComponentHostView<Empty>?
|
||||||
|
|
||||||
private var viewsIconView: UIImageView?
|
private var viewsIconView: UIImageView?
|
||||||
private var viewStatsCountText: AnimatedCountLabelView?
|
private var viewStatsCountText: AnimatedCountLabelView?
|
||||||
@ -564,6 +565,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.textFieldExternalState.dismissedEmojiSuggestionPosition = self.textFieldExternalState.currentEmojiSuggestion?.position
|
self.textFieldExternalState.dismissedEmojiSuggestionPosition = self.textFieldExternalState.currentEmojiSuggestion?.position
|
||||||
|
self.textFieldExternalState.dismissedEmojiSearchPosition = self.textFieldExternalState.currentEmojiSearch?.position
|
||||||
self.state?.updated()
|
self.state?.updated()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -727,6 +729,15 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
textFieldView.updateEmojiSuggestion(transition: .immediate)
|
textFieldView.updateEmojiSuggestion(transition: .immediate)
|
||||||
}
|
}
|
||||||
self.state?.updated()
|
self.state?.updated()
|
||||||
|
} else if let _ = self.textField.view, let currentEmojiSearch = self.textFieldExternalState.currentEmojiSearch, let currentEmojiSearchView = self.currentEmojiSearchView {
|
||||||
|
if let result = currentEmojiSearchView.hitTest(self.convert(point, to: currentEmojiSearchView), with: event) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
self.textFieldExternalState.dismissedEmojiSearchPosition = currentEmojiSearch.position
|
||||||
|
if let textFieldView = self.textField.view as? TextFieldComponent.View {
|
||||||
|
textFieldView.updateEmojiSuggestion(transition: .immediate)
|
||||||
|
}
|
||||||
|
self.state?.updated()
|
||||||
}
|
}
|
||||||
|
|
||||||
if result == nil, let stickersResultPanel = self.stickersResultPanel?.view, let panelResult = stickersResultPanel.hitTest(self.convert(point, to: stickersResultPanel), with: event), panelResult !== stickersResultPanel {
|
if result == nil, let stickersResultPanel = self.stickersResultPanel?.view, let panelResult = stickersResultPanel.hitTest(self.convert(point, to: stickersResultPanel), with: event), panelResult !== stickersResultPanel {
|
||||||
@ -869,7 +880,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
transition: placeholderTransition,
|
transition: placeholderTransition,
|
||||||
component: AnyComponent(AnimatedTextComponent(
|
component: AnyComponent(AnimatedTextComponent(
|
||||||
font: Font.regular(17.0),
|
font: Font.regular(17.0),
|
||||||
color: UIColor(rgb: 0xffffff, alpha: 0.3),
|
color: UIColor(rgb: 0xffffff, alpha: 0.4),
|
||||||
items: placeholderItems
|
items: placeholderItems
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -2137,6 +2148,18 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let emojiSearch = self.textFieldExternalState.currentEmojiSearch, emojiSearch.disposable == nil {
|
||||||
|
emojiSearch.disposable = (EmojiSuggestionsComponent.searchData(context: component.context, isSavedMessages: false, query: emojiSearch.position.value)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak emojiSearch] result in
|
||||||
|
guard let self, let emojiSearch, self.textFieldExternalState.currentEmojiSearch === emojiSearch else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
emojiSearch.value = result
|
||||||
|
self.state?.updated()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var hasTrackingView = self.textFieldExternalState.hasTrackingView
|
var hasTrackingView = self.textFieldExternalState.hasTrackingView
|
||||||
if let currentEmojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion, let value = currentEmojiSuggestion.value as? [TelegramMediaFile], value.isEmpty {
|
if let currentEmojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion, let value = currentEmojiSuggestion.value as? [TelegramMediaFile], value.isEmpty {
|
||||||
hasTrackingView = false
|
hasTrackingView = false
|
||||||
@ -2159,6 +2182,20 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
currentEmojiSuggestionView?.removeFromSuperview()
|
currentEmojiSuggestionView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let currentEmojiSearch = self.textFieldExternalState.currentEmojiSearch {
|
||||||
|
self.textFieldExternalState.currentEmojiSearch = nil
|
||||||
|
currentEmojiSearch.disposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let currentEmojiSearchView = self.currentEmojiSearchView {
|
||||||
|
self.currentEmojiSearchView = nil
|
||||||
|
|
||||||
|
currentEmojiSearchView.alpha = 0.0
|
||||||
|
currentEmojiSearchView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak currentEmojiSearchView] _ in
|
||||||
|
currentEmojiSearchView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let currentEmojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion, let value = currentEmojiSuggestion.value as? [TelegramMediaFile] {
|
if let currentEmojiSuggestion = self.textFieldExternalState.currentEmojiSuggestion, let value = currentEmojiSuggestion.value as? [TelegramMediaFile] {
|
||||||
@ -2171,8 +2208,6 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
self.addSubview(currentEmojiSuggestionView)
|
self.addSubview(currentEmojiSuggestionView)
|
||||||
|
|
||||||
currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
currentEmojiSuggestionView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
|
||||||
//self.installEmojiSuggestionPreviewGesture(hostView: currentEmojiSuggestionView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let globalPosition: CGPoint
|
let globalPosition: CGPoint
|
||||||
@ -2190,7 +2225,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
context: component.context,
|
context: component.context,
|
||||||
userLocation: .other,
|
userLocation: .other,
|
||||||
theme: EmojiSuggestionsComponent.Theme(
|
theme: EmojiSuggestionsComponent.Theme(
|
||||||
backgroundColor: UIColor(white: 0.0, alpha: 0.5),
|
backgroundColor: UIColor(white: 0.1, alpha: 1.0),
|
||||||
textColor: .white,
|
textColor: .white,
|
||||||
placeholderColor: UIColor(rgb: 0xffffff).mixedWith(UIColor(rgb: 0x1c1c1d), alpha: 0.9)
|
placeholderColor: UIColor(rgb: 0xffffff).mixedWith(UIColor(rgb: 0x1c1c1d), alpha: 0.9)
|
||||||
),
|
),
|
||||||
@ -2209,7 +2244,7 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
|
|
||||||
var text: String?
|
var text: String?
|
||||||
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
|
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
|
||||||
loop: for attribute in file.attributes {
|
loop: for attribute in file.attributes {
|
||||||
switch attribute {
|
switch attribute {
|
||||||
case let .CustomEmoji(_, _, displayText, _):
|
case let .CustomEmoji(_, _, displayText, _):
|
||||||
text = displayText
|
text = displayText
|
||||||
@ -2261,6 +2296,113 @@ public final class MessageInputPanelComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let currentEmojiSearch = self.textFieldExternalState.currentEmojiSearch, let value = currentEmojiSearch.value as? [TelegramMediaFile], !value.isEmpty {
|
||||||
|
let currentEmojiSearchView: ComponentHostView<Empty>
|
||||||
|
if let current = self.currentEmojiSearchView {
|
||||||
|
currentEmojiSearchView = current
|
||||||
|
} else {
|
||||||
|
currentEmojiSearchView = ComponentHostView<Empty>()
|
||||||
|
self.currentEmojiSearchView = currentEmojiSearchView
|
||||||
|
self.addSubview(currentEmojiSearchView)
|
||||||
|
|
||||||
|
currentEmojiSearchView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalPosition: CGPoint
|
||||||
|
if let textView = self.textField.view {
|
||||||
|
globalPosition = textView.convert(currentEmojiSearch.localPosition, to: self)
|
||||||
|
globalPosition.x += 16.0
|
||||||
|
} else {
|
||||||
|
globalPosition = .zero
|
||||||
|
}
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 7.0
|
||||||
|
|
||||||
|
let viewSize = currentEmojiSearchView.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(EmojiSuggestionsComponent(
|
||||||
|
context: component.context,
|
||||||
|
userLocation: .other,
|
||||||
|
theme: EmojiSuggestionsComponent.Theme(
|
||||||
|
backgroundColor: UIColor(white: 0.1, alpha: 1.0),
|
||||||
|
textColor: .white,
|
||||||
|
placeholderColor: UIColor(rgb: 0xffffff).mixedWith(UIColor(rgb: 0x1c1c1d), alpha: 0.9)
|
||||||
|
),
|
||||||
|
animationCache: component.context.animationCache,
|
||||||
|
animationRenderer: component.context.animationRenderer,
|
||||||
|
files: value,
|
||||||
|
action: { [weak self] file in
|
||||||
|
guard let self, let textView = self.textField.view as? TextFieldComponent.View, let currentEmojiSearch = self.textFieldExternalState.currentEmojiSearch else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioServicesPlaySystemSound(0x450)
|
||||||
|
|
||||||
|
let inputState = textView.getInputState()
|
||||||
|
let inputText = NSMutableAttributedString(attributedString: inputState.inputText)
|
||||||
|
|
||||||
|
var text: String?
|
||||||
|
var emojiAttribute: ChatTextInputTextCustomEmojiAttribute?
|
||||||
|
loop: for attribute in file.attributes {
|
||||||
|
switch attribute {
|
||||||
|
case let .CustomEmoji(_, _, displayText, _):
|
||||||
|
text = displayText
|
||||||
|
emojiAttribute = ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file)
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let emojiAttribute = emojiAttribute, let text = text {
|
||||||
|
let replacementText = NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: emojiAttribute])
|
||||||
|
|
||||||
|
var range = currentEmojiSearch.position.range
|
||||||
|
let previousText = inputText.attributedSubstring(from: range)
|
||||||
|
if range.location != 0 && inputText.attributedSubstring(from: NSRange(location: range.location - 1, length: range.length + 1)).string.hasPrefix(":") {
|
||||||
|
range = NSRange(location: range.location - 1, length: range.length + 1)
|
||||||
|
}
|
||||||
|
inputText.replaceCharacters(in: range, with: replacementText)
|
||||||
|
|
||||||
|
var replacedUpperBound = range.lowerBound
|
||||||
|
while true {
|
||||||
|
if inputText.attributedSubstring(from: NSRange(location: 0, length: replacedUpperBound)).string.hasSuffix(previousText.string) {
|
||||||
|
let replaceRange = NSRange(location: replacedUpperBound - previousText.length, length: previousText.length)
|
||||||
|
if replaceRange.location < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let adjacentString = inputText.attributedSubstring(from: replaceRange)
|
||||||
|
if adjacentString.string != previousText.string || adjacentString.attribute(ChatTextInputAttributes.customEmoji, at: 0, effectiveRange: nil) != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
inputText.replaceCharacters(in: replaceRange, with: NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: emojiAttribute.interactivelySelectedFromPackId, fileId: emojiAttribute.fileId, file: emojiAttribute.file)]))
|
||||||
|
replacedUpperBound = replaceRange.lowerBound
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectionPosition = range.lowerBound + (replacementText.string as NSString).length
|
||||||
|
textView.updateText(inputText, selectionRange: selectionPosition ..< selectionPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: self.bounds.width - sideInset * 2.0, height: 100.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
var viewFrame = CGRect(origin: CGPoint(x: globalPosition.x - floor((viewSize.width) * 0.5), y: globalPosition.y - 4.0 - viewSize.height), size: viewSize)
|
||||||
|
if viewFrame.origin.x + viewFrame.width > self.bounds.width - sideInset {
|
||||||
|
viewFrame.origin.x = self.bounds.width - sideInset - viewFrame.width
|
||||||
|
}
|
||||||
|
viewFrame.origin.x = max(viewFrame.origin.x, sideInset)
|
||||||
|
|
||||||
|
currentEmojiSearchView.frame = viewFrame
|
||||||
|
if let componentView = currentEmojiSearchView.componentView as? EmojiSuggestionsComponent.View {
|
||||||
|
componentView.adjustBackground(relativePositionX: floor(globalPosition.x - viewFrame.minX))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,9 @@ public final class TextFieldComponent: Component {
|
|||||||
public var currentEmojiSuggestion: EmojiSuggestion?
|
public var currentEmojiSuggestion: EmojiSuggestion?
|
||||||
public var dismissedEmojiSuggestionPosition: EmojiSuggestion.Position?
|
public var dismissedEmojiSuggestionPosition: EmojiSuggestion.Position?
|
||||||
|
|
||||||
|
public var currentEmojiSearch: EmojiSearch?
|
||||||
|
public var dismissedEmojiSearchPosition: EmojiSearch.Position?
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,6 +63,25 @@ public final class TextFieldComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final class EmojiSearch {
|
||||||
|
public struct Position: Equatable {
|
||||||
|
public var range: NSRange
|
||||||
|
public var value: String
|
||||||
|
}
|
||||||
|
|
||||||
|
public var localPosition: CGPoint
|
||||||
|
public var position: Position
|
||||||
|
public var disposable: Disposable?
|
||||||
|
public var value: Any?
|
||||||
|
|
||||||
|
init(localPosition: CGPoint, position: Position) {
|
||||||
|
self.localPosition = localPosition
|
||||||
|
self.position = position
|
||||||
|
self.disposable = nil
|
||||||
|
self.value = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum PasteData {
|
public enum PasteData {
|
||||||
case sticker(image: UIImage, isMemoji: Bool)
|
case sticker(image: UIImage, isMemoji: Bool)
|
||||||
case images([UIImage])
|
case images([UIImage])
|
||||||
@ -1248,6 +1270,51 @@ public final class TextFieldComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if let index = selectedSubstring.string.range(of: ":", options: .backwards) {
|
||||||
|
let queryRange = index.upperBound ..< selectedSubstring.string.endIndex
|
||||||
|
let query = String(selectedSubstring.string[queryRange])
|
||||||
|
if !query.isEmpty && !query.contains(where: { c in
|
||||||
|
for s in c.unicodeScalars {
|
||||||
|
if CharacterSet.whitespacesAndNewlines.contains(s) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}) {
|
||||||
|
let beginning = self.textView.beginningOfDocument
|
||||||
|
let characterRange = NSRange(queryRange, in: selectedSubstring.string)
|
||||||
|
|
||||||
|
let start = self.textView.position(from: beginning, offset: characterRange.location)
|
||||||
|
let end = self.textView.position(from: beginning, offset: characterRange.location + characterRange.length)
|
||||||
|
|
||||||
|
if let start = start, let end = end, let textRange = self.textView.textRange(from: start, to: end) {
|
||||||
|
let selectionRects = self.textView.selectionRects(for: textRange)
|
||||||
|
let emojiSearchPosition = EmojiSearch.Position(range: characterRange, value: query)
|
||||||
|
|
||||||
|
hasTracking = true
|
||||||
|
|
||||||
|
if let trackingRect = selectionRects.first?.rect {
|
||||||
|
let trackingPosition = CGPoint(x: trackingRect.midX, y: trackingRect.minY)
|
||||||
|
if component.externalState.dismissedEmojiSearchPosition == emojiSearchPosition {
|
||||||
|
} else {
|
||||||
|
hasTrackingView = true
|
||||||
|
|
||||||
|
let emojiSearch: EmojiSearch
|
||||||
|
if let current = component.externalState.currentEmojiSearch, current.position.value == emojiSearchPosition.value {
|
||||||
|
emojiSearch = current
|
||||||
|
} else {
|
||||||
|
emojiSearch = EmojiSearch(localPosition: trackingPosition, position: emojiSearchPosition)
|
||||||
|
component.externalState.currentEmojiSearch = emojiSearch
|
||||||
|
}
|
||||||
|
emojiSearch.localPosition = trackingPosition
|
||||||
|
emojiSearch.position = emojiSearchPosition
|
||||||
|
component.externalState.dismissedEmojiSearchPosition = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasTracking {
|
if !hasTracking {
|
||||||
|
@ -275,9 +275,10 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
|||||||
|> castError(ChatContextQueryError.self)
|
|> castError(ChatContextQueryError.self)
|
||||||
return signal |> then(commands)
|
return signal |> then(commands)
|
||||||
case let .contextRequest(addressName, query):
|
case let .contextRequest(addressName, query):
|
||||||
guard let peer else {
|
guard let chatPeerId = chatLocation.peerId else {
|
||||||
return .single({ _ in return .contextRequestResult(nil, nil) })
|
return .single({ _ in return .contextRequestResult(nil, nil) })
|
||||||
}
|
}
|
||||||
|
|
||||||
var delayRequest = true
|
var delayRequest = true
|
||||||
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
|
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = .complete()
|
||||||
if let previousQuery = previousQuery {
|
if let previousQuery = previousQuery {
|
||||||
@ -294,7 +295,6 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
|||||||
signal = .single({ _ in return .contextRequestResult(nil, nil) })
|
signal = .single({ _ in return .contextRequestResult(nil, nil) })
|
||||||
}
|
}
|
||||||
|
|
||||||
let chatPeer = peer
|
|
||||||
let contextBot = context.engine.peers.resolvePeerByName(name: addressName, referrer: nil)
|
let contextBot = context.engine.peers.resolvePeerByName(name: addressName, referrer: nil)
|
||||||
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
|
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
|
||||||
guard case let .result(result) = result else {
|
guard case let .result(result) = result else {
|
||||||
@ -305,7 +305,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
|||||||
|> castError(ChatContextQueryError.self)
|
|> castError(ChatContextQueryError.self)
|
||||||
|> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in
|
|> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> in
|
||||||
if case let .user(user) = peer, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder {
|
if case let .user(user) = peer, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder {
|
||||||
let contextResults = context.engine.messages.requestChatContextResults(botId: user.id, peerId: chatPeer.id, query: query, location: context.sharedContext.locationManager.flatMap { locationManager -> Signal<(Double, Double)?, NoError> in
|
let contextResults = context.engine.messages.requestChatContextResults(botId: user.id, peerId: chatPeerId, query: query, location: context.sharedContext.locationManager.flatMap { locationManager -> Signal<(Double, Double)?, NoError> in
|
||||||
return `deferred` {
|
return `deferred` {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
requestBotLocationStatus(user.id)
|
requestBotLocationStatus(user.id)
|
||||||
|
@ -3316,7 +3316,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
|||||||
component: AnyComponent(EmojiSuggestionsComponent(
|
component: AnyComponent(EmojiSuggestionsComponent(
|
||||||
context: context,
|
context: context,
|
||||||
userLocation: .other,
|
userLocation: .other,
|
||||||
theme: EmojiSuggestionsComponent.Theme(theme: theme),
|
theme: EmojiSuggestionsComponent.Theme(theme: theme, backgroundColor: theme.list.itemBlocksBackgroundColor),
|
||||||
animationCache: presentationContext.animationCache,
|
animationCache: presentationContext.animationCache,
|
||||||
animationRenderer: presentationContext.animationRenderer,
|
animationRenderer: presentationContext.animationRenderer,
|
||||||
files: value,
|
files: value,
|
||||||
|
@ -189,6 +189,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
return self.hasGroupCallOnScreenPromise.get()
|
return self.hasGroupCallOnScreenPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var streamController: MediaStreamComponentController?
|
||||||
|
|
||||||
private var immediateHasOngoingCallValue = Atomic<Bool>(value: false)
|
private var immediateHasOngoingCallValue = Atomic<Bool>(value: false)
|
||||||
public var immediateHasOngoingCall: Bool {
|
public var immediateHasOngoingCall: Bool {
|
||||||
return self.immediateHasOngoingCallValue.with { $0 }
|
return self.immediateHasOngoingCallValue.with { $0 }
|
||||||
@ -947,64 +949,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
} else if let current = self.currentCall, case .group = current {
|
} else if let current = self.currentCall, case .group = current {
|
||||||
self.updateCurrentCall(call: nil)
|
self.updateCurrentCall(call: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if call.flatMap(VideoChatCall.group) != self.groupCallController?.call {
|
|
||||||
self.groupCallController?.dismiss(closing: true, manual: false)
|
|
||||||
self.groupCallController = nil
|
|
||||||
self.hasOngoingCall.set(false)
|
|
||||||
|
|
||||||
if let call = call, let navigationController = mainWindow.viewController as? NavigationController {
|
|
||||||
mainWindow.hostView.containerView.endEditing(true)
|
|
||||||
|
|
||||||
if call.isStream {
|
|
||||||
self.hasGroupCallOnScreenPromise.set(true)
|
|
||||||
let groupCallController = MediaStreamComponentController(call: call)
|
|
||||||
groupCallController.onViewDidAppear = { [weak self] in
|
|
||||||
if let self {
|
|
||||||
self.hasGroupCallOnScreenPromise.set(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
groupCallController.onViewDidDisappear = { [weak self] in
|
|
||||||
if let self {
|
|
||||||
self.hasGroupCallOnScreenPromise.set(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
groupCallController.navigationPresentation = .flatModal
|
|
||||||
groupCallController.parentNavigationController = navigationController
|
|
||||||
self.groupCallController = groupCallController
|
|
||||||
navigationController.pushViewController(groupCallController)
|
|
||||||
} else {
|
|
||||||
self.hasGroupCallOnScreenPromise.set(true)
|
|
||||||
|
|
||||||
let _ = (makeVoiceChatControllerInitialData(sharedContext: self, accountContext: call.accountContext, call: .group(call))
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self, weak navigationController] initialData in
|
|
||||||
guard let self, let navigationController else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let groupCallController = makeVoiceChatController(sharedContext: self, accountContext: call.accountContext, call: .group(call), initialData: initialData, sourceCallController: nil)
|
|
||||||
groupCallController.onViewDidAppear = { [weak self] in
|
|
||||||
if let self {
|
|
||||||
self.hasGroupCallOnScreenPromise.set(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
groupCallController.onViewDidDisappear = { [weak self] in
|
|
||||||
if let self {
|
|
||||||
self.hasGroupCallOnScreenPromise.set(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
groupCallController.navigationPresentation = .flatModal
|
|
||||||
groupCallController.parentNavigationController = navigationController
|
|
||||||
strongSelf.groupCallController = groupCallController
|
|
||||||
navigationController.pushViewController(groupCallController)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
self.hasOngoingCall.set(true)
|
|
||||||
} else {
|
|
||||||
self.hasOngoingCall.set(false)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mainWindow.inCallNavigate = { [weak self] in
|
mainWindow.inCallNavigate = { [weak self] in
|
||||||
@ -1219,6 +1163,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
self.groupCallController = nil
|
self.groupCallController = nil
|
||||||
groupCallController.dismiss()
|
groupCallController.dismiss()
|
||||||
}
|
}
|
||||||
|
if let streamController = self.streamController {
|
||||||
|
self.streamController = nil
|
||||||
|
streamController.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
if shouldResetGroupCallOnScreen {
|
if shouldResetGroupCallOnScreen {
|
||||||
self.hasGroupCallOnScreenPromise.set(.single(false))
|
self.hasGroupCallOnScreenPromise.set(.single(false))
|
||||||
@ -1346,7 +1294,12 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if case let .group(groupCall) = call {
|
var groupCallIsStream = false
|
||||||
|
if case let .group(groupCall) = call, case let .group(value) = groupCall {
|
||||||
|
groupCallIsStream = value.isStream
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .group(groupCall) = call, !groupCallIsStream {
|
||||||
let _ = (makeVoiceChatControllerInitialData(sharedContext: self, accountContext: groupCall.accountContext, call: groupCall)
|
let _ = (makeVoiceChatControllerInitialData(sharedContext: self, accountContext: groupCall.accountContext, call: groupCall)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self, weak transitioningToConferenceCallController] initialData in
|
|> deliverOnMainQueue).start(next: { [weak self, weak transitioningToConferenceCallController] initialData in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -1399,6 +1352,17 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if case let .group(groupCall) = call, case let .group(group) = groupCall, groupCallIsStream {
|
||||||
|
if let navigationController = self.mainWindow?.viewController as? NavigationController {
|
||||||
|
let streamController = MediaStreamComponentController(call: group)
|
||||||
|
streamController.navigationPresentation = .flatModal
|
||||||
|
streamController.parentNavigationController = navigationController
|
||||||
|
self.streamController = streamController
|
||||||
|
|
||||||
|
navigationController.pushViewController(streamController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.currentCall != nil {
|
if self.currentCall != nil {
|
||||||
self.callStateDisposable = (combineLatest(queue: .mainQueue(),
|
self.callStateDisposable = (combineLatest(queue: .mainQueue(),
|
||||||
self.hasGroupCallOnScreenPromise.get(),
|
self.hasGroupCallOnScreenPromise.get(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user