mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
Initial support for channel stats
UI fixes
This commit is contained in:
parent
cd08fa1f3b
commit
c1067d24cb
@ -1120,6 +1120,8 @@
|
||||
D0F67FF21EE6B915000E5906 /* ChannelMembersSearchControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F67FF11EE6B915000E5906 /* ChannelMembersSearchControllerNode.swift */; };
|
||||
D0F67FF41EE6C10F000E5906 /* ChannelMembersSearchContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F67FF31EE6C10F000E5906 /* ChannelMembersSearchContainerNode.swift */; };
|
||||
D0F6800A1EE750EE000E5906 /* ChannelBannedMemberController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F680091EE750EE000E5906 /* ChannelBannedMemberController.swift */; };
|
||||
D0F760DB222034910074F7E5 /* ChannelStatsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F760DA222034910074F7E5 /* ChannelStatsController.swift */; };
|
||||
D0F760DD222034980074F7E5 /* ChannelStatsControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F760DC222034980074F7E5 /* ChannelStatsControllerNode.swift */; };
|
||||
D0F8C397201774A200236FC5 /* FeedGroupingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C396201774A200236FC5 /* FeedGroupingController.swift */; };
|
||||
D0F8C399201774AF00236FC5 /* FeedGroupingControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C398201774AF00236FC5 /* FeedGroupingControllerNode.swift */; };
|
||||
D0F9720F1FFE4BD5002595C8 /* notification.caf in Resources */ = {isa = PBXBuildFile; fileRef = D0C50E431E93FCD200F62E39 /* notification.caf */; };
|
||||
@ -2332,6 +2334,8 @@
|
||||
D0F69EA91D6B9BCB0046BCD6 /* libavformat.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavformat.a; path = "third-party/FFmpeg-iOS/lib/libavformat.a"; sourceTree = "<group>"; };
|
||||
D0F69EAA1D6B9BCB0046BCD6 /* libavutil.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libavutil.a; path = "third-party/FFmpeg-iOS/lib/libavutil.a"; sourceTree = "<group>"; };
|
||||
D0F69EAB1D6B9BCB0046BCD6 /* libswresample.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libswresample.a; path = "third-party/FFmpeg-iOS/lib/libswresample.a"; sourceTree = "<group>"; };
|
||||
D0F760DA222034910074F7E5 /* ChannelStatsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelStatsController.swift; sourceTree = "<group>"; };
|
||||
D0F760DC222034980074F7E5 /* ChannelStatsControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelStatsControllerNode.swift; sourceTree = "<group>"; };
|
||||
D0F7AB341DCFADCD009AD9A1 /* ChatMessageBubbleImages.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageBubbleImages.swift; sourceTree = "<group>"; };
|
||||
D0F7AB381DCFF87B009AD9A1 /* ChatMessageDateHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageDateHeader.swift; sourceTree = "<group>"; };
|
||||
D0F8C396201774A200236FC5 /* FeedGroupingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedGroupingController.swift; sourceTree = "<group>"; };
|
||||
@ -4206,6 +4210,8 @@
|
||||
D0F4B019211073C500912B92 /* DeviceContactInfoController.swift */,
|
||||
092F368F2157AB46001A9F49 /* ItemListCallListItem.swift */,
|
||||
09DD88E821BAF65E000766BC /* ItemListAddressItem.swift */,
|
||||
D0F760DA222034910074F7E5 /* ChannelStatsController.swift */,
|
||||
D0F760DC222034980074F7E5 /* ChannelStatsControllerNode.swift */,
|
||||
);
|
||||
name = "Peer Info";
|
||||
sourceTree = "<group>";
|
||||
@ -5335,6 +5341,7 @@
|
||||
D0EC6D161EB9F58800EBF1C3 /* MediaTrackFrameDecoder.swift in Sources */,
|
||||
D056CD701FF147B000880D28 /* IconButtonNode.swift in Sources */,
|
||||
D0EC6D171EB9F58800EBF1C3 /* FFMpegAudioFrameDecoder.swift in Sources */,
|
||||
D0F760DB222034910074F7E5 /* ChannelStatsController.swift in Sources */,
|
||||
D0EC6D181EB9F58800EBF1C3 /* FFMpegMediaFrameSource.swift in Sources */,
|
||||
D0EC6D191EB9F58800EBF1C3 /* FFMpegMediaFrameSourceContext.swift in Sources */,
|
||||
D02D60AE206BD47300FEFE1E /* SecureIdDocumentTypeSelectionController.swift in Sources */,
|
||||
@ -5369,6 +5376,7 @@
|
||||
D007019E2029EFDD006B9E34 /* ICloudResources.swift in Sources */,
|
||||
D0EC6D221EB9F58800EBF1C3 /* PhotoResources.swift in Sources */,
|
||||
D048EA871F4F296400188713 /* InstantPageSettingsFontSizeItemNode.swift in Sources */,
|
||||
D0F760DD222034980074F7E5 /* ChannelStatsControllerNode.swift in Sources */,
|
||||
D0EC6D231EB9F58800EBF1C3 /* StickerResources.swift in Sources */,
|
||||
09C9EA3821A044B500E90146 /* StringForDuration.swift in Sources */,
|
||||
D0EC6D241EB9F58800EBF1C3 /* CachedResourceRepresentations.swift in Sources */,
|
||||
|
@ -15,6 +15,7 @@ private final class ChannelInfoControllerArguments {
|
||||
let openChannelTypeSetup: () -> Void
|
||||
let changeNotificationMuteSettings: () -> Void
|
||||
let openSharedMedia: () -> Void
|
||||
let openStats: () -> Void
|
||||
let openAdmins: () -> Void
|
||||
let openMembers: () -> Void
|
||||
let openBanned: () -> Void
|
||||
@ -25,7 +26,7 @@ private final class ChannelInfoControllerArguments {
|
||||
let displayContextMenu: (ChannelInfoEntryTag, String) -> Void
|
||||
let aboutLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void
|
||||
let toggleSignatures:(Bool) -> Void
|
||||
init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, tapAvatarAction: @escaping () -> Void, changeProfilePhoto: @escaping () -> Void, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updateEditingDescriptionText: @escaping (String) -> Void, openChannelTypeSetup: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openAdmins: @escaping () -> Void, openMembers: @escaping () -> Void, openBanned: @escaping () -> Void, reportChannel: @escaping () -> Void, leaveChannel: @escaping () -> Void, deleteChannel: @escaping () -> Void, displayAddressNameContextMenu: @escaping (String) -> Void, displayContextMenu: @escaping (ChannelInfoEntryTag, String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, toggleSignatures: @escaping(Bool)->Void) {
|
||||
init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, tapAvatarAction: @escaping () -> Void, changeProfilePhoto: @escaping () -> Void, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updateEditingDescriptionText: @escaping (String) -> Void, openChannelTypeSetup: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openStats: @escaping () -> Void, openAdmins: @escaping () -> Void, openMembers: @escaping () -> Void, openBanned: @escaping () -> Void, reportChannel: @escaping () -> Void, leaveChannel: @escaping () -> Void, deleteChannel: @escaping () -> Void, displayAddressNameContextMenu: @escaping (String) -> Void, displayContextMenu: @escaping (ChannelInfoEntryTag, String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, toggleSignatures: @escaping(Bool)->Void) {
|
||||
self.account = account
|
||||
self.avatarAndNameInfoContext = avatarAndNameInfoContext
|
||||
self.tapAvatarAction = tapAvatarAction
|
||||
@ -35,6 +36,7 @@ private final class ChannelInfoControllerArguments {
|
||||
self.openChannelTypeSetup = openChannelTypeSetup
|
||||
self.changeNotificationMuteSettings = changeNotificationMuteSettings
|
||||
self.openSharedMedia = openSharedMedia
|
||||
self.openStats = openStats
|
||||
self.openAdmins = openAdmins
|
||||
self.openMembers = openMembers
|
||||
self.openBanned = openBanned
|
||||
@ -73,6 +75,7 @@ private enum ChannelInfoEntry: ItemListNodeEntry {
|
||||
case banned(theme: PresentationTheme, text: String, value: String)
|
||||
case notifications(theme: PresentationTheme, text: String, value: String)
|
||||
case sharedMedia(theme: PresentationTheme, text: String)
|
||||
case stats(theme: PresentationTheme, text: String)
|
||||
case signMessages(theme: PresentationTheme, text: String, value: Bool)
|
||||
case signInfo(theme: PresentationTheme, text: String)
|
||||
case report(theme: PresentationTheme, text: String)
|
||||
@ -87,7 +90,7 @@ private enum ChannelInfoEntry: ItemListNodeEntry {
|
||||
return ChannelInfoSection.discriptionAndType.rawValue
|
||||
case .admins, .members, .banned:
|
||||
return ChannelInfoSection.members.rawValue
|
||||
case .sharedMedia, .notifications:
|
||||
case .sharedMedia, .notifications, .stats:
|
||||
return ChannelInfoSection.sharedMediaAndNotifications.rawValue
|
||||
case .report, .leave, .deleteChannel:
|
||||
return ChannelInfoSection.reportOrLeave.rawValue
|
||||
@ -122,12 +125,14 @@ private enum ChannelInfoEntry: ItemListNodeEntry {
|
||||
return 12
|
||||
case .sharedMedia:
|
||||
return 14
|
||||
case .report:
|
||||
case .stats:
|
||||
return 15
|
||||
case .leave:
|
||||
case .report:
|
||||
return 16
|
||||
case .deleteChannel:
|
||||
case .leave:
|
||||
return 17
|
||||
case .deleteChannel:
|
||||
return 18
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,6 +240,12 @@ private enum ChannelInfoEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .stats(lhsTheme, lhsText):
|
||||
if case let .stats(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .report(lhsTheme, lhsText):
|
||||
if case let .report(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -322,6 +333,10 @@ private enum ChannelInfoEntry: ItemListNodeEntry {
|
||||
return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .plain, action: {
|
||||
arguments.openSharedMedia()
|
||||
})
|
||||
case let .stats(theme, text):
|
||||
return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .plain, action: {
|
||||
arguments.openStats()
|
||||
})
|
||||
case let .notifications(theme, text, value):
|
||||
return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: {
|
||||
arguments.changeNotificationMuteSettings()
|
||||
@ -482,6 +497,9 @@ private func channelInfoEntries(account: Account, presentationData: Presentation
|
||||
}
|
||||
if state.editingState == nil {
|
||||
entries.append(ChannelInfoEntry.sharedMedia(theme: presentationData.theme, text: presentationData.strings.GroupInfo_SharedMedia))
|
||||
if let cachedChannelData = view.cachedData as? CachedChannelData, cachedChannelData.flags.contains(.canViewStats) {
|
||||
entries.append(ChannelInfoEntry.stats(theme: presentationData.theme, text: presentationData.strings.ChannelInfo_Stats))
|
||||
}
|
||||
}
|
||||
|
||||
if peer.flags.contains(.isCreator) {
|
||||
@ -557,6 +575,9 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
|
||||
let navigateDisposable = MetaDisposable()
|
||||
actionsDisposable.add(navigateDisposable)
|
||||
|
||||
let statsUrlDisposable = MetaDisposable()
|
||||
actionsDisposable.add(statsUrlDisposable)
|
||||
|
||||
var avatarGalleryTransitionArguments: ((AvatarGalleryEntry) -> GalleryTransitionArguments?)?
|
||||
let avatarAndNameInfoContext = ItemListAvatarAndNameInfoItemContext()
|
||||
var updateHiddenAvatarImpl: (() -> Void)?
|
||||
@ -722,6 +743,43 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
|
||||
if let controller = peerSharedMediaController(context: context, peerId: peerId) {
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
}, openStats: {
|
||||
var urlSignal = channelStatsUrl(postbox: context.account.postbox, network: context.account.network, peerId: peerId, params: "")
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, strings: presentationData.strings, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
presentControllerImpl?(controller, nil)
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.15, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.start()
|
||||
|
||||
urlSignal = urlSignal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
cancelImpl = {
|
||||
statsUrlDisposable.set(nil)
|
||||
}
|
||||
|
||||
statsUrlDisposable.set((urlSignal
|
||||
|> deliverOnMainQueue).start(next: { url in
|
||||
pushControllerImpl?(ChannelStatsController(context: context, url: url, peerId: peerId))
|
||||
}, error: { _ in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
}))
|
||||
}, openAdmins: {
|
||||
pushControllerImpl?(channelAdminsController(context: context, peerId: peerId))
|
||||
}, openMembers: {
|
||||
|
67
TelegramUI/ChannelStatsController.swift
Normal file
67
TelegramUI/ChannelStatsController.swift
Normal file
@ -0,0 +1,67 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
|
||||
final class ChannelStatsController: ViewController {
|
||||
private var controllerNode: ChannelStatsControllerNode {
|
||||
return self.displayNode as! ChannelStatsControllerNode
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let url: String
|
||||
private let peerId: PeerId
|
||||
|
||||
private var presentationData: PresentationData
|
||||
|
||||
init(context: AccountContext, url: String, peerId: PeerId) {
|
||||
self.context = context
|
||||
self.url = url
|
||||
self.peerId = peerId
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||
|
||||
self.navigationItem.title = self.presentationData.strings.ChannelInfo_Stats
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc func closePressed() {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
override func loadDisplayNode() {
|
||||
self.displayNode = ChannelStatsControllerNode(context: self.context, presentationData: self.presentationData, peerId: self.peerId, url: self.url, present: { [weak self] c, a in
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
})
|
||||
}
|
||||
|
||||
override func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.controllerNode.animateOut(completion: { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
}
|
||||
|
||||
override var presentationController: UIPresentationController? {
|
||||
get {
|
||||
return nil
|
||||
} set(value) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
144
TelegramUI/ChannelStatsControllerNode.swift
Normal file
144
TelegramUI/ChannelStatsControllerNode.swift
Normal file
@ -0,0 +1,144 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import WebKit
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
private class WeakChannelStatsScriptMessageHandler: NSObject, WKScriptMessageHandler {
|
||||
private let f: (WKScriptMessage) -> ()
|
||||
|
||||
init(_ f: @escaping (WKScriptMessage) -> ()) {
|
||||
self.f = f
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
func userContentController(_ controller: WKUserContentController, didReceive scriptMessage: WKScriptMessage) {
|
||||
self.f(scriptMessage)
|
||||
}
|
||||
}
|
||||
|
||||
final class ChannelStatsControllerNode: ViewControllerTracingNode, WKNavigationDelegate {
|
||||
private var webView: WKWebView?
|
||||
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
var presentationData: PresentationData
|
||||
private let present: (ViewController, Any?) -> Void
|
||||
|
||||
private let refreshDisposable = MetaDisposable()
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, peerId: PeerId, url: String, present: @escaping (ViewController, Any?) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.peerId = peerId
|
||||
self.present = present
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = .white
|
||||
|
||||
let js = "var TelegramWebviewProxyProto = function() {}; " +
|
||||
"TelegramWebviewProxyProto.prototype.postEvent = function(eventName, eventData) { " +
|
||||
"window.webkit.messageHandlers.performAction.postMessage({'eventName': eventName, 'eventData': eventData}); " +
|
||||
"}; " +
|
||||
"var TelegramWebviewProxy = new TelegramWebviewProxyProto();"
|
||||
|
||||
let configuration = WKWebViewConfiguration()
|
||||
let userController = WKUserContentController()
|
||||
|
||||
let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: false)
|
||||
userController.addUserScript(userScript)
|
||||
|
||||
userController.add(WeakChannelStatsScriptMessageHandler { [weak self] message in
|
||||
if let strongSelf = self {
|
||||
strongSelf.handleScriptMessage(message)
|
||||
}
|
||||
}, name: "performAction")
|
||||
|
||||
configuration.userContentController = userController
|
||||
let webView = WKWebView(frame: CGRect(), configuration: configuration)
|
||||
if #available(iOSApplicationExtension 9.0, *) {
|
||||
webView.allowsLinkPreview = false
|
||||
}
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
webView.navigationDelegate = self
|
||||
webView.interactiveTransitionGestureRecognizerTest = { point -> Bool in
|
||||
return point.x > 30.0
|
||||
}
|
||||
|
||||
self.view.addSubview(webView)
|
||||
self.webView = webView
|
||||
|
||||
if let parsedUrl = URL(string: url) {
|
||||
webView.load(URLRequest(url: parsedUrl))
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.refreshDisposable.dispose()
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if let webView = self.webView {
|
||||
webView.frame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: max(1.0, layout.size.height - navigationBarHeight)))
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
func animateOut(completion: (() -> Void)? = nil) {
|
||||
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { _ in
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
|
||||
private func handleScriptMessage(_ message: WKScriptMessage) {
|
||||
guard let body = message.body as? [String: Any] else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let eventName = body["eventName"] as? String else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
|
||||
if let url = navigationAction.request.url, url.scheme == "tg" {
|
||||
if url.path == "statsrefresh" {
|
||||
var params = ""
|
||||
if let query = url.query, let components = URLComponents(string: "/" + query) {
|
||||
if let queryItems = components.queryItems {
|
||||
for queryItem in queryItems {
|
||||
if let value = queryItem.value {
|
||||
if queryItem.name == "params" {
|
||||
params = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.refreshDisposable.set((channelStatsUrl(postbox: self.context.account.postbox, network: self.context.account.network, peerId: self.peerId, params: params)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] url in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let parsedUrl = URL(string: url) {
|
||||
strongSelf.webView?.load(URLRequest(url: parsedUrl))
|
||||
}
|
||||
}, error: { _ in
|
||||
|
||||
}))
|
||||
}
|
||||
decisionHandler(.cancel)
|
||||
} else {
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
}
|
||||
}
|
@ -2117,6 +2117,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
if let strongSelf = self, strongSelf.isNodeLoaded, canSendMessagesToChat(strongSelf.presentationInterfaceState) {
|
||||
if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState({ $0.withUpdatedReplyMessageId(message.id) }).updatedSearch(nil) })
|
||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||
strongSelf.chatDisplayNode.ensureInputViewFocused()
|
||||
}
|
||||
}
|
||||
@ -2353,6 +2354,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
}
|
||||
}.updatedSearch(current.search == nil ? ChatSearchData(domain: domain).withUpdatedQuery(query) : current.search?.withUpdatedDomain(domain).withUpdatedQuery(query))
|
||||
})
|
||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||
}
|
||||
}, dismissMessageSearch: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
@ -2369,6 +2371,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
return current
|
||||
}
|
||||
})
|
||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||
}
|
||||
}, navigateMessageSearch: { [weak self] action in
|
||||
if let strongSelf = self {
|
||||
@ -2395,6 +2398,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
}
|
||||
return current
|
||||
})
|
||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||
if let navigateIndex = navigateIndex {
|
||||
switch strongSelf.chatLocation {
|
||||
case .peer:
|
||||
@ -2441,6 +2445,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
return state
|
||||
}
|
||||
})
|
||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||
}
|
||||
}, navigateToMessage: { [weak self] messageId in
|
||||
self?.navigateToMessage(from: nil, to: .id(messageId))
|
||||
@ -3692,6 +3697,21 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
}
|
||||
}
|
||||
|
||||
private func updateItemNodesSearchTextHighlightStates() {
|
||||
var searchString: String?
|
||||
if let search = self.presentationInterfaceState.search, let resultsState = search.resultsState, !resultsState.messageIndices.isEmpty {
|
||||
searchString = search.query
|
||||
}
|
||||
if searchString != self.controllerInteraction?.searchTextHighightState {
|
||||
self.controllerInteraction?.searchTextHighightState = searchString
|
||||
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
itemNode.updateSearchTextHighlightState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateItemNodesHighlightedStates(animated: Bool) {
|
||||
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
@ -4721,6 +4741,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
strongSelf.navigateToMessage(from: nil, to: .index(navigateIndex))
|
||||
}
|
||||
}
|
||||
strongSelf.updateItemNodesSearchTextHighlightStates()
|
||||
}, completed: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.searching.set(false)
|
||||
@ -4771,6 +4792,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
}
|
||||
}
|
||||
}
|
||||
self.updateItemNodesSearchTextHighlightStates()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -5303,7 +5325,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
}
|
||||
self.commitPurposefulAction()
|
||||
self.chatDisplayNode.historyNode.disconnect()
|
||||
let _ = removePeerChat(postbox: self.context.account.postbox, peerId: peerId, reportChatSpam: reportChatSpam).start()
|
||||
let _ = removePeerChat(account: self.context.account, peerId: peerId, reportChatSpam: reportChatSpam).start()
|
||||
(self.navigationController as? NavigationController)?.popToRoot(animated: true)
|
||||
|
||||
let _ = requestUpdatePeerIsBlocked(account: self.context.account, peerId: peerId, isBlocked: true).start()
|
||||
|
@ -96,6 +96,7 @@ public final class ChatControllerInteraction {
|
||||
var contextHighlightedState: ChatInterfaceHighlightedState?
|
||||
var automaticMediaDownloadSettings: MediaAutoDownloadSettings
|
||||
var pollActionState: ChatInterfacePollActionState
|
||||
var searchTextHighightState: String?
|
||||
|
||||
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState) {
|
||||
self.openMessage = openMessage
|
||||
|
@ -445,7 +445,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
|
||||
if let strongSelf = self {
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
|
||||
let _ = removePeerChat(postbox: strongSelf.context.account.postbox, peerId: peerId, reportChatSpam: false).start(completed: {
|
||||
let _ = removePeerChat(account: strongSelf.context.account, peerId: peerId, reportChatSpam: false).start(completed: {
|
||||
self?.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
|
||||
})
|
||||
let _ = requestUpdatePeerIsBlocked(account: strongSelf.context.account, peerId: peer.peerId, isBlocked: true).start()
|
||||
@ -1087,7 +1087,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
|
||||
let signal: Signal<Void, NoError> = strongSelf.context.account.postbox.transaction { transaction -> Void in
|
||||
for peerId in peerIds {
|
||||
removePeerChat(transaction: transaction, mediaBox: context.account.postbox.mediaBox, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: false)
|
||||
removePeerChat(account: context.account, transaction: transaction, mediaBox: context.account.postbox.mediaBox, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: false)
|
||||
}
|
||||
}
|
||||
|> afterDisposed {
|
||||
@ -1157,7 +1157,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie
|
||||
}
|
||||
if shouldCommit {
|
||||
strongSelf.chatListDisplayNode.chatListNode.setCurrentRemovingPeerId(peerId)
|
||||
let _ = removePeerChat(postbox: strongSelf.context.account.postbox, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: deleteGloballyIfPossible).start(completed: {
|
||||
let _ = removePeerChat(account: strongSelf.context.account, peerId: peerId, reportChatSpam: false, deleteGloballyIfPossible: deleteGloballyIfPossible).start(completed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
@ -141,6 +141,9 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
|
||||
return false
|
||||
}
|
||||
|
||||
func updateSearchTextHighlightState(text: String?) {
|
||||
}
|
||||
|
||||
func updateAutomaticMediaDownloadSettings(_ settings: MediaAutoDownloadSettings) {
|
||||
}
|
||||
|
||||
|
@ -1449,6 +1449,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
actionButtonsNode.removeFromSupernode()
|
||||
strongSelf.actionButtonsNode = nil
|
||||
}
|
||||
|
||||
strongSelf.updateSearchTextHighlightState()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1958,6 +1960,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
override func updateSearchTextHighlightState() {
|
||||
for contentNode in self.contentNodes {
|
||||
contentNode.updateSearchTextHighlightState(text: self.item?.controllerInteraction.searchTextHighightState)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateHighlightedState(animated: Bool) {
|
||||
super.updateHighlightedState(animated: animated)
|
||||
|
||||
|
@ -209,6 +209,9 @@ public class ChatMessageItemView: ListViewItemNode {
|
||||
func updateSelectionState(animated: Bool) {
|
||||
}
|
||||
|
||||
func updateSearchTextHighlightState() {
|
||||
}
|
||||
|
||||
func updateHighlightedState(animated: Bool) {
|
||||
var isHighlightedInOverlay = false
|
||||
if let item = self.item, let contextHighlightedState = item.controllerInteraction.contextHighlightedState {
|
||||
|
@ -35,6 +35,8 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
private let statusNode: ChatMessageDateAndStatusNode
|
||||
private var linkHighlightingNode: LinkHighlightingNode?
|
||||
|
||||
private var textHighlightingNodes: [LinkHighlightingNode] = []
|
||||
|
||||
private var cachedChatMessageText: CachedChatMessageText?
|
||||
|
||||
required init() {
|
||||
@ -384,4 +386,33 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override func updateSearchTextHighlightState(text: String?) {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
let rectsSet: [[CGRect]]
|
||||
if let text = text, !text.isEmpty {
|
||||
rectsSet = self.textNode.textRangesRects(text: text)
|
||||
} else {
|
||||
rectsSet = []
|
||||
}
|
||||
for i in 0 ..< rectsSet.count {
|
||||
let rects = rectsSet[i]
|
||||
let textHighlightNode: LinkHighlightingNode
|
||||
if self.textHighlightingNodes.count < i {
|
||||
textHighlightNode = self.textHighlightingNodes[i]
|
||||
} else {
|
||||
textHighlightNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.bubble.incomingTextHighlightColor : item.presentationData.theme.theme.chat.bubble.outgoingTextHighlightColor)
|
||||
self.textHighlightingNodes.append(textHighlightNode)
|
||||
self.insertSubnode(textHighlightNode, belowSubnode: self.textNode)
|
||||
}
|
||||
textHighlightNode.frame = self.textNode.frame
|
||||
textHighlightNode.updateRects(rects)
|
||||
}
|
||||
for i in (rectsSet.count ..< self.textHighlightingNodes.count).reversed() {
|
||||
self.textHighlightingNodes[i].removeFromSupernode()
|
||||
self.textHighlightingNodes.remove(at: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,6 +158,8 @@ private let bubble = PresentationThemeChatBubble(
|
||||
outgoingLinkHighlightColor: accentColor.withAlphaComponent(0.5),
|
||||
infoPrimaryTextColor: UIColor(rgb: 0xffffff),
|
||||
infoLinkTextColor: accentColor,
|
||||
incomingTextHighlightColor: UIColor(rgb: 0xffe438),
|
||||
outgoingTextHighlightColor: UIColor(rgb: 0xffe438),
|
||||
incomingAccentTextColor: UIColor(rgb: 0xffffff),
|
||||
outgoingAccentTextColor: UIColor(rgb: 0xffffff),
|
||||
incomingAccentControlColor: UIColor(rgb: 0xffffff),
|
||||
|
@ -158,6 +158,8 @@ private let bubble = PresentationThemeChatBubble(
|
||||
outgoingLinkHighlightColor: accentColor.withAlphaComponent(0.5),
|
||||
infoPrimaryTextColor: UIColor(rgb: 0xffffff),
|
||||
infoLinkTextColor: accentColor,
|
||||
incomingTextHighlightColor: UIColor(rgb: 0xffe438),
|
||||
outgoingTextHighlightColor: UIColor(rgb: 0xffe438),
|
||||
incomingAccentTextColor: UIColor(rgb: 0xffffff),
|
||||
outgoingAccentTextColor: UIColor(rgb: 0xffffff),
|
||||
incomingAccentControlColor: UIColor(rgb: 0xffffff),
|
||||
|
@ -189,6 +189,8 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun
|
||||
outgoingLinkHighlightColor: accentColor.withAlphaComponent(0.3),
|
||||
infoPrimaryTextColor: UIColor(rgb: 0x000000),
|
||||
infoLinkTextColor: UIColor(rgb: 0x004bad),
|
||||
incomingTextHighlightColor: UIColor(rgb: 0xffe438),
|
||||
outgoingTextHighlightColor: UIColor(rgb: 0xffe438),
|
||||
incomingAccentTextColor: UIColor(rgb: 0x007ee5),
|
||||
outgoingAccentTextColor: UIColor(rgb: 0x00a700),
|
||||
incomingAccentControlColor: UIColor(rgb: 0x007ee5),
|
||||
@ -245,6 +247,8 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, serviceBackgroun
|
||||
outgoingLinkHighlightColor: UIColor(rgb: 0xffffff, alpha: 0.3),
|
||||
infoPrimaryTextColor: UIColor(rgb: 0x000000),
|
||||
infoLinkTextColor: UIColor(rgb: 0x004bad),
|
||||
incomingTextHighlightColor: UIColor(rgb: 0xffe438),
|
||||
outgoingTextHighlightColor: UIColor(rgb: 0xffe438),
|
||||
incomingAccentTextColor: accentColor,
|
||||
outgoingAccentTextColor: UIColor(rgb: 0xffffff),
|
||||
incomingAccentControlColor: accentColor,
|
||||
|
@ -970,6 +970,25 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
|
||||
}
|
||||
if let contactDataManager = context.sharedContext.contactDataManager {
|
||||
let _ = (contactDataManager.createContactWithData(composedContactData)
|
||||
|> mapToSignal { contactIdAndData -> Signal<(DeviceContactStableId, DeviceContactExtendedData, Peer?)?, NoError> in
|
||||
guard let (id, data) = contactIdAndData else {
|
||||
return .single(nil)
|
||||
}
|
||||
if filteredPhoneNumbers.count == 1 {
|
||||
return importContact(account: context.account, firstName: composedContactData.basicData.firstName, lastName: composedContactData.basicData.lastName, phoneNumber: filteredPhoneNumbers[0].value)
|
||||
|> mapToSignal { peerId -> Signal<(DeviceContactStableId, DeviceContactExtendedData, Peer?)?, NoError> in
|
||||
if let peerId = peerId {
|
||||
return context.account.postbox.transaction { transaction -> (DeviceContactStableId, DeviceContactExtendedData, Peer?)? in
|
||||
return (id, data, transaction.getPeer(peerId))
|
||||
}
|
||||
} else {
|
||||
return .single((id, data, nil))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single((id, data, nil))
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { contactIdAndData in
|
||||
updateState { state in
|
||||
var state = state
|
||||
@ -977,7 +996,7 @@ public func deviceContactInfoController(context: AccountContext, subject: Device
|
||||
return state
|
||||
}
|
||||
if let contactIdAndData = contactIdAndData {
|
||||
//completion(nil, contactIdAndData.0, contactIdAndData.1)
|
||||
completion(contactIdAndData.2, contactIdAndData.0, contactIdAndData.1)
|
||||
}
|
||||
completed?()
|
||||
dismissImpl?(true)
|
||||
|
@ -206,7 +206,18 @@ final class FFMpegMediaFrameSource: NSObject, MediaFrameSource {
|
||||
let preferSoftwareDecoding = self.preferSoftwareDecoding
|
||||
let fetchAutomatically = self.fetchAutomatically
|
||||
|
||||
let currentSemaphore = Atomic<Atomic<DispatchSemaphore?>?>(value: nil)
|
||||
|
||||
disposable.set(ActionDisposable {
|
||||
self.performWithContext({ context in
|
||||
context.close()
|
||||
})
|
||||
currentSemaphore.with({ $0 })?.with({ $0 })?.signal()
|
||||
})
|
||||
|
||||
self.performWithContext { [weak self] context in
|
||||
let _ = currentSemaphore.swap(context.currentSemaphore)
|
||||
|
||||
context.initializeState(postbox: postbox, resourceReference: resourceReference, tempFilePath: tempFilePath, streamable: streamable, video: video, preferSoftwareDecoding: preferSoftwareDecoding, fetchAutomatically: fetchAutomatically)
|
||||
|
||||
context.seek(timestamp: timestamp, completed: { streamDescriptions, timestamp in
|
||||
|
@ -85,14 +85,21 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
||||
fetchedData = fileData
|
||||
} else {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
let _ = context.currentSemaphore.swap(semaphore)
|
||||
var completedRequest = false
|
||||
let disposable = data.start(next: { data in
|
||||
if data.count == readCount {
|
||||
fetchedData = data
|
||||
completedRequest = true
|
||||
semaphore.signal()
|
||||
}
|
||||
})
|
||||
semaphore.wait()
|
||||
let _ = context.currentSemaphore.swap(nil)
|
||||
disposable.dispose()
|
||||
if !completedRequest {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -115,7 +122,9 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
||||
} else {
|
||||
let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false))
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
let _ = context.currentSemaphore.swap(semaphore)
|
||||
let readingOffset = context.readingOffset
|
||||
var completedRequest = false
|
||||
let disposable = data.start(next: { next in
|
||||
if next.complete {
|
||||
let readCount = max(0, min(next.size - readingOffset, Int(bufferSize)))
|
||||
@ -132,11 +141,16 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
||||
fetchedData = data
|
||||
close(fd)
|
||||
}
|
||||
completedRequest = true
|
||||
semaphore.signal()
|
||||
}
|
||||
})
|
||||
semaphore.wait()
|
||||
let _ = context.currentSemaphore.swap(nil)
|
||||
disposable.dispose()
|
||||
if !completedRequest {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
}
|
||||
if let fetchedData = fetchedData {
|
||||
@ -147,6 +161,9 @@ private func readPacketCallback(userData: UnsafeMutableRawPointer?, buffer: Unsa
|
||||
context.readingOffset += Int(fetchedCount)
|
||||
}
|
||||
|
||||
if context.closed {
|
||||
return -1
|
||||
}
|
||||
return fetchedCount
|
||||
}
|
||||
|
||||
@ -169,14 +186,21 @@ private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whe
|
||||
var resultSize: Int = Int(Int32.max - 1)
|
||||
let data = postbox.mediaBox.resourceData(resourceReference.resource, pathExtension: nil, option: .complete(waitUntilFetchStatus: false))
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
let _ = context.currentSemaphore.swap(semaphore)
|
||||
var completedRequest = false
|
||||
let disposable = data.start(next: { next in
|
||||
if next.complete {
|
||||
resultSize = Int(next.size)
|
||||
completedRequest = true
|
||||
semaphore.signal()
|
||||
}
|
||||
})
|
||||
semaphore.wait()
|
||||
let _ = context.currentSemaphore.swap(nil)
|
||||
disposable.dispose()
|
||||
if !completedRequest {
|
||||
return -1
|
||||
}
|
||||
resourceSize = resultSize
|
||||
}
|
||||
} else {
|
||||
@ -210,6 +234,10 @@ private func seekCallback(userData: UnsafeMutableRawPointer?, offset: Int64, whe
|
||||
}
|
||||
}
|
||||
|
||||
if context.closed {
|
||||
return -1
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@ -240,6 +268,8 @@ final class FFMpegMediaFrameSourceContext: NSObject {
|
||||
private var preferSoftwareDecoding: Bool = false
|
||||
fileprivate var fetchAutomatically: Bool = true
|
||||
|
||||
let currentSemaphore = Atomic<DispatchSemaphore?>(value: nil)
|
||||
|
||||
init(thread: Thread) {
|
||||
self.thread = thread
|
||||
}
|
||||
@ -584,6 +614,10 @@ final class FFMpegMediaFrameSourceContext: NSObject {
|
||||
completed(FFMpegMediaFrameSourceDescriptionSet(audio: audioDescription, video: videoDescription, extraVideoFrames: extraVideoFrames), actualPts)
|
||||
}
|
||||
}
|
||||
|
||||
func close() {
|
||||
self.closed = true
|
||||
}
|
||||
}
|
||||
|
||||
private func videoFrameFromPacket(_ packet: FFMpegPacket, videoStream: StreamContext) -> MediaTrackDecodableFrame {
|
||||
|
@ -21,7 +21,7 @@ func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaResourceD
|
||||
let madeProgress = Atomic<Bool>(value: false)
|
||||
option.progressHandler = { progress, error, _, _ in
|
||||
if !madeProgress.swap(true) {
|
||||
subscriber.putNext(.reset)
|
||||
//subscriber.putNext(.reset)
|
||||
}
|
||||
}
|
||||
let size = CGSize(width: 1280.0, height: 1280.0)
|
||||
@ -37,7 +37,7 @@ func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaResourceD
|
||||
if let image = image {
|
||||
if let info = info, let degraded = info[PHImageResultIsDegradedKey], (degraded as AnyObject).boolValue!{
|
||||
if !madeProgress.swap(true) {
|
||||
subscriber.putNext(.reset)
|
||||
//subscriber.putNext(.reset)
|
||||
}
|
||||
} else {
|
||||
_ = madeProgress.swap(true)
|
||||
@ -71,7 +71,7 @@ func fetchPhotoLibraryResource(localIdentifier: String) -> Signal<MediaResourceD
|
||||
}
|
||||
} else {
|
||||
if !madeProgress.swap(true) {
|
||||
subscriber.putNext(.reset)
|
||||
//subscriber.putNext(.reset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -502,6 +502,8 @@ public final class PresentationThemeChatBubble {
|
||||
public let outgoingLinkHighlightColor: UIColor
|
||||
public let infoPrimaryTextColor: UIColor
|
||||
public let infoLinkTextColor: UIColor
|
||||
public let incomingTextHighlightColor: UIColor
|
||||
public let outgoingTextHighlightColor: UIColor
|
||||
|
||||
public let incomingAccentTextColor: UIColor
|
||||
public let outgoingAccentTextColor: UIColor
|
||||
@ -557,7 +559,7 @@ public final class PresentationThemeChatBubble {
|
||||
public let incomingPolls: PresentationThemeChatBubblePolls
|
||||
public let outgoingPolls: PresentationThemeChatBubblePolls
|
||||
|
||||
public init(incoming: PresentationThemeBubbleColor, outgoing: PresentationThemeBubbleColor, freeform: PresentationThemeBubbleColor, incomingPrimaryTextColor: UIColor, incomingSecondaryTextColor: UIColor, incomingLinkTextColor: UIColor, incomingLinkHighlightColor: UIColor, outgoingPrimaryTextColor: UIColor, outgoingSecondaryTextColor: UIColor, outgoingLinkTextColor: UIColor, outgoingLinkHighlightColor: UIColor, infoPrimaryTextColor: UIColor, infoLinkTextColor: UIColor, incomingAccentTextColor: UIColor, outgoingAccentTextColor: UIColor, incomingAccentControlColor: UIColor, outgoingAccentControlColor: UIColor, incomingMediaActiveControlColor: UIColor, outgoingMediaActiveControlColor: UIColor, incomingMediaInactiveControlColor: UIColor, outgoingMediaInactiveControlColor: UIColor, outgoingCheckColor: UIColor, incomingPendingActivityColor: UIColor, outgoingPendingActivityColor: UIColor, mediaDateAndStatusFillColor: UIColor, mediaDateAndStatusTextColor: UIColor, incomingFileTitleColor: UIColor, outgoingFileTitleColor: UIColor, incomingFileDescriptionColor: UIColor, outgoingFileDescriptionColor: UIColor, incomingFileDurationColor: UIColor, outgoingFileDurationColor: UIColor, shareButtonFillColor: PresentationThemeVariableColor, shareButtonStrokeColor: PresentationThemeVariableColor, shareButtonForegroundColor: PresentationThemeVariableColor, mediaOverlayControlBackgroundColor: UIColor, mediaOverlayControlForegroundColor: UIColor, actionButtonsIncomingFillColor: PresentationThemeVariableColor, actionButtonsIncomingStrokeColor: PresentationThemeVariableColor, actionButtonsIncomingTextColor: PresentationThemeVariableColor, actionButtonsOutgoingFillColor: PresentationThemeVariableColor, actionButtonsOutgoingStrokeColor: PresentationThemeVariableColor, actionButtonsOutgoingTextColor: PresentationThemeVariableColor, selectionControlBorderColor: UIColor, selectionControlFillColor: UIColor, selectionControlForegroundColor: UIColor, mediaHighlightOverlayColor: UIColor, deliveryFailedFillColor: UIColor, deliveryFailedForegroundColor: UIColor, incomingMediaPlaceholderColor: UIColor, outgoingMediaPlaceholderColor: UIColor, incomingPolls: PresentationThemeChatBubblePolls, outgoingPolls: PresentationThemeChatBubblePolls) {
|
||||
public init(incoming: PresentationThemeBubbleColor, outgoing: PresentationThemeBubbleColor, freeform: PresentationThemeBubbleColor, incomingPrimaryTextColor: UIColor, incomingSecondaryTextColor: UIColor, incomingLinkTextColor: UIColor, incomingLinkHighlightColor: UIColor, outgoingPrimaryTextColor: UIColor, outgoingSecondaryTextColor: UIColor, outgoingLinkTextColor: UIColor, outgoingLinkHighlightColor: UIColor, infoPrimaryTextColor: UIColor, infoLinkTextColor: UIColor, incomingTextHighlightColor: UIColor, outgoingTextHighlightColor: UIColor, incomingAccentTextColor: UIColor, outgoingAccentTextColor: UIColor, incomingAccentControlColor: UIColor, outgoingAccentControlColor: UIColor, incomingMediaActiveControlColor: UIColor, outgoingMediaActiveControlColor: UIColor, incomingMediaInactiveControlColor: UIColor, outgoingMediaInactiveControlColor: UIColor, outgoingCheckColor: UIColor, incomingPendingActivityColor: UIColor, outgoingPendingActivityColor: UIColor, mediaDateAndStatusFillColor: UIColor, mediaDateAndStatusTextColor: UIColor, incomingFileTitleColor: UIColor, outgoingFileTitleColor: UIColor, incomingFileDescriptionColor: UIColor, outgoingFileDescriptionColor: UIColor, incomingFileDurationColor: UIColor, outgoingFileDurationColor: UIColor, shareButtonFillColor: PresentationThemeVariableColor, shareButtonStrokeColor: PresentationThemeVariableColor, shareButtonForegroundColor: PresentationThemeVariableColor, mediaOverlayControlBackgroundColor: UIColor, mediaOverlayControlForegroundColor: UIColor, actionButtonsIncomingFillColor: PresentationThemeVariableColor, actionButtonsIncomingStrokeColor: PresentationThemeVariableColor, actionButtonsIncomingTextColor: PresentationThemeVariableColor, actionButtonsOutgoingFillColor: PresentationThemeVariableColor, actionButtonsOutgoingStrokeColor: PresentationThemeVariableColor, actionButtonsOutgoingTextColor: PresentationThemeVariableColor, selectionControlBorderColor: UIColor, selectionControlFillColor: UIColor, selectionControlForegroundColor: UIColor, mediaHighlightOverlayColor: UIColor, deliveryFailedFillColor: UIColor, deliveryFailedForegroundColor: UIColor, incomingMediaPlaceholderColor: UIColor, outgoingMediaPlaceholderColor: UIColor, incomingPolls: PresentationThemeChatBubblePolls, outgoingPolls: PresentationThemeChatBubblePolls) {
|
||||
self.incoming = incoming
|
||||
self.outgoing = outgoing
|
||||
self.freeform = freeform
|
||||
@ -572,6 +574,8 @@ public final class PresentationThemeChatBubble {
|
||||
self.outgoingLinkHighlightColor = outgoingLinkHighlightColor
|
||||
self.infoPrimaryTextColor = infoPrimaryTextColor
|
||||
self.infoLinkTextColor = infoLinkTextColor
|
||||
self.incomingTextHighlightColor = incomingTextHighlightColor
|
||||
self.outgoingTextHighlightColor = outgoingTextHighlightColor
|
||||
|
||||
self.incomingAccentTextColor = incomingAccentTextColor
|
||||
self.outgoingAccentTextColor = outgoingAccentTextColor
|
||||
|
Binary file not shown.
@ -140,7 +140,8 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me
|
||||
|
||||
let result: Signal<MediaResourceData, NoError>
|
||||
if opportunistic {
|
||||
result = signal |> take(1)
|
||||
result = signal
|
||||
|> take(1)
|
||||
} else {
|
||||
result = signal
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user