mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-29 19:35:08 +00:00
Added galleries for web search results
This commit is contained in:
parent
feaaed3f41
commit
98de1f903f
@ -90,6 +90,8 @@
|
|||||||
09DD88F321BF907C000766BC /* WebSearchRecentQueryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88F221BF907C000766BC /* WebSearchRecentQueryItem.swift */; };
|
09DD88F321BF907C000766BC /* WebSearchRecentQueryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88F221BF907C000766BC /* WebSearchRecentQueryItem.swift */; };
|
||||||
09DD88F521BF9730000766BC /* WebSearchRecentQueries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88F421BF9730000766BC /* WebSearchRecentQueries.swift */; };
|
09DD88F521BF9730000766BC /* WebSearchRecentQueries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88F421BF9730000766BC /* WebSearchRecentQueries.swift */; };
|
||||||
09DD88FA21BFD70B000766BC /* ThemedTextAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88F921BFD70B000766BC /* ThemedTextAlertController.swift */; };
|
09DD88FA21BFD70B000766BC /* ThemedTextAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88F921BFD70B000766BC /* ThemedTextAlertController.swift */; };
|
||||||
|
09F799FA21C3542D00820234 /* LegacyWebSearchGallery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F799F921C3542D00820234 /* LegacyWebSearchGallery.swift */; };
|
||||||
|
09F799FC21C3FF3000820234 /* WebSearchGalleryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F799FB21C3FF3000820234 /* WebSearchGalleryController.swift */; };
|
||||||
09FE756D2153F5F900A3120F /* CallRouteActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */; };
|
09FE756D2153F5F900A3120F /* CallRouteActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */; };
|
||||||
9F06830921A404AB001D8EDB /* NotificationExceptionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.swift */; };
|
9F06830921A404AB001D8EDB /* NotificationExceptionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.swift */; };
|
||||||
9F06830B21A404C4001D8EDB /* NotificationExcetionSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830A21A404C4001D8EDB /* NotificationExcetionSettingsController.swift */; };
|
9F06830B21A404C4001D8EDB /* NotificationExcetionSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830A21A404C4001D8EDB /* NotificationExcetionSettingsController.swift */; };
|
||||||
@ -1178,6 +1180,8 @@
|
|||||||
09DD88F221BF907C000766BC /* WebSearchRecentQueryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchRecentQueryItem.swift; sourceTree = "<group>"; };
|
09DD88F221BF907C000766BC /* WebSearchRecentQueryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchRecentQueryItem.swift; sourceTree = "<group>"; };
|
||||||
09DD88F421BF9730000766BC /* WebSearchRecentQueries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchRecentQueries.swift; sourceTree = "<group>"; };
|
09DD88F421BF9730000766BC /* WebSearchRecentQueries.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchRecentQueries.swift; sourceTree = "<group>"; };
|
||||||
09DD88F921BFD70B000766BC /* ThemedTextAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedTextAlertController.swift; sourceTree = "<group>"; };
|
09DD88F921BFD70B000766BC /* ThemedTextAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedTextAlertController.swift; sourceTree = "<group>"; };
|
||||||
|
09F799F921C3542D00820234 /* LegacyWebSearchGallery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyWebSearchGallery.swift; sourceTree = "<group>"; };
|
||||||
|
09F799FB21C3FF3000820234 /* WebSearchGalleryController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchGalleryController.swift; sourceTree = "<group>"; };
|
||||||
09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRouteActionSheetItem.swift; sourceTree = "<group>"; };
|
09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRouteActionSheetItem.swift; sourceTree = "<group>"; };
|
||||||
9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExceptionControllerNode.swift; sourceTree = "<group>"; };
|
9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExceptionControllerNode.swift; sourceTree = "<group>"; };
|
||||||
9F06830A21A404C4001D8EDB /* NotificationExcetionSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExcetionSettingsController.swift; sourceTree = "<group>"; };
|
9F06830A21A404C4001D8EDB /* NotificationExcetionSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExcetionSettingsController.swift; sourceTree = "<group>"; };
|
||||||
@ -2360,6 +2364,7 @@
|
|||||||
0962E67E21BA786A00245FD9 /* WebSearchItem.swift */,
|
0962E67E21BA786A00245FD9 /* WebSearchItem.swift */,
|
||||||
09DD88F221BF907C000766BC /* WebSearchRecentQueryItem.swift */,
|
09DD88F221BF907C000766BC /* WebSearchRecentQueryItem.swift */,
|
||||||
09DD88F421BF9730000766BC /* WebSearchRecentQueries.swift */,
|
09DD88F421BF9730000766BC /* WebSearchRecentQueries.swift */,
|
||||||
|
09F799FB21C3FF3000820234 /* WebSearchGalleryController.swift */,
|
||||||
);
|
);
|
||||||
name = "Web Search";
|
name = "Web Search";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -3171,6 +3176,7 @@
|
|||||||
D07ABBAA202A1BD1003671DE /* LegacyWallpaperEditor.swift */,
|
D07ABBAA202A1BD1003671DE /* LegacyWallpaperEditor.swift */,
|
||||||
D0380DAC204ED434000414AB /* LegacyLiveUploadInterface.swift */,
|
D0380DAC204ED434000414AB /* LegacyLiveUploadInterface.swift */,
|
||||||
D097C26B20DD1EA5007BB4B8 /* OverlayStatusController.swift */,
|
D097C26B20DD1EA5007BB4B8 /* OverlayStatusController.swift */,
|
||||||
|
09F799F921C3542D00820234 /* LegacyWebSearchGallery.swift */,
|
||||||
);
|
);
|
||||||
name = "Legacy Components";
|
name = "Legacy Components";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -5035,6 +5041,7 @@
|
|||||||
D01776B51F1D6CCC0044446D /* RadialStatusContentNode.swift in Sources */,
|
D01776B51F1D6CCC0044446D /* RadialStatusContentNode.swift in Sources */,
|
||||||
D02F4AF01FD4C46D004DFBAE /* SystemVideoContent.swift in Sources */,
|
D02F4AF01FD4C46D004DFBAE /* SystemVideoContent.swift in Sources */,
|
||||||
D0477D1F1F619E0700412B44 /* GalleryVideoDecoration.swift in Sources */,
|
D0477D1F1F619E0700412B44 /* GalleryVideoDecoration.swift in Sources */,
|
||||||
|
09F799FA21C3542D00820234 /* LegacyWebSearchGallery.swift in Sources */,
|
||||||
D01C99781F4F382C00DCFAF6 /* InstantPageSettingsItemTheme.swift in Sources */,
|
D01C99781F4F382C00DCFAF6 /* InstantPageSettingsItemTheme.swift in Sources */,
|
||||||
D0CAD8FB20AE1D1B00ACD96E /* ChannelMemberCategoryListContext.swift in Sources */,
|
D0CAD8FB20AE1D1B00ACD96E /* ChannelMemberCategoryListContext.swift in Sources */,
|
||||||
D0EC6CC21EB9F58800EBF1C3 /* LegacyEmptyController.swift in Sources */,
|
D0EC6CC21EB9F58800EBF1C3 /* LegacyEmptyController.swift in Sources */,
|
||||||
@ -5585,6 +5592,7 @@
|
|||||||
D0EC6DD41EB9F58900EBF1C3 /* CommandChatInputContextPanelNode.swift in Sources */,
|
D0EC6DD41EB9F58900EBF1C3 /* CommandChatInputContextPanelNode.swift in Sources */,
|
||||||
D0EC6DD51EB9F58900EBF1C3 /* CommandChatInputPanelItem.swift in Sources */,
|
D0EC6DD51EB9F58900EBF1C3 /* CommandChatInputPanelItem.swift in Sources */,
|
||||||
D0EC6DD61EB9F58900EBF1C3 /* VerticalListContextResultsChatInputContextPanelNode.swift in Sources */,
|
D0EC6DD61EB9F58900EBF1C3 /* VerticalListContextResultsChatInputContextPanelNode.swift in Sources */,
|
||||||
|
09F799FC21C3FF3000820234 /* WebSearchGalleryController.swift in Sources */,
|
||||||
D0EC6DD71EB9F58900EBF1C3 /* VerticalListContextResultsChatInputPanelItem.swift in Sources */,
|
D0EC6DD71EB9F58900EBF1C3 /* VerticalListContextResultsChatInputPanelItem.swift in Sources */,
|
||||||
D0EC6DD81EB9F58900EBF1C3 /* VerticalListContextResultsChatInputPanelButtonItem.swift in Sources */,
|
D0EC6DD81EB9F58900EBF1C3 /* VerticalListContextResultsChatInputPanelButtonItem.swift in Sources */,
|
||||||
D04281F4200E5AB0009DDE36 /* ChatRecentActionsController.swift in Sources */,
|
D04281F4200E5AB0009DDE36 /* ChatRecentActionsController.swift in Sources */,
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import Foundation
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
|
||||||
private final class CallRatingContentActionNode: HighlightableButtonNode {
|
private final class CallRatingContentActionNode: HighlightableButtonNode {
|
||||||
@ -320,8 +321,19 @@ private final class CallRatingAlertContentNode: AlertContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func rateCallAndSendLogs(account: Account, report: ReportCallRating, starsCount: Int, comment: String, includeLogs: Bool) {
|
private func rateCallAndSendLogs(account: Account, report: ReportCallRating, starsCount: Int, comment: String, includeLogs: Bool) -> Signal<Void, NoError> {
|
||||||
let _ = rateCall(account: account, report: report, starsCount: Int32(starsCount), comment: comment).start()
|
var signal = rateCall(account: account, report: report, starsCount: Int32(starsCount), comment: comment)
|
||||||
|
if includeLogs {
|
||||||
|
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 4244000)
|
||||||
|
// signal = signal
|
||||||
|
// |> then(
|
||||||
|
// enqueueMessages(account: account, peerId: peerId, messages: EnqueueMessage.message(text: "", attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil))
|
||||||
|
// |> map { _ -> Void in
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
return signal
|
||||||
}
|
}
|
||||||
|
|
||||||
func callRatingController(account: Account, report: ReportCallRating, present: @escaping (ViewController) -> Void) -> AlertController {
|
func callRatingController(account: Account, report: ReportCallRating, present: @escaping (ViewController) -> Void) -> AlertController {
|
||||||
@ -338,13 +350,13 @@ func callRatingController(account: Account, report: ReportCallRating, present: @
|
|||||||
if let contentNode = contentNode, let rating = contentNode.rating {
|
if let contentNode = contentNode, let rating = contentNode.rating {
|
||||||
if rating < 4 {
|
if rating < 4 {
|
||||||
let controller = textAlertController(account: account, title: strings.Call_ReportIncludeLog, text: strings.Call_ReportIncludeLogDescription, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Call_ReportSkip, action: {
|
let controller = textAlertController(account: account, title: strings.Call_ReportIncludeLog, text: strings.Call_ReportIncludeLogDescription, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Call_ReportSkip, action: {
|
||||||
rateCallAndSendLogs(account: account, report: report, starsCount: rating, comment: contentNode.comment, includeLogs: false)
|
let _ = rateCallAndSendLogs(account: account, report: report, starsCount: rating, comment: contentNode.comment, includeLogs: false).start()
|
||||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Call_ReportSend, action: {
|
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Call_ReportSend, action: {
|
||||||
rateCallAndSendLogs(account: account, report: report, starsCount: rating, comment: contentNode.comment, includeLogs: true)
|
let _ = rateCallAndSendLogs(account: account, report: report, starsCount: rating, comment: contentNode.comment, includeLogs: true).start()
|
||||||
})])
|
})])
|
||||||
present(controller)
|
present(controller)
|
||||||
} else {
|
} else {
|
||||||
rateCallAndSendLogs(account: account, report: report, starsCount: rating, comment: contentNode.comment, includeLogs: false)
|
let _ = rateCallAndSendLogs(account: account, report: report, starsCount: rating, comment: contentNode.comment, includeLogs: false).start
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})]
|
})]
|
||||||
|
|||||||
@ -177,7 +177,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
|
|||||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false)
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
let textNodeFrame = self.textNode.frame
|
let textNodeFrame = self.textNode.frame
|
||||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||||
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
@ -206,7 +206,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
|
|||||||
switch recognizer.state {
|
switch recognizer.state {
|
||||||
case .ended:
|
case .ended:
|
||||||
var foundTapAction = false
|
var foundTapAction = false
|
||||||
let tapAction = self.tapActionAtPoint(recognizer.location(in: self.view))
|
let tapAction = self.tapActionAtPoint(recognizer.location(in: self.view), gesture: .tap)
|
||||||
switch tapAction {
|
switch tapAction {
|
||||||
case .none:
|
case .none:
|
||||||
break
|
break
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import MobileCoreServices
|
import MobileCoreServices
|
||||||
|
import LegacyComponents
|
||||||
|
|
||||||
public enum ChatControllerPeekActions {
|
public enum ChatControllerPeekActions {
|
||||||
case standard
|
case standard
|
||||||
@ -3747,11 +3748,12 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentMediaPicker(fileMode: Bool, editingMedia: Bool, completion: @escaping ([Any]) -> Void) {
|
private func presentMediaPicker(fileMode: Bool, editingMedia: Bool, completion: @escaping ([Any]) -> Void) {
|
||||||
let _ = (self.account.postbox.transaction { transaction -> GeneratedMediaStoreSettings in
|
let _ = (self.account.postbox.transaction { transaction -> (GeneratedMediaStoreSettings, SearchBotsConfiguration) in
|
||||||
let entry = transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.generatedMediaStoreSettings) as? GeneratedMediaStoreSettings
|
let entry = transaction.getPreferencesEntry(key: ApplicationSpecificPreferencesKeys.generatedMediaStoreSettings) as? GeneratedMediaStoreSettings
|
||||||
return entry ?? GeneratedMediaStoreSettings.defaultSettings
|
let configuration = currentSearchBotsConfiguration(transaction: transaction)
|
||||||
|
return (entry ?? GeneratedMediaStoreSettings.defaultSettings, configuration)
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] settings in
|
|> deliverOnMainQueue).start(next: { [weak self] settings, searchBotsConfiguration in
|
||||||
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -3766,7 +3768,14 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
legacyController.bind(controller: controller)
|
legacyController.bind(controller: controller)
|
||||||
legacyController.deferScreenEdgeGestures = [.top]
|
legacyController.deferScreenEdgeGestures = [.top]
|
||||||
|
|
||||||
configureLegacyAssetPicker(controller, account: strongSelf.account, peer: peer)
|
configureLegacyAssetPicker(controller, account: strongSelf.account, peer: peer, presentWebSearch: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let controller = WebSearchController(account: strongSelf.account, chatLocation: .peer(peer.id), configuration: searchBotsConfiguration, sendSelected: { (resuls, collection, editingContext) in
|
||||||
|
|
||||||
|
})
|
||||||
|
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||||
|
}
|
||||||
|
})
|
||||||
controller.descriptionGenerator = legacyAssetPickerItemGenerator()
|
controller.descriptionGenerator = legacyAssetPickerItemGenerator()
|
||||||
controller.completionBlock = { [weak legacyController] signals in
|
controller.completionBlock = { [weak legacyController] signals in
|
||||||
if let legacyController = legacyController {
|
if let legacyController = legacyController {
|
||||||
@ -3800,19 +3809,45 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] configuration in
|
|> deliverOnMainQueue).start(next: { [weak self] configuration in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let controller = WebSearchController(account: strongSelf.account, chatLocation: strongSelf.chatLocation, configuration: configuration, sendSelected: { [weak self] ids, collection in
|
let controller = WebSearchController(account: strongSelf.account, chatLocation: strongSelf.chatLocation, configuration: configuration, sendSelected: { [weak self] ids, collection, editingContext in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
var results: [ChatContextResult] = []
|
||||||
for id in ids {
|
for id in ids {
|
||||||
var result: ChatContextResult?
|
var result: ChatContextResult?
|
||||||
for r in collection.results {
|
for r in collection.results {
|
||||||
if r.id == id {
|
if r.id == id {
|
||||||
result = r
|
result = r
|
||||||
|
results.append(r)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let result = result {
|
}
|
||||||
strongSelf.enqueueChatContextResult(collection, result, includeViaBot: false)
|
|
||||||
|
if !results.isEmpty {
|
||||||
|
var signals: [Any] = []
|
||||||
|
for result in results {
|
||||||
|
let editableItem = LegacyWebSearchItem(result: result, dimensions: CGSize(), thumbnailImage: .complete(), originalImage: .complete())
|
||||||
|
if editingContext.adjustments(for: editableItem) != nil {
|
||||||
|
if let imageSignal = editingContext.imageSignal(for: editableItem) {
|
||||||
|
let signal = imageSignal.map { image -> Any in
|
||||||
|
if let image = image as? UIImage {
|
||||||
|
let dict: [AnyHashable: Any] = [
|
||||||
|
"type": "editedPhoto",
|
||||||
|
"image": image
|
||||||
|
]
|
||||||
|
return legacyAssetPickerItemGenerator()(dict, nil, nil, nil)
|
||||||
|
} else {
|
||||||
|
return SSignal.complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
signals.append(signal)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strongSelf.enqueueChatContextResult(collection, result, includeViaBot: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf.enqueueMediaMessages(signals: signals)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -4027,11 +4062,14 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
|||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] settings in
|
|> deliverOnMainQueue).start(next: { [weak self] settings in
|
||||||
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
|
||||||
let controller = legacyPasteMenu(account: strongSelf.account, peer: peer, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, images: images, sendMessagesWithSignals: { signals in
|
|
||||||
self?.enqueueMediaMessages(signals: signals)
|
|
||||||
})
|
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
strongSelf.present(controller, in: .window(.root))
|
let _ = presentLegacyPasteMenu(account: strongSelf.account, peer: peer, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, images: images, sendMessagesWithSignals: { signals in
|
||||||
|
self?.enqueueMediaMessages(signals: signals)
|
||||||
|
}, present: { [weak self] controller, arguments in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.present(controller, in: .window(.root), with: arguments)
|
||||||
|
}
|
||||||
|
}, initialLayout: strongSelf.validLayout)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -544,14 +544,15 @@ final class ChatListNode: ListView {
|
|||||||
if previousState.editing != state.editing {
|
if previousState.editing != state.editing {
|
||||||
disableAnimations = false
|
disableAnimations = false
|
||||||
} else {
|
} else {
|
||||||
var previousPinnedCount = 0
|
var previousPinnedChats: [PeerId] = []
|
||||||
var updatedPinnedCount = 0
|
var updatedPinnedChats: [PeerId] = []
|
||||||
|
|
||||||
var didIncludeRemovingPeerId = false
|
var didIncludeRemovingPeerId = false
|
||||||
if let previous = previousView {
|
if let previous = previousView {
|
||||||
for entry in previous.filteredEntries {
|
for entry in previous.filteredEntries {
|
||||||
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||||
if index.pinningIndex != nil {
|
if index.pinningIndex != nil {
|
||||||
previousPinnedCount += 1
|
previousPinnedChats.append(index.messageIndex.id.peerId)
|
||||||
}
|
}
|
||||||
if index.messageIndex.id.peerId == removingPeerId {
|
if index.messageIndex.id.peerId == removingPeerId {
|
||||||
didIncludeRemovingPeerId = true
|
didIncludeRemovingPeerId = true
|
||||||
@ -563,14 +564,14 @@ final class ChatListNode: ListView {
|
|||||||
for entry in processedView.filteredEntries {
|
for entry in processedView.filteredEntries {
|
||||||
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||||
if index.pinningIndex != nil {
|
if index.pinningIndex != nil {
|
||||||
updatedPinnedCount += 1
|
updatedPinnedChats.append(index.messageIndex.id.peerId)
|
||||||
}
|
}
|
||||||
if index.messageIndex.id.peerId == removingPeerId {
|
if index.messageIndex.id.peerId == removingPeerId {
|
||||||
doesIncludeRemovingPeerId = true
|
doesIncludeRemovingPeerId = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if previousPinnedCount != updatedPinnedCount {
|
if previousPinnedChats != updatedPinnedChats {
|
||||||
disableAnimations = false
|
disableAnimations = false
|
||||||
}
|
}
|
||||||
if previousState.selectedPeerIds != state.selectedPeerIds {
|
if previousState.selectedPeerIds != state.selectedPeerIds {
|
||||||
@ -580,6 +581,7 @@ final class ChatListNode: ListView {
|
|||||||
disableAnimations = false
|
disableAnimations = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var searchMode = false
|
var searchMode = false
|
||||||
if case .peers = mode {
|
if case .peers = mode {
|
||||||
|
|||||||
@ -29,16 +29,16 @@ enum UnreadSearchBadge : Equatable {
|
|||||||
|
|
||||||
var count: Int32 {
|
var count: Int32 {
|
||||||
switch self {
|
switch self {
|
||||||
case let .muted(count), let .unmuted(count):
|
case let .muted(count), let .unmuted(count):
|
||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var isMuted: Bool {
|
var isMuted: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .muted:
|
case .muted:
|
||||||
return true
|
return true
|
||||||
case .unmuted:
|
case .unmuted:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,6 +46,7 @@ enum UnreadSearchBadge : Equatable {
|
|||||||
private struct ChatListSearchRecentPeersEntry: Comparable, Identifiable {
|
private struct ChatListSearchRecentPeersEntry: Comparable, Identifiable {
|
||||||
let index: Int
|
let index: Int
|
||||||
let peer: Peer
|
let peer: Peer
|
||||||
|
let presence: PeerPresence?
|
||||||
let unreadBadge: UnreadSearchBadge?
|
let unreadBadge: UnreadSearchBadge?
|
||||||
let itemCustomWidth: CGFloat?
|
let itemCustomWidth: CGFloat?
|
||||||
var stableId: PeerId {
|
var stableId: PeerId {
|
||||||
@ -62,6 +63,13 @@ private struct ChatListSearchRecentPeersEntry: Comparable, Identifiable {
|
|||||||
if !lhs.peer.isEqual(rhs.peer) {
|
if !lhs.peer.isEqual(rhs.peer) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if let lhsPresence = lhs.presence, let rhsPresence = rhs.presence {
|
||||||
|
if !lhsPresence.isEqual(to: rhsPresence) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (lhs.presence != nil) != (rhs.presence != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.unreadBadge != rhs.unreadBadge {
|
if lhs.unreadBadge != rhs.unreadBadge {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -73,7 +81,7 @@ private struct ChatListSearchRecentPeersEntry: Comparable, Identifiable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, peerSelected: @escaping (Peer) -> Void, peerLongTapped: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool) -> ListViewItem {
|
func item(account: Account, theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, peerSelected: @escaping (Peer) -> Void, peerLongTapped: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool) -> ListViewItem {
|
||||||
return HorizontalPeerItem(theme: theme, strings: strings, mode: mode, account: account, peer: self.peer, unreadBadge: self.unreadBadge, action: peerSelected, longTapAction: { peer in
|
return HorizontalPeerItem(theme: theme, strings: strings, mode: mode, account: account, peer: self.peer, presence: self.presence, unreadBadge: self.unreadBadge, action: peerSelected, longTapAction: { peer in
|
||||||
peerLongTapped(peer)
|
peerLongTapped(peer)
|
||||||
}, isPeerSelected: isPeerSelected, customWidth: itemCustomWidth)
|
}, isPeerSelected: isPeerSelected, customWidth: itemCustomWidth)
|
||||||
}
|
}
|
||||||
@ -114,7 +122,7 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
|||||||
private let isPeerSelected: (PeerId) -> Bool
|
private let isPeerSelected: (PeerId) -> Bool
|
||||||
|
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
private let itemCustomWidthValuePromise:ValuePromise<CGFloat?> = ValuePromise(nil, ignoreRepeated: true)
|
private let itemCustomWidthValuePromise: ValuePromise<CGFloat?> = ValuePromise(nil, ignoreRepeated: true)
|
||||||
|
|
||||||
private var items: [ListViewItem] = []
|
private var items: [ListViewItem] = []
|
||||||
private var queuedTransitions: [ChatListSearchRecentNodeTransition] = []
|
private var queuedTransitions: [ChatListSearchRecentNodeTransition] = []
|
||||||
@ -140,8 +148,7 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
|||||||
|
|
||||||
let peersDisposable = DisposableSet()
|
let peersDisposable = DisposableSet()
|
||||||
|
|
||||||
|
let recent: Signal<([Peer], [PeerId: UnreadSearchBadge], [PeerId : PeerPresence]), NoError> = recentPeers(account: account)
|
||||||
let recent: Signal<([Peer], [PeerId : UnreadSearchBadge]), NoError> = recentPeers(account: account)
|
|
||||||
|> filter { value -> Bool in
|
|> filter { value -> Bool in
|
||||||
switch value {
|
switch value {
|
||||||
case .disabled:
|
case .disabled:
|
||||||
@ -151,47 +158,51 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
} |> mapToSignal { recent in
|
} |> mapToSignal { recent in
|
||||||
switch recent {
|
switch recent {
|
||||||
case .disabled:
|
case .disabled:
|
||||||
return .single(([], [:]))
|
return .single(([], [:], [:]))
|
||||||
case let .peers(peers):
|
case let .peers(peers):
|
||||||
return combineLatest(peers.filter { !$0.isDeleted }.map {account.postbox.peerView(id: $0.id)}) |> mapToSignal { peerViews -> Signal<([Peer], [PeerId: UnreadSearchBadge]), NoError> in
|
return combineLatest(peers.filter { !$0.isDeleted }.map {account.postbox.peerView(id: $0.id)}) |> mapToSignal { peerViews -> Signal<([Peer], [PeerId: UnreadSearchBadge], [PeerId: PeerPresence]), NoError> in
|
||||||
return account.postbox.unreadMessageCountsView(items: peerViews.map {.peer($0.peerId)}) |> map { values in
|
return account.postbox.unreadMessageCountsView(items: peerViews.map {.peer($0.peerId)}) |> map { values in
|
||||||
|
var peers: [Peer] = []
|
||||||
var peers:[Peer] = []
|
var unread: [PeerId: UnreadSearchBadge] = [:]
|
||||||
var unread:[PeerId: UnreadSearchBadge] = [:]
|
var presences: [PeerId: PeerPresence] = [:]
|
||||||
for peerView in peerViews {
|
for peerView in peerViews {
|
||||||
if let peer = peerViewMainPeer(peerView) {
|
if let peer = peerViewMainPeer(peerView) {
|
||||||
var isMuted: Bool = false
|
var isMuted: Bool = false
|
||||||
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
|
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
|
||||||
switch notificationSettings.muteState {
|
switch notificationSettings.muteState {
|
||||||
case .muted:
|
case .muted:
|
||||||
isMuted = true
|
isMuted = true
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let unreadCount = values.count(for: .peer(peerView.peerId))
|
||||||
|
if let unreadCount = unreadCount, unreadCount > 0 {
|
||||||
|
unread[peerView.peerId] = isMuted ? .muted(unreadCount) : .unmuted(unreadCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let presence = peerView.peerPresences[peer.id] {
|
||||||
|
presences[peer.id] = presence
|
||||||
|
}
|
||||||
|
|
||||||
|
peers.append(peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
let unreadCount = values.count(for: .peer(peerView.peerId))
|
|
||||||
if let unreadCount = unreadCount, unreadCount > 0 {
|
|
||||||
unread[peerView.peerId] = isMuted ? .muted(unreadCount) : .unmuted(unreadCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
peers.append(peer)
|
|
||||||
}
|
}
|
||||||
|
return (peers, unread, presences)
|
||||||
}
|
}
|
||||||
return (peers, unread)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let previous: Atomic<[ChatListSearchRecentPeersEntry]> = Atomic(value: [])
|
let previous: Atomic<[ChatListSearchRecentPeersEntry]> = Atomic(value: [])
|
||||||
let firstTime:Atomic<Bool> = Atomic(value: true)
|
let firstTime:Atomic<Bool> = Atomic(value: true)
|
||||||
peersDisposable.add(combineLatest(recent |> deliverOnMainQueue, itemCustomWidthValuePromise.get() |> deliverOnMainQueue).start(next: { [weak self] peers, itemCustomWidth in
|
peersDisposable.add((combineLatest(recent, self.itemCustomWidthValuePromise.get()) |> deliverOnMainQueue).start(next: { [weak self] peers, itemCustomWidth in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var entries: [ChatListSearchRecentPeersEntry] = []
|
var entries: [ChatListSearchRecentPeersEntry] = []
|
||||||
for peer in peers.0 {
|
for peer in peers.0 {
|
||||||
entries.append(ChatListSearchRecentPeersEntry(index: entries.count, peer: peer, unreadBadge: peers.1[peer.id], itemCustomWidth: itemCustomWidth))
|
entries.append(ChatListSearchRecentPeersEntry(index: entries.count, peer: peer, presence: peers.2[peer.id], unreadBadge: peers.1[peer.id], itemCustomWidth: itemCustomWidth))
|
||||||
}
|
}
|
||||||
|
|
||||||
let animated = !firstTime.swap(false)
|
let animated = !firstTime.swap(false)
|
||||||
@ -265,7 +276,7 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
|||||||
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 92.0, height: size.width)
|
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 92.0, height: size.width)
|
||||||
self.listView.position = CGPoint(x: size.width / 2.0, y: 92.0 / 2.0 + 29.0)
|
self.listView.position = CGPoint(x: size.width / 2.0, y: 92.0 / 2.0 + 29.0)
|
||||||
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: 92.0, height: size.width), insets: insets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: 92.0, height: size.width), insets: insets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||||
itemCustomWidthValuePromise.set(itemCustomWidth)
|
self.itemCustomWidthValuePromise.set(itemCustomWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewAndPeerAtPoint(_ point: CGPoint) -> (UIView, PeerId)? {
|
func viewAndPeerAtPoint(_ point: CGPoint) -> (UIView, PeerId)? {
|
||||||
|
|||||||
@ -377,7 +377,7 @@ private func universalServiceMessageString(theme: ChatPresentationThemeData?, st
|
|||||||
}
|
}
|
||||||
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
||||||
case let .customText(text, entities):
|
case let .customText(text, entities):
|
||||||
attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, fixedFont: titleFont)
|
attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, fixedFont: titleFont, underlineLinks: false)
|
||||||
case let .botDomainAccessGranted(domain):
|
case let .botDomainAccessGranted(domain):
|
||||||
attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).0, font: titleFont, textColor: primaryTextColor)
|
attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).0, font: titleFont, textColor: primaryTextColor)
|
||||||
case let .botSentSecureValues(types):
|
case let .botSentSecureValues(types):
|
||||||
@ -656,9 +656,9 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
let textNodeFrame = self.labelNode.frame
|
let textNodeFrame = self.labelNode.frame
|
||||||
if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)) {
|
if let (index, attributes) = self.labelNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY - 10.0)), gesture == .tap {
|
||||||
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
var concealed = true
|
var concealed = true
|
||||||
if let attributeText = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
if let attributeText = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||||
|
|||||||
@ -906,7 +906,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
let textNodeFrame = self.textNode.frame
|
let textNodeFrame = self.textNode.frame
|
||||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||||
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
|
|||||||
@ -143,7 +143,7 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
|
|||||||
func updateAutomaticMediaDownloadSettings(_ settings: AutomaticMediaDownloadSettings) {
|
func updateAutomaticMediaDownloadSettings(_ settings: AutomaticMediaDownloadSettings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -221,7 +221,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
|||||||
return .waitForSingleTap
|
return .waitForSingleTap
|
||||||
}
|
}
|
||||||
for contentNode in strongSelf.contentNodes {
|
for contentNode in strongSelf.contentNodes {
|
||||||
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: point.x - contentNode.frame.minX, y: point.y - contentNode.frame.minY))
|
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: point.x - contentNode.frame.minX, y: point.y - contentNode.frame.minY), gesture: .tap)
|
||||||
switch tapAction {
|
switch tapAction {
|
||||||
case .none:
|
case .none:
|
||||||
break
|
break
|
||||||
@ -1570,7 +1570,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
var foundTapAction = false
|
var foundTapAction = false
|
||||||
loop: for contentNode in self.contentNodes {
|
loop: for contentNode in self.contentNodes {
|
||||||
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY))
|
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture)
|
||||||
switch tapAction {
|
switch tapAction {
|
||||||
case .none, .ignore:
|
case .none, .ignore:
|
||||||
break
|
break
|
||||||
@ -1629,7 +1629,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
|||||||
selectAll = true
|
selectAll = true
|
||||||
}
|
}
|
||||||
tapMessage = contentNode.item?.message
|
tapMessage = contentNode.item?.message
|
||||||
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY))
|
let tapAction = contentNode.tapActionAtPoint(CGPoint(x: location.x - contentNode.frame.minX, y: location.y - contentNode.frame.minY), gesture: gesture)
|
||||||
switch tapAction {
|
switch tapAction {
|
||||||
case .none, .ignore:
|
case .none, .ignore:
|
||||||
break
|
break
|
||||||
|
|||||||
@ -210,7 +210,7 @@ class ChatMessageCallBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
if self.buttonNode.frame.contains(point) {
|
if self.buttonNode.frame.contains(point) {
|
||||||
return .ignore
|
return .ignore
|
||||||
} else if self.bounds.contains(point), let item = self.item {
|
} else if self.bounds.contains(point), let item = self.item {
|
||||||
|
|||||||
@ -312,7 +312,7 @@ class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
if self.buttonNode.frame.contains(point) {
|
if self.buttonNode.frame.contains(point) {
|
||||||
return .openMessage
|
return .openMessage
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,7 +82,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble
|
|||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
if self.bounds.contains(point) {
|
if self.bounds.contains(point) {
|
||||||
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
||||||
if content.instantPage != nil {
|
if content.instantPage != nil {
|
||||||
|
|||||||
@ -77,7 +77,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent
|
|||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
if self.bounds.contains(point) {
|
if self.bounds.contains(point) {
|
||||||
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
||||||
if content.instantPage != nil {
|
if content.instantPage != nil {
|
||||||
|
|||||||
@ -82,10 +82,10 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont
|
|||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
if self.bounds.contains(point) {
|
if self.bounds.contains(point) {
|
||||||
let contentNodeFrame = self.contentNode.frame
|
let contentNodeFrame = self.contentNode.frame
|
||||||
return self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY))
|
return self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture)
|
||||||
}
|
}
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,7 +110,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
if self.bounds.contains(point) {
|
if self.bounds.contains(point) {
|
||||||
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
||||||
if content.instantPage != nil {
|
if content.instantPage != nil {
|
||||||
|
|||||||
@ -449,10 +449,14 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
|
|||||||
statusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusSize.width, y: fittedLayoutSize.height - statusSize.height + 10.0), size: statusSize)
|
statusFrame = CGRect(origin: CGPoint(x: boundingWidth - statusSize.width, y: fittedLayoutSize.height - statusSize.height + 10.0), size: statusSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let statusFrameValue = statusFrame, descriptionFrame.intersects(statusFrameValue) {
|
if let statusFrameValue = statusFrame, descriptionFrame.intersects(statusFrameValue) {
|
||||||
fittedLayoutSize.height += 10.0
|
fittedLayoutSize.height += 10.0
|
||||||
statusFrame = statusFrameValue.offsetBy(dx: 0.0, dy: 10.0)
|
statusFrame = statusFrameValue.offsetBy(dx: 0.0, dy: 10.0)
|
||||||
}
|
}
|
||||||
|
if let statusFrameValue = statusFrame, let iconFrame = iconFrame, iconFrame.intersects(statusFrameValue) {
|
||||||
|
fittedLayoutSize.height += 15.0
|
||||||
|
statusFrame = statusFrameValue.offsetBy(dx: 0.0, dy: 15.0)
|
||||||
|
}
|
||||||
|
|
||||||
if isAudio && !isVoice {
|
if isAudio && !isVoice {
|
||||||
streamingCacheStatusFrame = CGRect(origin: CGPoint(x: fittedLayoutSize.width + 6.0, y: 8.0), size: CGSize(width: streamingProgressDiameter, height: streamingProgressDiameter))
|
streamingCacheStatusFrame = CGRect(origin: CGPoint(x: fittedLayoutSize.width + 6.0, y: 8.0), size: CGSize(width: streamingProgressDiameter, height: streamingProgressDiameter))
|
||||||
|
|||||||
@ -111,7 +111,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
if self.bounds.contains(point) {
|
if self.bounds.contains(point) {
|
||||||
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
||||||
if content.instantPage != nil {
|
if content.instantPage != nil {
|
||||||
|
|||||||
@ -445,7 +445,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return mediaHidden
|
return mediaHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -260,7 +260,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
return mediaHidden
|
return mediaHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -234,7 +234,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .minimal)
|
let dateText = stringForMessageTimestampStatus(message: item.message, dateTimeFormat: item.presentationData.dateTimeFormat, nameDisplayOrder: item.presentationData.nameDisplayOrder, strings: item.presentationData.strings, format: .regular)
|
||||||
|
|
||||||
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude))
|
let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude))
|
||||||
|
|
||||||
|
|||||||
@ -277,7 +277,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
let textNodeFrame = self.textNode.frame
|
let textNodeFrame = self.textNode.frame
|
||||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||||
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||||
|
|||||||
@ -308,10 +308,10 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||||
if self.bounds.contains(point) {
|
if self.bounds.contains(point) {
|
||||||
let contentNodeFrame = self.contentNode.frame
|
let contentNodeFrame = self.contentNode.frame
|
||||||
let result = self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY))
|
let result = self.contentNode.tapActionAtPoint(point.offsetBy(dx: -contentNodeFrame.minX, dy: -contentNodeFrame.minY), gesture: gesture)
|
||||||
switch result {
|
switch result {
|
||||||
case .none:
|
case .none:
|
||||||
break
|
break
|
||||||
|
|||||||
@ -44,7 +44,7 @@ final class ComposeControllerNode: ASDisplayNode {
|
|||||||
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateChannelActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
|
ContactListAdditionalOption(title: self.presentationData.strings.Compose_NewChannel, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/CreateChannelActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
|
||||||
openCreateNewChannelImpl?()
|
openCreateNewChannelImpl?()
|
||||||
})
|
})
|
||||||
]))
|
]), displayPermissionPlaceholder: false)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
|||||||
@ -683,11 +683,13 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
private var authorizationDisposable: Disposable?
|
private var authorizationDisposable: Disposable?
|
||||||
|
|
||||||
private var authorizationNode: PermissionContentNode
|
private var authorizationNode: PermissionContentNode
|
||||||
|
private let displayPermissionPlaceholder: Bool
|
||||||
|
|
||||||
init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil) {
|
init(account: Account, presentation: ContactListPresentation, filters: [ContactListFilter] = [.excludeSelf], selectionState: ContactListNodeGroupSelectionState? = nil, displayPermissionPlaceholder: Bool = true) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.presentation = presentation
|
self.presentation = presentation
|
||||||
self.filters = filters
|
self.filters = filters
|
||||||
|
self.displayPermissionPlaceholder = displayPermissionPlaceholder
|
||||||
|
|
||||||
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
@ -1005,21 +1007,19 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
fixSearchableListNodeScrolling(strongSelf.listNode)
|
fixSearchableListNodeScrolling(strongSelf.listNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
authorizeImpl = { [weak self] in
|
authorizeImpl = {
|
||||||
if let strongSelf = self {
|
let _ = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|
||||||
let _ = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|
|> take(1)
|
||||||
|> take(1)
|
|> deliverOnMainQueue).start(next: { status in
|
||||||
|> deliverOnMainQueue).start(next: { status in
|
switch status {
|
||||||
switch status {
|
case .notDetermined:
|
||||||
case .notDetermined:
|
DeviceAccess.authorizeAccess(to: .contacts)
|
||||||
DeviceAccess.authorizeAccess(to: .contacts)
|
case .denied, .restricted:
|
||||||
case .denied, .restricted:
|
account.telegramApplicationContext.applicationBindings.openSettings()
|
||||||
account.telegramApplicationContext.applicationBindings.openSettings()
|
default:
|
||||||
default:
|
break
|
||||||
break
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openPrivacyPolicyImpl = { [weak self] in
|
openPrivacyPolicyImpl = { [weak self] in
|
||||||
@ -1133,8 +1133,8 @@ final class ContactListNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.listNode.isHidden = transition.isEmpty
|
self.listNode.isHidden = self.displayPermissionPlaceholder && transition.isEmpty
|
||||||
self.authorizationNode.isHidden = !transition.isEmpty
|
self.authorizationNode.isHidden = !transition.isEmpty || !self.displayPermissionPlaceholder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -230,20 +230,19 @@ final class GridMessageItemNode: GridItemNode {
|
|||||||
self.statusNode.transitionToState(.none, completion: { [weak self] in
|
self.statusNode.transitionToState(.none, completion: { [weak self] in
|
||||||
self?.statusNode.isHidden = true
|
self?.statusNode.isHidden = true
|
||||||
})
|
})
|
||||||
videoAccessoryNode.isHidden = true
|
self.videoAccessoryNode.isHidden = true
|
||||||
self.resourceStatus = nil
|
self.resourceStatus = nil
|
||||||
} else if let file = media as? TelegramMediaFile, file.isVideo {
|
} else if let file = media as? TelegramMediaFile, file.isVideo {
|
||||||
mediaDimensions = file.dimensions
|
mediaDimensions = file.dimensions
|
||||||
self.imageNode.setSignal(mediaGridMessageVideo(postbox: account.postbox, videoReference: .message(message: MessageReference(item.message), media: file)))
|
self.imageNode.setSignal(mediaGridMessageVideo(postbox: account.postbox, videoReference: .message(message: MessageReference(item.message), media: file)))
|
||||||
|
|
||||||
if let duration = file.duration {
|
if let duration = file.duration {
|
||||||
videoAccessoryNode.setup(stringForDuration(duration))
|
self.videoAccessoryNode.setup(stringForDuration(duration))
|
||||||
videoAccessoryNode.isHidden = false
|
self.videoAccessoryNode.isHidden = false
|
||||||
} else {
|
} else {
|
||||||
videoAccessoryNode.isHidden = true
|
self.videoAccessoryNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
self.resourceStatus = nil
|
self.resourceStatus = nil
|
||||||
self.fetchStatusDisposable.set((messageMediaFileStatus(account: account, messageId: messageId, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in
|
self.fetchStatusDisposable.set((messageMediaFileStatus(account: account, messageId: messageId, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
|||||||
@ -22,8 +22,9 @@ final class HorizontalPeerItem: ListViewItem {
|
|||||||
let longTapAction: (Peer) -> Void
|
let longTapAction: (Peer) -> Void
|
||||||
let isPeerSelected: (PeerId) -> Bool
|
let isPeerSelected: (PeerId) -> Bool
|
||||||
let customWidth: CGFloat?
|
let customWidth: CGFloat?
|
||||||
|
let presence: PeerPresence?
|
||||||
let unreadBadge: UnreadSearchBadge?
|
let unreadBadge: UnreadSearchBadge?
|
||||||
init(theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, account: Account, peer: Peer, unreadBadge: UnreadSearchBadge?, action: @escaping (Peer) -> Void, longTapAction: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, customWidth: CGFloat?) {
|
init(theme: PresentationTheme, strings: PresentationStrings, mode: HorizontalPeerItemMode, account: Account, peer: Peer, presence: PeerPresence?, unreadBadge: UnreadSearchBadge?, action: @escaping (Peer) -> Void, longTapAction: @escaping (Peer) -> Void, isPeerSelected: @escaping (PeerId) -> Bool, customWidth: CGFloat?) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
@ -33,15 +34,14 @@ final class HorizontalPeerItem: ListViewItem {
|
|||||||
self.longTapAction = longTapAction
|
self.longTapAction = longTapAction
|
||||||
self.isPeerSelected = isPeerSelected
|
self.isPeerSelected = isPeerSelected
|
||||||
self.customWidth = customWidth
|
self.customWidth = customWidth
|
||||||
|
self.presence = presence
|
||||||
self.unreadBadge = unreadBadge
|
self.unreadBadge = unreadBadge
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
async {
|
async {
|
||||||
let node = HorizontalPeerItemNode()
|
let node = HorizontalPeerItemNode()
|
||||||
|
|
||||||
let (nodeLayout, apply) = node.asyncLayout()(self, params)
|
let (nodeLayout, apply) = node.asyncLayout()(self, params)
|
||||||
|
|
||||||
node.insets = nodeLayout.insets
|
node.insets = nodeLayout.insets
|
||||||
node.contentSize = nodeLayout.contentSize
|
node.contentSize = nodeLayout.contentSize
|
||||||
|
|
||||||
@ -77,6 +77,7 @@ final class HorizontalPeerItemNode: ListViewItemNode {
|
|||||||
private(set) var peerNode: SelectablePeerNode
|
private(set) var peerNode: SelectablePeerNode
|
||||||
let badgeBackgroundNode: ASImageNode
|
let badgeBackgroundNode: ASImageNode
|
||||||
let badgeTextNode: TextNode
|
let badgeTextNode: TextNode
|
||||||
|
let statusNode: ASImageNode
|
||||||
private(set) var item: HorizontalPeerItem?
|
private(set) var item: HorizontalPeerItem?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@ -86,16 +87,22 @@ final class HorizontalPeerItemNode: ListViewItemNode {
|
|||||||
self.badgeBackgroundNode.displaysAsynchronously = false
|
self.badgeBackgroundNode.displaysAsynchronously = false
|
||||||
self.badgeBackgroundNode.displayWithoutProcessing = true
|
self.badgeBackgroundNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
|
|
||||||
self.badgeTextNode = TextNode()
|
self.badgeTextNode = TextNode()
|
||||||
self.badgeTextNode.isUserInteractionEnabled = false
|
self.badgeTextNode.isUserInteractionEnabled = false
|
||||||
self.badgeTextNode.displaysAsynchronously = true
|
self.badgeTextNode.displaysAsynchronously = true
|
||||||
|
|
||||||
|
self.statusNode = ASImageNode()
|
||||||
|
self.statusNode.isLayerBacked = true
|
||||||
|
self.statusNode.displaysAsynchronously = false
|
||||||
|
self.statusNode.displayWithoutProcessing = true
|
||||||
|
self.statusNode.isHidden = true
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.peerNode)
|
self.addSubnode(self.peerNode)
|
||||||
addSubnode(badgeBackgroundNode)
|
self.addSubnode(self.badgeBackgroundNode)
|
||||||
addSubnode(badgeTextNode)
|
self.addSubnode(self.badgeTextNode)
|
||||||
|
self.addSubnode(self.statusNode)
|
||||||
self.peerNode.toggleSelection = { [weak self] in
|
self.peerNode.toggleSelection = { [weak self] in
|
||||||
if let item = self?.item {
|
if let item = self?.item {
|
||||||
item.action(item.peer)
|
item.action(item.peer)
|
||||||
@ -106,7 +113,6 @@ final class HorizontalPeerItemNode: ListViewItemNode {
|
|||||||
item.longTapAction(item.peer)
|
item.longTapAction(item.peer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
@ -157,13 +163,28 @@ final class HorizontalPeerItemNode: ListViewItemNode {
|
|||||||
badgeAttributedString = NSAttributedString()
|
badgeAttributedString = NSAttributedString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let currentStatusImage: UIImage?
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||||
|
if let presence = item.presence as? TelegramUserPresence {
|
||||||
|
let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: Int32(timestamp))
|
||||||
|
switch relativeStatus {
|
||||||
|
case .online:
|
||||||
|
currentStatusImage = PresentationResourcesChatList.recentStatusOnlineIcon(item.theme)
|
||||||
|
default:
|
||||||
|
currentStatusImage = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
currentStatusImage = nil
|
||||||
|
}
|
||||||
|
|
||||||
let (badgeLayout, badgeApply) = badgeTextLayout(TextNodeLayoutArguments(attributedString: badgeAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 50.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
let (badgeLayout, badgeApply) = badgeTextLayout(TextNodeLayoutArguments(attributedString: badgeAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 50.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
|
|
||||||
var badgeSize: CGFloat = 0.0
|
var badgeSize: CGFloat = 0.0
|
||||||
if let currentBadgeBackgroundImage = currentBadgeBackgroundImage {
|
if let currentBadgeBackgroundImage = currentBadgeBackgroundImage {
|
||||||
badgeSize += max(currentBadgeBackgroundImage.size.width, badgeLayout.size.width + 10.0) + 5.0
|
badgeSize += max(currentBadgeBackgroundImage.size.width, badgeLayout.size.width + 10.0) + 5.0
|
||||||
}
|
}
|
||||||
|
|
||||||
return (itemLayout, { animated in
|
return (itemLayout, { animated in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
@ -189,6 +210,17 @@ final class HorizontalPeerItemNode: ListViewItemNode {
|
|||||||
strongSelf.badgeBackgroundNode.isHidden = true
|
strongSelf.badgeBackgroundNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let currentStatusImage = currentStatusImage {
|
||||||
|
strongSelf.statusNode.image = currentStatusImage
|
||||||
|
strongSelf.statusNode.isHidden = false
|
||||||
|
|
||||||
|
let statusSize = currentStatusImage.size
|
||||||
|
strongSelf.statusNode.frame = CGRect(x: itemLayout.size.width - statusSize.width - 18.0, y: itemLayout.size.height - statusSize.height - 18.0, width: statusSize.width, height: statusSize.height)
|
||||||
|
} else {
|
||||||
|
strongSelf.statusNode.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let _ = badgeApply()
|
let _ = badgeApply()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -143,15 +143,18 @@ func legacyMenuPaletteFromTheme(_ theme: PresentationTheme) -> TGMenuSheetPallet
|
|||||||
return TGMenuSheetPallete(dark: theme.overallDarkAppearance, backgroundColor: sheetTheme.opaqueItemBackgroundColor, selectionColor: sheetTheme.opaqueItemHighlightedBackgroundColor, separatorColor: sheetTheme.opaqueItemSeparatorColor, accentColor: sheetTheme.controlAccentColor, destructiveColor: sheetTheme.destructiveActionTextColor, textColor: sheetTheme.primaryTextColor, secondaryTextColor: sheetTheme.secondaryTextColor, spinnerColor: sheetTheme.secondaryTextColor, badgeTextColor: sheetTheme.controlAccentColor, badgeImage: nil, cornersImage: generateStretchableFilledCircleImage(diameter: 11.0, color: nil, strokeColor: nil, strokeWidth: nil, backgroundColor: sheetTheme.opaqueItemBackgroundColor))
|
return TGMenuSheetPallete(dark: theme.overallDarkAppearance, backgroundColor: sheetTheme.opaqueItemBackgroundColor, selectionColor: sheetTheme.opaqueItemHighlightedBackgroundColor, separatorColor: sheetTheme.opaqueItemSeparatorColor, accentColor: sheetTheme.controlAccentColor, destructiveColor: sheetTheme.destructiveActionTextColor, textColor: sheetTheme.primaryTextColor, secondaryTextColor: sheetTheme.secondaryTextColor, spinnerColor: sheetTheme.secondaryTextColor, badgeTextColor: sheetTheme.controlAccentColor, badgeImage: nil, cornersImage: generateStretchableFilledCircleImage(diameter: 11.0, color: nil, strokeColor: nil, strokeWidth: nil, backgroundColor: sheetTheme.opaqueItemBackgroundColor))
|
||||||
}
|
}
|
||||||
|
|
||||||
func legacyPasteMenu(account: Account, peer: Peer, saveEditedPhotos: Bool, allowGrouping: Bool, theme: PresentationTheme, strings: PresentationStrings, images: [UIImage], sendMessagesWithSignals: @escaping ([Any]?) -> Void) -> ViewController {
|
func presentLegacyPasteMenu(account: Account, peer: Peer, saveEditedPhotos: Bool, allowGrouping: Bool, theme: PresentationTheme, strings: PresentationStrings, images: [UIImage], sendMessagesWithSignals: @escaping ([Any]?) -> Void, present: (ViewController, Any?) -> Void, initialLayout: ContainerViewLayout? = nil) -> ViewController {
|
||||||
|
let legacyController = LegacyController(presentation: .custom, theme: theme, initialLayout: initialLayout)
|
||||||
let legacyController = LegacyController(presentation: .custom, theme: theme)
|
legacyController.statusBar.statusBarStyle = .Ignore
|
||||||
legacyController.statusBar.statusBarStyle = .Hide
|
|
||||||
legacyController.controllerLoaded = { [weak legacyController] in
|
legacyController.controllerLoaded = { [weak legacyController] in
|
||||||
legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true
|
legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true
|
||||||
}
|
}
|
||||||
let baseController = TGViewController(context: legacyController.context)!
|
|
||||||
legacyController.bind(controller: baseController)
|
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||||
|
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
||||||
|
navigationController.setNavigationBarHidden(true, animated: false)
|
||||||
|
legacyController.bind(controller: navigationController)
|
||||||
|
|
||||||
var hasTimer = false
|
var hasTimer = false
|
||||||
if (peer is TelegramUser) && peer.id != account.peerId {
|
if (peer is TelegramUser) && peer.id != account.peerId {
|
||||||
hasTimer = true
|
hasTimer = true
|
||||||
@ -159,20 +162,15 @@ func legacyPasteMenu(account: Account, peer: Peer, saveEditedPhotos: Bool, allow
|
|||||||
let recipientName = peer.displayTitle
|
let recipientName = peer.displayTitle
|
||||||
|
|
||||||
legacyController.enableSizeClassSignal = true
|
legacyController.enableSizeClassSignal = true
|
||||||
legacyController.presentationCompleted = { [weak legacyController, weak baseController] in
|
|
||||||
if let strongLegacyController = legacyController, let baseController = baseController {
|
let controller = TGClipboardMenu.present(inParentController: emptyController, context: legacyController.context, images: images, hasCaption: true, hasTimer: hasTimer, recipientName: recipientName, completed: { selectionContext, editingContext, currentItem in
|
||||||
let controller = TGClipboardMenu.present(inParentController: baseController, context: strongLegacyController.context, images: images, hasCaption: true, hasTimer: hasTimer, recipientName: recipientName, completed: { selectionContext, editingContext, currentItem in
|
let signals = TGClipboardMenu.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, descriptionGenerator: legacyAssetPickerItemGenerator())
|
||||||
let signals = TGClipboardMenu.resultSignals(for: selectionContext, editingContext: editingContext, currentItem: currentItem, descriptionGenerator: legacyAssetPickerItemGenerator())
|
sendMessagesWithSignals(signals)
|
||||||
sendMessagesWithSignals(signals)
|
}, dismissed: { [weak legacyController] in
|
||||||
}, dismissed: {
|
legacyController?.dismiss()
|
||||||
if let strongLegacyController = legacyController {
|
}, sourceView: emptyController.view, sourceRect: nil)!
|
||||||
strongLegacyController.dismiss()
|
controller.customRemoveFromParentViewController = { [weak legacyController] in
|
||||||
}
|
legacyController?.dismiss()
|
||||||
}, sourceView: baseController.view, sourceRect: nil)
|
|
||||||
controller?.customRemoveFromParentViewController = { [weak legacyController] in
|
|
||||||
legacyController?.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let presentationDisposable = account.telegramApplicationContext.presentationData.start(next: { [weak legacyController] presentationData in
|
let presentationDisposable = account.telegramApplicationContext.presentationData.start(next: { [weak legacyController] presentationData in
|
||||||
@ -182,5 +180,8 @@ func legacyPasteMenu(account: Account, peer: Peer, saveEditedPhotos: Bool, allow
|
|||||||
})
|
})
|
||||||
legacyController.disposables.add(presentationDisposable)
|
legacyController.disposables.add(presentationDisposable)
|
||||||
|
|
||||||
|
present(legacyController, nil)
|
||||||
|
controller.present(in: emptyController, sourceView: nil, animated: true)
|
||||||
|
|
||||||
return legacyController
|
return legacyController
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ func guessMimeTypeByFileExtension(_ ext: String) -> String {
|
|||||||
return TGMimeTypeMap.mimeType(forExtension: ext) ?? "application/binary"
|
return TGMimeTypeMap.mimeType(forExtension: ext) ?? "application/binary"
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, account: Account, peer: Peer, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false) {
|
func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, account: Account, peer: Peer, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, presentWebSearch: (() -> Void)?) {
|
||||||
controller.captionsEnabled = captionsEnabled
|
controller.captionsEnabled = captionsEnabled
|
||||||
controller.inhibitDocumentCaptions = false
|
controller.inhibitDocumentCaptions = false
|
||||||
controller.suggestionContext = legacySuggestionContext(account: account, peerId: peer.id)
|
controller.suggestionContext = legacySuggestionContext(account: account, peerId: peer.id)
|
||||||
@ -21,11 +21,11 @@ func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, account:
|
|||||||
controller.hasTimer = true
|
controller.hasTimer = true
|
||||||
}
|
}
|
||||||
controller.dismissalBlock = {
|
controller.dismissalBlock = {
|
||||||
|
|
||||||
}
|
}
|
||||||
controller.localMediaCacheEnabled = false
|
controller.localMediaCacheEnabled = false
|
||||||
controller.shouldStoreAssets = storeCreatedAssets
|
controller.shouldStoreAssets = storeCreatedAssets
|
||||||
controller.shouldShowFileTipIfNeeded = showFileTooltip
|
controller.shouldShowFileTipIfNeeded = showFileTooltip
|
||||||
|
controller.requestSearchController = presentWebSearch
|
||||||
}
|
}
|
||||||
|
|
||||||
func legacyAssetPicker(applicationContext: TelegramApplicationContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: Peer?, saveEditedPhotos: Bool, allowGrouping: Bool) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> {
|
func legacyAssetPicker(applicationContext: TelegramApplicationContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: Peer?, saveEditedPhotos: Bool, allowGrouping: Bool) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> {
|
||||||
|
|||||||
329
TelegramUI/LegacyWebSearchGallery.swift
Normal file
329
TelegramUI/LegacyWebSearchGallery.swift
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
import Foundation
|
||||||
|
import LegacyComponents
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramCore
|
||||||
|
import Postbox
|
||||||
|
import SSignalKit
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
|
||||||
|
class LegacyWebSearchItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem {
|
||||||
|
var isVideo: Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var uniqueIdentifier: String! {
|
||||||
|
return self.result.id
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: ChatContextResult
|
||||||
|
let dimensions: CGSize
|
||||||
|
let thumbnailImage: Signal<UIImage, NoError>
|
||||||
|
let originalImage: Signal<UIImage, NoError>
|
||||||
|
|
||||||
|
init(result: ChatContextResult, dimensions: CGSize, thumbnailImage: Signal<UIImage, NoError>, originalImage: Signal<UIImage, NoError>) {
|
||||||
|
self.result = result
|
||||||
|
self.dimensions = dimensions
|
||||||
|
self.thumbnailImage = thumbnailImage
|
||||||
|
self.originalImage = originalImage
|
||||||
|
}
|
||||||
|
|
||||||
|
var originalSize: CGSize {
|
||||||
|
return self.dimensions
|
||||||
|
}
|
||||||
|
|
||||||
|
func thumbnailImageSignal() -> SSignal! {
|
||||||
|
return SSignal(generator: { subscriber -> SDisposable? in
|
||||||
|
let disposable = self.thumbnailImage.start(next: { image in
|
||||||
|
subscriber?.putNext(image)
|
||||||
|
subscriber?.putCompletion()
|
||||||
|
})
|
||||||
|
|
||||||
|
return SBlockDisposable(block: {
|
||||||
|
disposable.dispose()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func screenImageSignal(_ position: TimeInterval) -> SSignal! {
|
||||||
|
return self.originalImageSignal(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
func originalImageSignal(_ position: TimeInterval) -> SSignal! {
|
||||||
|
return SSignal(generator: { subscriber -> SDisposable? in
|
||||||
|
let disposable = self.originalImage.start(next: { image in
|
||||||
|
subscriber?.putNext(image)
|
||||||
|
subscriber?.putCompletion()
|
||||||
|
})
|
||||||
|
|
||||||
|
return SBlockDisposable(block: {
|
||||||
|
disposable.dispose()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LegacyWebSearchGalleryItem: TGModernGalleryImageItem, TGModernGalleryEditableItem, TGModernGallerySelectableItem {
|
||||||
|
var selectionContext: TGMediaSelectionContext!
|
||||||
|
|
||||||
|
var editingContext: TGMediaEditingContext!
|
||||||
|
let item: LegacyWebSearchItem
|
||||||
|
|
||||||
|
init(item: LegacyWebSearchItem) {
|
||||||
|
self.item = item
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
func editableMediaItem() -> TGMediaEditableItem! {
|
||||||
|
return self.item
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectableMediaItem() -> TGMediaSelectableItem! {
|
||||||
|
return self.item
|
||||||
|
}
|
||||||
|
|
||||||
|
func toolbarTabs() -> TGPhotoEditorTab {
|
||||||
|
return [.cropTab, .paintTab, .toolsTab]
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniqueId() -> String! {
|
||||||
|
return self.item.uniqueIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewClass() -> AnyClass! {
|
||||||
|
return LegacyWebSearchGalleryItemView.self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGModernGalleryEditableItemView
|
||||||
|
{
|
||||||
|
func setHiddenAsBeingEdited(_ hidden: Bool) {
|
||||||
|
self.imageView.isHidden = hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
override func setItem(_ item: TGModernGalleryItem!, synchronously: Bool) {
|
||||||
|
if let item = item as? LegacyWebSearchGalleryItem {
|
||||||
|
self._setItem(item)
|
||||||
|
self.imageSize = TGFitSize(item.editableMediaItem().originalSize!, CGSize(width: 1600, height: 1600))
|
||||||
|
|
||||||
|
let signal = item.editingContext.imageSignal(for: item.editableMediaItem())?.map(toSignal: { result -> SSignal? in
|
||||||
|
if let image = result as? UIImage {
|
||||||
|
return SSignal.single(image)
|
||||||
|
} else if result == nil, let mediaItem = item.editableMediaItem() as? LegacyWebSearchItem {
|
||||||
|
return mediaItem.originalImageSignal(0.0)
|
||||||
|
} else {
|
||||||
|
return SSignal.complete()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
self.imageView.setSignal(signal?.deliver(on: SQueue.main())?.afterNext({ [weak self] next in
|
||||||
|
if let strongSelf = self, let image = next as? UIImage {
|
||||||
|
strongSelf.imageSize = image.size
|
||||||
|
strongSelf.reset()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
} else {
|
||||||
|
self.imageView.setSignal(nil)
|
||||||
|
super.setItem(item, synchronously: synchronously)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func contentView() -> UIView! {
|
||||||
|
return self.imageView
|
||||||
|
}
|
||||||
|
|
||||||
|
override func transitionContentView() -> UIView! {
|
||||||
|
return self.contentView()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func transitionViewContentRect() -> CGRect {
|
||||||
|
let contentView = self.transitionContentView()!
|
||||||
|
return contentView.convert(contentView.bounds, to: self.transitionView())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func galleryItems(account: Account, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext, editingContext: TGMediaEditingContext) -> ([TGModernGalleryItem], TGModernGalleryItem?) {
|
||||||
|
var focusItem: TGModernGalleryItem?
|
||||||
|
var galleryItems: [TGModernGalleryItem] = []
|
||||||
|
for result in results {
|
||||||
|
var thumbnailDimensions: CGSize?
|
||||||
|
var thumbnailResource: TelegramMediaResource?
|
||||||
|
var imageResource: TelegramMediaResource?
|
||||||
|
var imageDimensions = CGSize()
|
||||||
|
|
||||||
|
let thumbnailSignal: Signal<UIImage, NoError>
|
||||||
|
let originalSignal: Signal<UIImage, NoError>
|
||||||
|
switch result {
|
||||||
|
case let .externalReference(_, _, _, _, _, _, content, thumbnail, _):
|
||||||
|
if let content = content {
|
||||||
|
imageResource = content.resource
|
||||||
|
}
|
||||||
|
if let thumbnail = thumbnail {
|
||||||
|
thumbnailResource = thumbnail.resource
|
||||||
|
thumbnailDimensions = thumbnail.dimensions
|
||||||
|
}
|
||||||
|
if let dimensions = content?.dimensions {
|
||||||
|
imageDimensions = dimensions
|
||||||
|
}
|
||||||
|
// if let imageResource = imageResource {
|
||||||
|
// updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
|
||||||
|
// }
|
||||||
|
case let .internalReference(_, _, _, _, _, image, _, _):
|
||||||
|
if let image = image {
|
||||||
|
if let largestRepresentation = largestImageRepresentation(image.representations) {
|
||||||
|
imageDimensions = largestRepresentation.dimensions
|
||||||
|
imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 1000.0, height: 800.0))?.resource
|
||||||
|
}
|
||||||
|
if let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 100.0)) {
|
||||||
|
thumbnailDimensions = thumbnailRepresentation.dimensions
|
||||||
|
thumbnailResource = thumbnailRepresentation.resource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let imageResource = imageResource {
|
||||||
|
var representations: [TelegramMediaImageRepresentation] = []
|
||||||
|
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
|
||||||
|
representations.append(TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource))
|
||||||
|
}
|
||||||
|
representations.append(TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource))
|
||||||
|
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, reference: nil, partialReference: nil)
|
||||||
|
thumbnailSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: true)
|
||||||
|
|> mapToSignal { (thumbnailData, fullSizeData, fullSizeComplete) -> Signal<UIImage, NoError> in
|
||||||
|
if let data = fullSizeData, let image = UIImage(data: data) {
|
||||||
|
return .single(image)
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
originalSignal = thumbnailSignal
|
||||||
|
|
||||||
|
let item = LegacyWebSearchItem(result: result, dimensions: imageDimensions, thumbnailImage: thumbnailSignal, originalImage: originalSignal)
|
||||||
|
let galleryItem = LegacyWebSearchGalleryItem(item: item)
|
||||||
|
galleryItem.selectionContext = selectionContext
|
||||||
|
galleryItem.editingContext = editingContext
|
||||||
|
if result.id == current.id {
|
||||||
|
focusItem = galleryItem
|
||||||
|
}
|
||||||
|
galleryItems.append(galleryItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (galleryItems, focusItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: PresentationTheme, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext, editingContext: TGMediaEditingContext, updateHiddenMedia: @escaping (String?) -> Void, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (ChatContextResult) -> Void, present: (ViewController, Any?) -> Void) {
|
||||||
|
let legacyController = LegacyController(presentation: .custom, theme: theme)
|
||||||
|
legacyController.statusBar.statusBarStyle = .Ignore
|
||||||
|
|
||||||
|
let controller = TGModernGalleryController(context: legacyController.context)!
|
||||||
|
legacyController.bind(controller: controller)
|
||||||
|
|
||||||
|
let (items, focusItem) = galleryItems(account: account, results: results, current: current, selectionContext: selectionContext, editingContext: editingContext)
|
||||||
|
|
||||||
|
let model = TGMediaPickerGalleryModel(context: legacyController.context, items: items, focus: focusItem, selectionContext: selectionContext, editingContext: editingContext, hasCaptions: true, allowCaptionEntities: true, hasTimer: false, onlyCrop: false, inhibitDocumentCaptions: false, hasSelectionPanel: false, hasCamera: false, recipientName: peer?.displayTitle)!
|
||||||
|
if let peer = peer {
|
||||||
|
model.suggestionContext = legacySuggestionContext(account: account, peerId: peer.id)
|
||||||
|
}
|
||||||
|
controller.model = model
|
||||||
|
model.controller = controller
|
||||||
|
model.externalSelectionCount = {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
model.useGalleryImageAsEditableItemImage = true
|
||||||
|
model.storeOriginalImageForItem = { item, image in
|
||||||
|
editingContext.setOriginalImage(image, for: item, synchronous: false)
|
||||||
|
}
|
||||||
|
model.willFinishEditingItem = { item, adjustments, representation, hasChanges in
|
||||||
|
if hasChanges {
|
||||||
|
editingContext.setAdjustments(adjustments, for: item)
|
||||||
|
}
|
||||||
|
editingContext.setTemporaryRep(representation, for: item)
|
||||||
|
//if let selectionContext = selectionContext, {
|
||||||
|
// selectionContex
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
model.didFinishEditingItem = { item, adjustments, result, thumbnail in
|
||||||
|
editingContext.setImage(result, thumbnailImage: thumbnail, for: item, synchronous: true)
|
||||||
|
}
|
||||||
|
model.saveItemCaption = { item, caption, entities in
|
||||||
|
editingContext.setCaption(caption, entities: entities, for: item)
|
||||||
|
}
|
||||||
|
//[model.interfaceView updateSelectionInterface:[self totalSelectionCount] counterVisible:([self totalSelectionCount] > 0) animated:false];
|
||||||
|
model.interfaceView.donePressed = { item in
|
||||||
|
if let item = item as? LegacyWebSearchGalleryItem {
|
||||||
|
controller.dismissWhenReady(animated: false)
|
||||||
|
completed(item.item.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.transitionHost = {
|
||||||
|
return transitionHostView()
|
||||||
|
}
|
||||||
|
controller.itemFocused = { item in
|
||||||
|
if let item = item as? LegacyWebSearchGalleryItem {
|
||||||
|
updateHiddenMedia(item.item.result.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.beginTransitionIn = { item, _ in
|
||||||
|
if let item = item as? LegacyWebSearchGalleryItem {
|
||||||
|
return transitionView(item.item.result)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.beginTransitionOut = { item, _ in
|
||||||
|
if let item = item as? LegacyWebSearchGalleryItem {
|
||||||
|
return transitionView(item.item.result)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
controller.completedTransitionOut = { [weak legacyController] in
|
||||||
|
updateHiddenMedia(nil)
|
||||||
|
legacyController?.dismiss()
|
||||||
|
}
|
||||||
|
present(legacyController, nil)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// if (item.selectionContext != nil && adjustments != nil && [editableItem conformsToProtocol:@protocol(TGMediaSelectableItem)])
|
||||||
|
// [item.selectionContext setItem:(id<TGMediaSelectableItem>)editableItem selected:true];
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
// model.interfaceView.donePressed = ^(id<TGWebSearchResultsGalleryItem> item)
|
||||||
|
// {
|
||||||
|
// __strong TGWebSearchController *strongSelf = weakSelf;
|
||||||
|
// if (strongSelf == nil)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// NSMutableArray *selectedItems = [strongSelf selectedItems];
|
||||||
|
//
|
||||||
|
// if (selectedItems.count == 0)
|
||||||
|
// [selectedItems addObject:[item webSearchResult]];
|
||||||
|
//
|
||||||
|
// strongSelf->_selectedItems = selectedItems;
|
||||||
|
// [strongSelf complete];
|
||||||
|
// };
|
||||||
|
// _galleryModel = model;
|
||||||
|
// modernGallery.model = model;
|
||||||
|
//
|
||||||
|
// __weak TGModernGalleryController *weakGallery = modernGallery;
|
||||||
|
// modernGallery.itemFocused = ^(id<TGWebSearchResultsGalleryItem> item)
|
||||||
|
// {
|
||||||
|
// __strong TGWebSearchController *strongSelf = weakSelf;
|
||||||
|
// __strong TGModernGalleryController *strongGallery = weakGallery;
|
||||||
|
// if (strongSelf != nil)
|
||||||
|
// {
|
||||||
|
// if (strongGallery.previewMode)
|
||||||
|
// return;
|
||||||
|
//
|
||||||
|
// id<TGWebSearchListItem> listItem = [strongSelf listItemForSearchResult:[item webSearchResult]];
|
||||||
|
// strongSelf->_hiddenItem = listItem;
|
||||||
|
// [strongSelf updateHiddenItemAnimated:false];
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
}
|
||||||
@ -173,7 +173,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
private var appliedItem: ListMessageItem?
|
private var appliedItem: ListMessageItem?
|
||||||
private var layoutParams: ListViewItemLayoutParams?
|
private var layoutParams: ListViewItemLayoutParams?
|
||||||
private var contentSizeValue: CGSize?
|
private var contentSizeValue: CGSize?
|
||||||
private var currentLeftOffet: CGFloat = 0.0
|
private var currentLeftOffset: CGFloat = 0.0
|
||||||
|
|
||||||
override var canBeLongTapped: Bool {
|
override var canBeLongTapped: Bool {
|
||||||
return true
|
return true
|
||||||
@ -305,7 +305,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
updatedTheme = item.theme
|
updatedTheme = item.theme
|
||||||
}
|
}
|
||||||
|
|
||||||
var leftInset: CGFloat = 60.0 + params.leftInset
|
var leftInset: CGFloat = 65.0 + params.leftInset
|
||||||
let rightInset: CGFloat = 8.0 + params.rightInset
|
let rightInset: CGFloat = 8.0 + params.rightInset
|
||||||
|
|
||||||
var leftOffset: CGFloat = 0.0
|
var leftOffset: CGFloat = 0.0
|
||||||
@ -483,7 +483,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
insets.top += header.height
|
insets.top += header.height
|
||||||
}
|
}
|
||||||
|
|
||||||
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: isAudio ? 56.0 : 52.0), insets: insets)
|
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 56.0), insets: insets)
|
||||||
|
|
||||||
return (nodeLayout, { animation in
|
return (nodeLayout, { animation in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -500,7 +500,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
strongSelf.appliedItem = item
|
strongSelf.appliedItem = item
|
||||||
strongSelf.layoutParams = params
|
strongSelf.layoutParams = params
|
||||||
strongSelf.contentSizeValue = nodeLayout.contentSize
|
strongSelf.contentSizeValue = nodeLayout.contentSize
|
||||||
strongSelf.currentLeftOffet = leftOffset
|
strongSelf.currentLeftOffset = leftOffset
|
||||||
|
|
||||||
if let _ = updatedTheme {
|
if let _ = updatedTheme {
|
||||||
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
||||||
@ -508,7 +508,6 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
|
|
||||||
strongSelf.progressNode.updateTheme(RadialProgressTheme(backgroundColor: item.theme.list.itemAccentColor, foregroundColor: item.theme.list.plainBackgroundColor, icon: nil))
|
strongSelf.progressNode.updateTheme(RadialProgressTheme(backgroundColor: item.theme.list.itemAccentColor, foregroundColor: item.theme.list.plainBackgroundColor, icon: nil))
|
||||||
strongSelf.linearProgressNode.backgroundColor = item.theme.list.itemAccentColor
|
strongSelf.linearProgressNode.backgroundColor = item.theme.list.itemAccentColor
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (selectionWidth, selectionApply) = selectionNodeWidthAndApply {
|
if let (selectionWidth, selectionApply) = selectionNodeWidthAndApply {
|
||||||
@ -552,7 +551,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: isAudio ? 32.0 : 29.0), size: descriptionNodeLayout.size))
|
transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: 32.0), size: descriptionNodeLayout.size))
|
||||||
let _ = descriptionNodeApply()
|
let _ = descriptionNodeApply()
|
||||||
|
|
||||||
let iconFrame: CGRect
|
let iconFrame: CGRect
|
||||||
@ -565,7 +564,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
}
|
}
|
||||||
transition.updateFrame(node: strongSelf.extensionIconNode, frame: iconFrame)
|
transition.updateFrame(node: strongSelf.extensionIconNode, frame: iconFrame)
|
||||||
strongSelf.extensionIconNode.image = extensionIconImage
|
strongSelf.extensionIconNode.image = extensionIconImage
|
||||||
transition.updateFrame(node: strongSelf.extensionIconText, frame: CGRect(origin: CGPoint(x: leftOffset + 9.0 + floor((42.0 - extensionTextLayout.size.width) / 2.0), y: 5.0 + floor((42.0 - extensionTextLayout.size.height) / 2.0)), size: extensionTextLayout.size))
|
transition.updateFrame(node: strongSelf.extensionIconText, frame: CGRect(origin: CGPoint(x: leftOffset + 12.0 + floor((42.0 - extensionTextLayout.size.width) / 2.0), y: 5.0 + floor((42.0 - extensionTextLayout.size.height) / 2.0)), size: extensionTextLayout.size))
|
||||||
|
|
||||||
let _ = extensionTextApply()
|
let _ = extensionTextApply()
|
||||||
|
|
||||||
@ -787,7 +786,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
|
|
||||||
switch maybeFetchStatus {
|
switch maybeFetchStatus {
|
||||||
case let .Fetching(_, progress):
|
case let .Fetching(_, progress):
|
||||||
let progressFrame = CGRect(x: self.currentLeftOffet + leftInset + 65.0, y: size.height - 2.0, width: floor((size.width - 65.0 - leftInset - rightInset) * CGFloat(progress)), height: 2.0)
|
let progressFrame = CGRect(x: self.currentLeftOffset + leftInset + 65.0, y: size.height - 2.0, width: floor((size.width - 65.0 - leftInset - rightInset) * CGFloat(progress)), height: 2.0)
|
||||||
if self.linearProgressNode.supernode == nil {
|
if self.linearProgressNode.supernode == nil {
|
||||||
self.addSubnode(self.linearProgressNode)
|
self.addSubnode(self.linearProgressNode)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import TelegramCore
|
|||||||
enum NativeVideoContentId: Hashable {
|
enum NativeVideoContentId: Hashable {
|
||||||
case message(MessageId, UInt32, MediaId)
|
case message(MessageId, UInt32, MediaId)
|
||||||
case instantPage(MediaId, MediaId)
|
case instantPage(MediaId, MediaId)
|
||||||
|
case contextResult(Int64, String)
|
||||||
|
|
||||||
static func ==(lhs: NativeVideoContentId, rhs: NativeVideoContentId) -> Bool {
|
static func ==(lhs: NativeVideoContentId, rhs: NativeVideoContentId) -> Bool {
|
||||||
switch lhs {
|
switch lhs {
|
||||||
@ -23,6 +24,12 @@ enum NativeVideoContentId: Hashable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case let .contextResult(queryId, resultId):
|
||||||
|
if case .contextResult(queryId, resultId) = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +39,8 @@ enum NativeVideoContentId: Hashable {
|
|||||||
return messageId.hashValue &* 31 &+ mediaId.hashValue
|
return messageId.hashValue &* 31 &+ mediaId.hashValue
|
||||||
case let .instantPage(pageId, mediaId):
|
case let .instantPage(pageId, mediaId):
|
||||||
return pageId.hashValue &* 31 &+ mediaId.hashValue
|
return pageId.hashValue &* 31 &+ mediaId.hashValue
|
||||||
|
case let .contextResult(queryId, resultId):
|
||||||
|
return queryId.hashValue &* 31 &+ resultId.hashValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,10 +43,12 @@ private func chatMessageGalleryControllerData(account: Account, message: Message
|
|||||||
} else if let image = media as? TelegramMediaImage {
|
} else if let image = media as? TelegramMediaImage {
|
||||||
galleryMedia = image
|
galleryMedia = image
|
||||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, !excludeWebPageMedia {
|
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, !excludeWebPageMedia {
|
||||||
if let file = content.file {
|
if ["photo", "document", "video", "gif", "audio", "voice"].contains(content.type) {
|
||||||
galleryMedia = file
|
if let file = content.file {
|
||||||
} else if let image = content.image {
|
galleryMedia = file
|
||||||
galleryMedia = image
|
} else if let image = content.image {
|
||||||
|
galleryMedia = image
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let instantPage = content.instantPage, let galleryMedia = galleryMedia {
|
if let instantPage = content.instantPage, let galleryMedia = galleryMedia {
|
||||||
switch instantPageType(of: content) {
|
switch instantPageType(of: content) {
|
||||||
|
|||||||
@ -136,7 +136,7 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
|
|||||||
|
|
||||||
let textInputState: ChatTextInputState
|
let textInputState: ChatTextInputState
|
||||||
if let text = text, !text.isEmpty {
|
if let text = text, !text.isEmpty {
|
||||||
let urlString = NSMutableAttributedString(string: "\(text)\n")
|
let urlString = NSMutableAttributedString(string: "\(url)\n")
|
||||||
let textString = NSAttributedString(string: "\(text)")
|
let textString = NSAttributedString(string: "\(text)")
|
||||||
let selectionRange: Range<Int> = urlString.length ..< (urlString.length + textString.length)
|
let selectionRange: Range<Int> = urlString.length ..< (urlString.length + textString.length)
|
||||||
urlString.append(textString)
|
urlString.append(textString)
|
||||||
|
|||||||
@ -115,6 +115,9 @@ public class PeerMediaCollectionController: TelegramController {
|
|||||||
}
|
}
|
||||||
}, openPeerMention: { _ in
|
}, openPeerMention: { _ in
|
||||||
}, openMessageContextMenu: { [weak self] message, _, _, _ in
|
}, openMessageContextMenu: { [weak self] message, _, _, _ in
|
||||||
|
var messageIds = Set<MessageId>()
|
||||||
|
messageIds.insert(message.id)
|
||||||
|
|
||||||
if let strongSelf = self, strongSelf.isNodeLoaded {
|
if let strongSelf = self, strongSelf.isNodeLoaded {
|
||||||
if let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message {
|
if let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message {
|
||||||
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
||||||
@ -124,6 +127,18 @@ public class PeerMediaCollectionController: TelegramController {
|
|||||||
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
|
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
|
||||||
navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(strongSelf.peerId), messageId: message.id)
|
navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(strongSelf.peerId), messageId: message.id)
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ContextMenuForward, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.forwardMessages(messageIds)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ContextMenuDelete, color: .destructive, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.deleteMessages(messageIds)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
]), ActionSheetItemGroup(items: [
|
]), ActionSheetItemGroup(items: [
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||||
@ -244,63 +259,8 @@ public class PeerMediaCollectionController: TelegramController {
|
|||||||
}, setupEditMessage: { _ in
|
}, setupEditMessage: { _ in
|
||||||
}, beginMessageSelection: { _ in
|
}, beginMessageSelection: { _ in
|
||||||
}, deleteSelectedMessages: { [weak self] in
|
}, deleteSelectedMessages: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds {
|
||||||
if let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
strongSelf.deleteMessages(messageIds)
|
||||||
strongSelf.messageContextDisposable.set((combineLatest(chatAvailableMessageActions(postbox: strongSelf.account.postbox, accountPeerId: strongSelf.account.peerId, messageIds: messageIds), strongSelf.peer.get() |> take(1)) |> deliverOnMainQueue).start(next: { actions, peer in
|
|
||||||
if let strongSelf = self, let peer = peer, !actions.options.isEmpty {
|
|
||||||
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
|
||||||
var items: [ActionSheetItem] = []
|
|
||||||
var personalPeerName: String?
|
|
||||||
var isChannel = false
|
|
||||||
if let user = peer as? TelegramUser {
|
|
||||||
personalPeerName = user.compactDisplayTitle
|
|
||||||
} else if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
|
||||||
isChannel = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if actions.options.contains(.deleteGlobally) {
|
|
||||||
let globalTitle: String
|
|
||||||
if isChannel {
|
|
||||||
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe
|
|
||||||
} else if let personalPeerName = personalPeerName {
|
|
||||||
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).0
|
|
||||||
} else {
|
|
||||||
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone
|
|
||||||
}
|
|
||||||
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() })
|
|
||||||
let _ = deleteMessagesInteractively(postbox: strongSelf.account.postbox, messageIds: Array(messageIds), type: .forEveryone).start()
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
if actions.options.contains(.deleteLocally) {
|
|
||||||
var localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe
|
|
||||||
if strongSelf.account.peerId == strongSelf.peerId {
|
|
||||||
if messageIds.count == 1 {
|
|
||||||
localOptionText = strongSelf.presentationData.strings.Conversation_Moderate_Delete
|
|
||||||
} else {
|
|
||||||
localOptionText = strongSelf.presentationData.strings.Conversation_DeleteManyMessages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() })
|
|
||||||
let _ = deleteMessagesInteractively(postbox: strongSelf.account.postbox, messageIds: Array(messageIds), type: .forLocalPeer).start()
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
|
||||||
})
|
|
||||||
])])
|
|
||||||
strongSelf.present(actionSheet, in: .window(.root))
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, reportSelectedMessages: { [weak self] in
|
}, reportSelectedMessages: { [weak self] in
|
||||||
if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
||||||
@ -313,37 +273,7 @@ public class PeerMediaCollectionController: TelegramController {
|
|||||||
}, forwardSelectedMessages: { [weak self] in
|
}, forwardSelectedMessages: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let forwardMessageIdsSet = strongSelf.interfaceState.selectionState?.selectedIds {
|
if let forwardMessageIdsSet = strongSelf.interfaceState.selectionState?.selectedIds {
|
||||||
let forwardMessageIds = Array(forwardMessageIdsSet).sorted()
|
strongSelf.forwardMessages(forwardMessageIdsSet)
|
||||||
|
|
||||||
let controller = PeerSelectionController(account: strongSelf.account)
|
|
||||||
controller.peerSelected = { [weak controller] peerId in
|
|
||||||
if let strongSelf = self, let _ = controller {
|
|
||||||
let _ = (strongSelf.account.postbox.transaction({ transaction -> Void in
|
|
||||||
transaction.updatePeerChatInterfaceState(peerId, update: { currentState in
|
|
||||||
if let currentState = currentState as? ChatInterfaceState {
|
|
||||||
return currentState.withUpdatedForwardMessageIds(forwardMessageIds)
|
|
||||||
} else {
|
|
||||||
return ChatInterfaceState().withUpdatedForwardMessageIds(forwardMessageIds)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}) |> deliverOnMainQueue).start(completed: {
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.updateInterfaceState(animated: false, { $0.withoutSelectionState() })
|
|
||||||
|
|
||||||
let ready = ValuePromise<Bool>()
|
|
||||||
|
|
||||||
strongSelf.messageContextDisposable.set((ready.get() |> take(1) |> deliverOnMainQueue).start(next: { _ in
|
|
||||||
if let strongController = controller {
|
|
||||||
strongController.dismiss()
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
(strongSelf.navigationController as? NavigationController)?.replaceTopController(ChatController(account: strongSelf.account, chatLocation: .peer(peerId)), animated: false, ready: ready)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
strongSelf.present(controller, in: .window(.root))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, forwardMessages: { _ in
|
}, forwardMessages: { _ in
|
||||||
@ -646,4 +576,97 @@ public class PeerMediaCollectionController: TelegramController {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func forwardMessages(_ messageIds: Set<MessageId>) {
|
||||||
|
let forwardMessageIds = Array(messageIds).sorted()
|
||||||
|
|
||||||
|
let controller = PeerSelectionController(account: self.account)
|
||||||
|
controller.peerSelected = { [weak self, weak controller] peerId in
|
||||||
|
if let strongSelf = self, let _ = controller {
|
||||||
|
let _ = (strongSelf.account.postbox.transaction({ transaction -> Void in
|
||||||
|
transaction.updatePeerChatInterfaceState(peerId, update: { currentState in
|
||||||
|
if let currentState = currentState as? ChatInterfaceState {
|
||||||
|
return currentState.withUpdatedForwardMessageIds(forwardMessageIds)
|
||||||
|
} else {
|
||||||
|
return ChatInterfaceState().withUpdatedForwardMessageIds(forwardMessageIds)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}) |> deliverOnMainQueue).start(completed: {
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.updateInterfaceState(animated: false, { $0.withoutSelectionState() })
|
||||||
|
|
||||||
|
let ready = ValuePromise<Bool>()
|
||||||
|
|
||||||
|
strongSelf.messageContextDisposable.set((ready.get() |> take(1) |> deliverOnMainQueue).start(next: { _ in
|
||||||
|
if let strongController = controller {
|
||||||
|
strongController.dismiss()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
(strongSelf.navigationController as? NavigationController)?.replaceTopController(ChatController(account: strongSelf.account, chatLocation: .peer(peerId)), animated: false, ready: ready)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.present(controller, in: .window(.root))
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteMessages(_ messageIds: Set<MessageId>) {
|
||||||
|
if !messageIds.isEmpty {
|
||||||
|
self.messageContextDisposable.set((combineLatest(chatAvailableMessageActions(postbox: self.account.postbox, accountPeerId: self.account.peerId, messageIds: messageIds), self.peer.get() |> take(1)) |> deliverOnMainQueue).start(next: { [weak self] actions, peer in
|
||||||
|
if let strongSelf = self, let peer = peer, !actions.options.isEmpty {
|
||||||
|
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
|
||||||
|
var items: [ActionSheetItem] = []
|
||||||
|
var personalPeerName: String?
|
||||||
|
var isChannel = false
|
||||||
|
if let user = peer as? TelegramUser {
|
||||||
|
personalPeerName = user.compactDisplayTitle
|
||||||
|
} else if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||||
|
isChannel = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if actions.options.contains(.deleteGlobally) {
|
||||||
|
let globalTitle: String
|
||||||
|
if isChannel {
|
||||||
|
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe
|
||||||
|
} else if let personalPeerName = personalPeerName {
|
||||||
|
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).0
|
||||||
|
} else {
|
||||||
|
globalTitle = strongSelf.presentationData.strings.Conversation_DeleteMessagesForEveryone
|
||||||
|
}
|
||||||
|
items.append(ActionSheetButtonItem(title: globalTitle, color: .destructive, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() })
|
||||||
|
let _ = deleteMessagesInteractively(postbox: strongSelf.account.postbox, messageIds: Array(messageIds), type: .forEveryone).start()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if actions.options.contains(.deleteLocally) {
|
||||||
|
var localOptionText = strongSelf.presentationData.strings.Conversation_DeleteMessagesForMe
|
||||||
|
if strongSelf.account.peerId == strongSelf.peerId {
|
||||||
|
if messageIds.count == 1 {
|
||||||
|
localOptionText = strongSelf.presentationData.strings.Conversation_Moderate_Delete
|
||||||
|
} else {
|
||||||
|
localOptionText = strongSelf.presentationData.strings.Conversation_DeleteManyMessages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.updateInterfaceState(animated: true, { $0.withoutSelectionState() })
|
||||||
|
let _ = deleteMessagesInteractively(postbox: strongSelf.account.postbox, messageIds: Array(messageIds), type: .forLocalPeer).start()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||||
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||||
|
actionSheet?.dismissAnimated()
|
||||||
|
})
|
||||||
|
])])
|
||||||
|
strongSelf.present(actionSheet, in: .window(.root))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,8 @@ class PermissionInfoItemNode: ListViewItemNode {
|
|||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
private let topStripeNode: ASDisplayNode
|
private let topStripeNode: ASDisplayNode
|
||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
|
private let closeButton: HighlightableButtonNode
|
||||||
|
|
||||||
let badgeNode: ASImageNode
|
let badgeNode: ASImageNode
|
||||||
let labelNode: TextNode
|
let labelNode: TextNode
|
||||||
let titleNode: TextNode
|
let titleNode: TextNode
|
||||||
@ -140,6 +141,10 @@ class PermissionInfoItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
self.textNode = TextNode()
|
self.textNode = TextNode()
|
||||||
self.textNode.isUserInteractionEnabled = false
|
self.textNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
self.closeButton = HighlightableButtonNode()
|
||||||
|
self.closeButton.hitTestSlop = UIEdgeInsetsMake(-8.0, -8.0, -8.0, -8.0)
|
||||||
|
self.closeButton.displaysAsynchronously = false
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
@ -147,6 +152,7 @@ class PermissionInfoItemNode: ListViewItemNode {
|
|||||||
self.addSubnode(self.labelNode)
|
self.addSubnode(self.labelNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.closeButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: PermissionInfoItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors?) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: PermissionInfoItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors?) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
@ -163,10 +169,13 @@ class PermissionInfoItemNode: ListViewItemNode {
|
|||||||
var updatedTheme: PresentationTheme?
|
var updatedTheme: PresentationTheme?
|
||||||
var updatedBadgeImage: UIImage?
|
var updatedBadgeImage: UIImage?
|
||||||
|
|
||||||
|
var updatedCloseIcon: UIImage?
|
||||||
|
|
||||||
let badgeDiameter: CGFloat = 20.0
|
let badgeDiameter: CGFloat = 20.0
|
||||||
if currentItem?.theme !== item.theme {
|
if currentItem?.theme !== item.theme {
|
||||||
updatedTheme = item.theme
|
updatedTheme = item.theme
|
||||||
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemDestructiveColor)
|
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemDestructiveColor)
|
||||||
|
updatedCloseIcon = PresentationResourcesItemList.itemListCloseIconImage(item.theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
let insets: UIEdgeInsets
|
let insets: UIEdgeInsets
|
||||||
@ -256,6 +265,15 @@ class PermissionInfoItemNode: ListViewItemNode {
|
|||||||
bottomStripeInset = leftInset
|
bottomStripeInset = leftInset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let item = strongSelf.item {
|
||||||
|
switch (item.subject, item.type) {
|
||||||
|
case (.contacts, _), (.notifications, .unreachable):
|
||||||
|
strongSelf.closeButton.isHidden = false
|
||||||
|
default:
|
||||||
|
strongSelf.closeButton.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
||||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
|
||||||
@ -267,6 +285,10 @@ class PermissionInfoItemNode: ListViewItemNode {
|
|||||||
strongSelf.badgeNode.image = updateBadgeImage
|
strongSelf.badgeNode.image = updateBadgeImage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let updatedCloseIcon = updatedCloseIcon {
|
||||||
|
strongSelf.closeButton.setImage(updatedCloseIcon, for: [])
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.badgeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 16.0), size: CGSize(width: badgeDiameter, height: badgeDiameter))
|
strongSelf.badgeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 16.0), size: CGSize(width: badgeDiameter, height: badgeDiameter))
|
||||||
|
|
||||||
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.midX - labelLayout.size.width / 2.0, y: strongSelf.badgeNode.frame.minY + 1.0), size: labelLayout.size)
|
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.midX - labelLayout.size.width / 2.0, y: strongSelf.badgeNode.frame.minY + 1.0), size: labelLayout.size)
|
||||||
@ -274,6 +296,8 @@ class PermissionInfoItemNode: ListViewItemNode {
|
|||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.maxX + 8.0, y: 16.0), size: titleLayout.size)
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: strongSelf.badgeNode.frame.maxX + 8.0, y: 16.0), size: titleLayout.size)
|
||||||
|
|
||||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + 9.0), size: textLayout.size)
|
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset, y: strongSelf.titleNode.frame.maxY + 9.0), size: textLayout.size)
|
||||||
|
|
||||||
|
strongSelf.closeButton.frame = CGRect(x: params.width - rightInset - 20.0, y: 2.0, width: 32.0, height: 32.0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public func largestRepresentationForPhoto(_ photo: TelegramMediaImage) -> Telegr
|
|||||||
return photo.representationForDisplayAtSize(CGSize(width: 1280.0, height: 1280.0))
|
return photo.representationForDisplayAtSize(CGSize(width: 1280.0, height: 1280.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false) -> Signal<(Data?, Data?, Bool), NoError> {
|
||||||
if let smallestRepresentation = smallestImageRepresentation(photoReference.media.representations), let largestRepresentation = photoReference.media.representationForDisplayAtSize(fullRepresentationSize) {
|
if let smallestRepresentation = smallestImageRepresentation(photoReference.media.representations), let largestRepresentation = photoReference.media.representationForDisplayAtSize(fullRepresentationSize) {
|
||||||
let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|
let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|
||||||
|
|
||||||
|
|||||||
@ -52,6 +52,8 @@ enum PresentationResourceKey: Int32 {
|
|||||||
|
|
||||||
case itemListCloudFetchIcon
|
case itemListCloudFetchIcon
|
||||||
|
|
||||||
|
case itemListCloseIconImage
|
||||||
|
|
||||||
case chatListLockTopLockedImage
|
case chatListLockTopLockedImage
|
||||||
case chatListLockBottomLockedImage
|
case chatListLockBottomLockedImage
|
||||||
case chatListLockTopUnlockedImage
|
case chatListLockTopUnlockedImage
|
||||||
@ -66,6 +68,7 @@ enum PresentationResourceKey: Int32 {
|
|||||||
case chatListMutedIcon
|
case chatListMutedIcon
|
||||||
case chatListVerifiedIcon
|
case chatListVerifiedIcon
|
||||||
case chatListSecretIcon
|
case chatListSecretIcon
|
||||||
|
case chatListRecentStatusOnlineIcon
|
||||||
|
|
||||||
case chatTitleLockIcon
|
case chatTitleLockIcon
|
||||||
case chatTitleMuteIcon
|
case chatTitleMuteIcon
|
||||||
|
|||||||
@ -111,6 +111,19 @@ struct PresentationResourcesChatList {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func recentStatusOnlineIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||||
|
return theme.image(PresentationResourceKey.chatListRecentStatusOnlineIcon.rawValue, { theme in
|
||||||
|
return generateImage(CGSize(width: 14.0, height: 14.0), rotatedContext: { size, context in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.clear(bounds)
|
||||||
|
context.setFillColor(theme.chatList.backgroundColor.cgColor)
|
||||||
|
context.fillEllipse(in: bounds)
|
||||||
|
context.setFillColor(theme.chatList.unreadBadgeActiveBackgroundColor.cgColor)
|
||||||
|
context.fillEllipse(in: bounds.insetBy(dx: 2.0, dy: 2.0))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static func badgeBackgroundActive(_ theme: PresentationTheme) -> UIImage? {
|
static func badgeBackgroundActive(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatListBadgeBackgroundActive.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatListBadgeBackgroundActive.rawValue, { theme in
|
||||||
return generateBadgeBackgroundImage(theme: theme, active: true)
|
return generateBadgeBackgroundImage(theme: theme, active: true)
|
||||||
|
|||||||
@ -135,4 +135,22 @@ struct PresentationResourcesItemList {
|
|||||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: theme.list.itemAccentColor)
|
generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: theme.list.itemAccentColor)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func itemListCloseIconImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
|
return theme.image(PresentationResourceKey.itemListCloseIconImage.rawValue, { theme in
|
||||||
|
return generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setBlendMode(.copy)
|
||||||
|
context.setStrokeColor(theme.list.disclosureArrowColor.cgColor)
|
||||||
|
context.setLineWidth(2.0)
|
||||||
|
context.setLineCap(.round)
|
||||||
|
context.move(to: CGPoint(x: 1.0, y: 1.0))
|
||||||
|
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0))
|
||||||
|
context.strokePath()
|
||||||
|
context.move(to: CGPoint(x: size.width - 1.0, y: 1.0))
|
||||||
|
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
|
||||||
|
context.strokePath()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -4,6 +4,7 @@ import SwiftSignalKit
|
|||||||
import Display
|
import Display
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
import LegacyComponents
|
||||||
|
|
||||||
private func requestContextResults(account: Account, botId: PeerId, query: String, peerId: PeerId, offset: String = "", existingResults: ChatContextResultCollection? = nil, limit: Int = 30) -> Signal<ChatContextResultCollection?, NoError> {
|
private func requestContextResults(account: Account, botId: PeerId, query: String, peerId: PeerId, offset: String = "", existingResults: ChatContextResultCollection? = nil, limit: Int = 30) -> Signal<ChatContextResultCollection?, NoError> {
|
||||||
return requestChatContextResults(account: account, botId: botId, peerId: peerId, query: query, offset: offset)
|
return requestChatContextResults(account: account, botId: botId, peerId: peerId, query: query, offset: offset)
|
||||||
@ -30,15 +31,18 @@ final class WebSearchControllerInteraction {
|
|||||||
let setSearchQuery: (String) -> Void
|
let setSearchQuery: (String) -> Void
|
||||||
let deleteRecentQuery: (String) -> Void
|
let deleteRecentQuery: (String) -> Void
|
||||||
let toggleSelection: ([String], Bool) -> Void
|
let toggleSelection: ([String], Bool) -> Void
|
||||||
let sendSelected: (ChatContextResultCollection) -> Void
|
let sendSelected: (ChatContextResultCollection, ChatContextResult?) -> Void
|
||||||
var selectionState: WebSearchSelectionState?
|
var selectionState: WebSearchSelectionState?
|
||||||
|
var hiddenMediaId: String?
|
||||||
|
let editingContext: TGMediaEditingContext
|
||||||
|
|
||||||
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping ([String], Bool) -> Void, sendSelected: @escaping (ChatContextResultCollection) -> Void) {
|
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping ([String], Bool) -> Void, sendSelected: @escaping (ChatContextResultCollection, ChatContextResult?) -> Void, editingContext: TGMediaEditingContext) {
|
||||||
self.openResult = openResult
|
self.openResult = openResult
|
||||||
self.setSearchQuery = setSearchQuery
|
self.setSearchQuery = setSearchQuery
|
||||||
self.deleteRecentQuery = deleteRecentQuery
|
self.deleteRecentQuery = deleteRecentQuery
|
||||||
self.toggleSelection = toggleSelection
|
self.toggleSelection = toggleSelection
|
||||||
self.sendSelected = sendSelected
|
self.sendSelected = sendSelected
|
||||||
|
self.editingContext = editingContext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +73,7 @@ final class WebSearchController: ViewController {
|
|||||||
|
|
||||||
private var navigationContentNode: WebSearchNavigationContentNode?
|
private var navigationContentNode: WebSearchNavigationContentNode?
|
||||||
|
|
||||||
init(account: Account, chatLocation: ChatLocation, configuration: SearchBotsConfiguration, sendSelected: @escaping ([String], ChatContextResultCollection) -> Void) {
|
init(account: Account, chatLocation: ChatLocation, configuration: SearchBotsConfiguration, sendSelected: @escaping ([String], ChatContextResultCollection, TGMediaEditingContext) -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.chatLocation = chatLocation
|
self.chatLocation = chatLocation
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
@ -117,8 +121,15 @@ final class WebSearchController: ViewController {
|
|||||||
}
|
}
|
||||||
self.navigationBar?.setContentNode(navigationContentNode, animated: false)
|
self.navigationBar?.setContentNode(navigationContentNode, animated: false)
|
||||||
|
|
||||||
self.controllerInteraction = WebSearchControllerInteraction(openResult: { result in
|
let editingContext = TGMediaEditingContext()
|
||||||
|
self.controllerInteraction = WebSearchControllerInteraction(openResult: { [weak self] result in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.controllerNode.openResult(currentResult: result, present: { [weak self] viewController, arguments in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.present(viewController, in: .window(.root), with: arguments)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}, setSearchQuery: { [weak self] query in
|
}, setSearchQuery: { [weak self] query in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.navigationContentNode?.setQuery(query)
|
strongSelf.navigationContentNode?.setQuery(query)
|
||||||
@ -133,11 +144,15 @@ final class WebSearchController: ViewController {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.updateInterfaceState { $0.withToggledSelectedMessages(ids, value: value) }
|
strongSelf.updateInterfaceState { $0.withToggledSelectedMessages(ids, value: value) }
|
||||||
}
|
}
|
||||||
}, sendSelected: { [weak self] collection in
|
}, sendSelected: { [weak self] collection, current in
|
||||||
if let strongSelf = self, let state = strongSelf.interfaceState.state, !state.selectionState.selectedIds.isEmpty {
|
if let strongSelf = self, let state = strongSelf.interfaceState.state {
|
||||||
sendSelected(Array(state.selectionState.selectedIds), collection)
|
var selectedIds = state.selectionState.selectedIds
|
||||||
|
if let current = current {
|
||||||
|
selectedIds.insert(current.id)
|
||||||
|
}
|
||||||
|
sendSelected(Array(selectedIds), collection, editingContext)
|
||||||
}
|
}
|
||||||
})
|
}, editingContext: editingContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Display
|
import Display
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
import LegacyComponents
|
||||||
|
|
||||||
private struct WebSearchContextResultStableId: Hashable {
|
private struct WebSearchContextResultStableId: Hashable {
|
||||||
let result: ChatContextResult
|
let result: ChatContextResult
|
||||||
@ -144,12 +145,17 @@ class WebSearchControllerNode: ASDisplayNode {
|
|||||||
private var hasMore = false
|
private var hasMore = false
|
||||||
private var isLoadingMore = false
|
private var isLoadingMore = false
|
||||||
|
|
||||||
|
private let selectionContext = TGMediaSelectionContext()
|
||||||
|
|
||||||
|
private let hiddenMediaId = Promise<String?>(nil)
|
||||||
|
private var hiddenMediaDisposable: Disposable?
|
||||||
|
|
||||||
private let results = ValuePromise<ChatContextResultCollection?>(nil, ignoreRepeated: true)
|
private let results = ValuePromise<ChatContextResultCollection?>(nil, ignoreRepeated: true)
|
||||||
|
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
private let loadMoreDisposable = MetaDisposable()
|
private let loadMoreDisposable = MetaDisposable()
|
||||||
|
|
||||||
private let recentDisposable = MetaDisposable()
|
private var recentDisposable: Disposable?
|
||||||
|
|
||||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
@ -219,7 +225,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
let previousRecentItems = Atomic<[WebSearchRecentQueryEntry]?>(value: nil)
|
let previousRecentItems = Atomic<[WebSearchRecentQueryEntry]?>(value: nil)
|
||||||
self.recentDisposable.set((combineLatest(webSearchRecentQueries(postbox: self.account.postbox), self.webSearchInterfaceStatePromise.get())
|
self.recentDisposable = (combineLatest(webSearchRecentQueries(postbox: self.account.postbox), self.webSearchInterfaceStatePromise.get())
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] queries, interfaceState in
|
|> deliverOnMainQueue).start(next: { [weak self] queries, interfaceState in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
var entries: [WebSearchRecentQueryEntry] = []
|
var entries: [WebSearchRecentQueryEntry] = []
|
||||||
@ -236,7 +242,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
|||||||
let transition = preparedWebSearchRecentTransition(from: previousEntries ?? [], to: entries, account: strongSelf.account, theme: interfaceState.presentationData.theme, strings: interfaceState.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction, header: header)
|
let transition = preparedWebSearchRecentTransition(from: previousEntries ?? [], to: entries, account: strongSelf.account, theme: interfaceState.presentationData.theme, strings: interfaceState.presentationData.strings, controllerInteraction: strongSelf.controllerInteraction, header: header)
|
||||||
strongSelf.enqueueRecentTransition(transition, firstTime: previousEntries == nil)
|
strongSelf.enqueueRecentTransition(transition, firstTime: previousEntries == nil)
|
||||||
}
|
}
|
||||||
}))
|
})
|
||||||
|
|
||||||
self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in
|
self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in
|
||||||
if let strongSelf = self, let bottom = visibleItems.bottom, let entries = strongSelf.currentEntries {
|
if let strongSelf = self, let bottom = visibleItems.bottom, let entries = strongSelf.currentEntries {
|
||||||
@ -253,10 +259,26 @@ class WebSearchControllerNode: ASDisplayNode {
|
|||||||
self.recentQueriesNode.beganInteractiveDragging = { [weak self] in
|
self.recentQueriesNode.beganInteractiveDragging = { [weak self] in
|
||||||
self?.dismissInput?()
|
self?.dismissInput?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.hiddenMediaDisposable = (self.hiddenMediaId.get()
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] id in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.controllerInteraction.hiddenMediaId = id
|
||||||
|
|
||||||
|
strongSelf.gridNode.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? WebSearchItemNode {
|
||||||
|
itemNode.updateHiddenMedia()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
|
self.disposable.dispose()
|
||||||
|
self.recentDisposable?.dispose()
|
||||||
self.loadMoreDisposable.dispose()
|
self.loadMoreDisposable.dispose()
|
||||||
|
self.hiddenMediaDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePresentationData(theme: PresentationTheme, strings: PresentationStrings) {
|
func updatePresentationData(theme: PresentationTheme, strings: PresentationStrings) {
|
||||||
@ -519,8 +541,79 @@ class WebSearchControllerNode: ASDisplayNode {
|
|||||||
|
|
||||||
@objc private func sendPressed() {
|
@objc private func sendPressed() {
|
||||||
if let results = self.currentProcessedResults {
|
if let results = self.currentProcessedResults {
|
||||||
self.controllerInteraction.sendSelected(results)
|
self.controllerInteraction.sendSelected(results, nil)
|
||||||
}
|
}
|
||||||
self.cancel?()
|
self.cancel?()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openResult(currentResult: ChatContextResult, present: (ViewController, Any?) -> Void) {
|
||||||
|
if let state = self.webSearchInterfaceState.state, state.mode == .images {
|
||||||
|
if let results = self.currentProcessedResults?.results {
|
||||||
|
presentLegacyWebSearchGallery(account: self.account, peer: nil, theme: self.theme, results: results, current: currentResult, selectionContext: self.selectionContext, editingContext: self.controllerInteraction.editingContext, updateHiddenMedia: { [weak self] id in
|
||||||
|
self?.hiddenMediaId.set(.single(id))
|
||||||
|
}, transitionHostView: { [weak self] in
|
||||||
|
return self?.gridNode.view
|
||||||
|
}, transitionView: { [weak self] result in
|
||||||
|
return self?.transitionView(for: result)
|
||||||
|
}, completed: { [weak self] result in
|
||||||
|
if let strongSelf = self, let results = strongSelf.currentProcessedResults {
|
||||||
|
strongSelf.controllerInteraction.sendSelected(results, nil)
|
||||||
|
strongSelf.cancel?()
|
||||||
|
}
|
||||||
|
}, present: present)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let results = self.currentProcessedResults?.results {
|
||||||
|
var entries: [WebSearchGalleryEntry] = []
|
||||||
|
var centralIndex: Int = 0
|
||||||
|
for i in 0 ..< results.count {
|
||||||
|
entries.append(WebSearchGalleryEntry(result: results[i]))
|
||||||
|
if results[i] == currentResult {
|
||||||
|
centralIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = WebSearchGalleryController(account: self.account, entries: entries, centralIndex: centralIndex, replaceRootController: { (controller, _) in
|
||||||
|
|
||||||
|
}, baseNavigationController: nil)
|
||||||
|
self.hiddenMediaId.set((controller.hiddenMedia |> deliverOnMainQueue)
|
||||||
|
|> map { entry in
|
||||||
|
return entry?.result.id
|
||||||
|
})
|
||||||
|
present(controller, WebSearchGalleryControllerPresentationArguments(transitionArguments: { [weak self] entry -> GalleryTransitionArguments? in
|
||||||
|
if let strongSelf = self {
|
||||||
|
var transitionNode: WebSearchItemNode?
|
||||||
|
strongSelf.gridNode.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? WebSearchItemNode, itemNode.item?.result.id == entry.result.id {
|
||||||
|
transitionNode = itemNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let transitionNode = transitionNode {
|
||||||
|
return GalleryTransitionArguments(transitionNode: (transitionNode, { [weak transitionNode] in
|
||||||
|
return transitionNode?.transitionView().snapshotContentTree(unhide: true)
|
||||||
|
}), addToTransitionSurface: { view in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.gridNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.gridNode.view)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func transitionView(for result: ChatContextResult) -> UIView? {
|
||||||
|
var transitionNode: WebSearchItemNode?
|
||||||
|
self.gridNode.forEachItemNode { itemNode in
|
||||||
|
if let itemNode = itemNode as? WebSearchItemNode, itemNode.item?.result.id == result.id {
|
||||||
|
transitionNode = itemNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let transitionNode = transitionNode {
|
||||||
|
return transitionNode.transitionView()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
254
TelegramUI/WebSearchGalleryController.swift
Normal file
254
TelegramUI/WebSearchGalleryController.swift
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
import Foundation
|
||||||
|
import Display
|
||||||
|
import QuickLook
|
||||||
|
import Postbox
|
||||||
|
import SwiftSignalKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import TelegramCore
|
||||||
|
import SafariServices
|
||||||
|
|
||||||
|
struct WebSearchGalleryEntry: Equatable {
|
||||||
|
let result: ChatContextResult
|
||||||
|
|
||||||
|
static func ==(lhs: WebSearchGalleryEntry, rhs: WebSearchGalleryEntry) -> Bool {
|
||||||
|
return lhs.result == rhs.result
|
||||||
|
}
|
||||||
|
|
||||||
|
func item(account: Account, presentationData: PresentationData) -> GalleryItem {
|
||||||
|
switch self.result {
|
||||||
|
case let .externalReference(queryId, id, type, _, _, url, content, thumbnail, _):
|
||||||
|
if let content = content, type == "gif", let thumbnailResource = thumbnail?.resource, let dimensions = content.dimensions {
|
||||||
|
let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]))
|
||||||
|
return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: fileReference, streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true), originData: nil, indexData: nil, contentInfo: nil, caption: NSAttributedString(), credit: nil, performAction: { _ in }, openActionOptions: { _ in })
|
||||||
|
}
|
||||||
|
case let .internalReference(queryId, id, _, _, _, _, file, _):
|
||||||
|
if let file = file {
|
||||||
|
return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: .standalone(media: file), streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true), originData: nil, indexData: nil, contentInfo: nil, caption: NSAttributedString(), credit: nil, performAction: { _ in }, openActionOptions: { _ in })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WebSearchGalleryControllerPresentationArguments {
|
||||||
|
let transitionArguments: (WebSearchGalleryEntry) -> GalleryTransitionArguments?
|
||||||
|
|
||||||
|
init(transitionArguments: @escaping (WebSearchGalleryEntry) -> GalleryTransitionArguments?) {
|
||||||
|
self.transitionArguments = transitionArguments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSearchGalleryController: ViewController {
|
||||||
|
private var galleryNode: GalleryControllerNode {
|
||||||
|
return self.displayNode as! GalleryControllerNode
|
||||||
|
}
|
||||||
|
|
||||||
|
private let account: Account
|
||||||
|
private var presentationData: PresentationData
|
||||||
|
|
||||||
|
private let _ready = Promise<Bool>()
|
||||||
|
override var ready: Promise<Bool> {
|
||||||
|
return self._ready
|
||||||
|
}
|
||||||
|
private var didSetReady = false
|
||||||
|
|
||||||
|
private let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var entries: [WebSearchGalleryEntry] = []
|
||||||
|
private var centralEntryIndex: Int?
|
||||||
|
|
||||||
|
private let centralItemTitle = Promise<String>()
|
||||||
|
private let centralItemTitleView = Promise<UIView?>()
|
||||||
|
private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>()
|
||||||
|
private let centralItemFooterContentNode = Promise<GalleryFooterContentNode?>()
|
||||||
|
private let centralItemAttributesDisposable = DisposableSet();
|
||||||
|
|
||||||
|
private let _hiddenMedia = Promise<WebSearchGalleryEntry?>(nil)
|
||||||
|
var hiddenMedia: Signal<WebSearchGalleryEntry?, NoError> {
|
||||||
|
return self._hiddenMedia.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
private let replaceRootController: (ViewController, ValuePromise<Bool>?) -> Void
|
||||||
|
private let baseNavigationController: NavigationController?
|
||||||
|
|
||||||
|
init(account: Account, entries: [WebSearchGalleryEntry], centralIndex: Int, replaceRootController: @escaping (ViewController, ValuePromise<Bool>?) -> Void, baseNavigationController: NavigationController?) {
|
||||||
|
self.account = account
|
||||||
|
self.replaceRootController = replaceRootController
|
||||||
|
self.baseNavigationController = baseNavigationController
|
||||||
|
|
||||||
|
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
|
||||||
|
|
||||||
|
let backItem = UIBarButtonItem(backButtonAppearanceWithTitle: presentationData.strings.Common_Back, target: self, action: #selector(self.donePressed))
|
||||||
|
self.navigationItem.leftBarButtonItem = backItem
|
||||||
|
|
||||||
|
self.statusBar.statusBarStyle = .White
|
||||||
|
|
||||||
|
let entriesSignal: Signal<[WebSearchGalleryEntry], NoError> = .single(entries)
|
||||||
|
|
||||||
|
self.disposable.set((entriesSignal |> deliverOnMainQueue).start(next: { [weak self] entries in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.entries = entries
|
||||||
|
strongSelf.centralEntryIndex = centralIndex
|
||||||
|
if strongSelf.isViewLoaded {
|
||||||
|
strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({
|
||||||
|
$0.item(account: account, presentationData: strongSelf.presentationData)
|
||||||
|
}), centralItemIndex: centralIndex, keepFirst: false)
|
||||||
|
|
||||||
|
let ready = strongSelf.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak strongSelf] _ in
|
||||||
|
strongSelf?.didSetReady = true
|
||||||
|
}
|
||||||
|
strongSelf._ready.set(ready |> map { true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.centralItemAttributesDisposable.add(self.centralItemTitle.get().start(next: { [weak self] title in
|
||||||
|
self?.navigationItem.title = title
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.centralItemAttributesDisposable.add(self.centralItemTitleView.get().start(next: { [weak self] titleView in
|
||||||
|
self?.navigationItem.titleView = titleView
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.centralItemAttributesDisposable.add(self.centralItemFooterContentNode.get().start(next: { [weak self] footerContentNode in
|
||||||
|
self?.galleryNode.updatePresentationState({
|
||||||
|
$0.withUpdatedFooterContentNode(footerContentNode)
|
||||||
|
}, transition: .immediate)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
required init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable.dispose()
|
||||||
|
self.centralItemAttributesDisposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func donePressed() {
|
||||||
|
self.dismiss(forceAway: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dismiss(forceAway: Bool) {
|
||||||
|
var animatedOutNode = true
|
||||||
|
var animatedOutInterface = false
|
||||||
|
|
||||||
|
let completion = { [weak self] in
|
||||||
|
if animatedOutNode && animatedOutInterface {
|
||||||
|
self?._hiddenMedia.set(.single(nil))
|
||||||
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? WebSearchGalleryControllerPresentationArguments {
|
||||||
|
if !self.entries.isEmpty {
|
||||||
|
if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]), !forceAway {
|
||||||
|
animatedOutNode = false
|
||||||
|
centralItemNode.animateOut(to: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: {
|
||||||
|
animatedOutNode = true
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.galleryNode.animateOut(animateContent: animatedOutNode, completion: {
|
||||||
|
animatedOutInterface = true
|
||||||
|
completion()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override func loadDisplayNode() {
|
||||||
|
let controllerInteraction = GalleryControllerInteraction(presentController: { [weak self] controller, arguments in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.present(controller, in: .window(.root), with: arguments)
|
||||||
|
}
|
||||||
|
}, dismissController: { [weak self] in
|
||||||
|
self?.dismiss(forceAway: true)
|
||||||
|
}, replaceRootController: { [weak self] controller, ready in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.replaceRootController(controller, ready)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
|
||||||
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
|
self.galleryNode.statusBar = self.statusBar
|
||||||
|
self.galleryNode.navigationBar = self.navigationBar
|
||||||
|
|
||||||
|
self.galleryNode.transitionDataForCentralItem = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode(), let presentationArguments = strongSelf.presentationArguments as? WebSearchGalleryControllerPresentationArguments {
|
||||||
|
if let transitionArguments = presentationArguments.transitionArguments(strongSelf.entries[centralItemNode.index]) {
|
||||||
|
return (transitionArguments.transitionNode, transitionArguments.addToTransitionSurface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.galleryNode.dismiss = { [weak self] in
|
||||||
|
self?._hiddenMedia.set(.single(nil))
|
||||||
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.galleryNode.pager.replaceItems(self.entries.map({
|
||||||
|
$0.item(account: account, presentationData: self.presentationData)
|
||||||
|
}), centralItemIndex: self.centralEntryIndex)
|
||||||
|
|
||||||
|
self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in
|
||||||
|
if let strongSelf = self {
|
||||||
|
var hiddenItem: WebSearchGalleryEntry?
|
||||||
|
if let index = index {
|
||||||
|
hiddenItem = strongSelf.entries[index]
|
||||||
|
|
||||||
|
if let node = strongSelf.galleryNode.pager.centralItemNode() {
|
||||||
|
strongSelf.centralItemTitle.set(node.title())
|
||||||
|
strongSelf.centralItemTitleView.set(node.titleView())
|
||||||
|
strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
|
||||||
|
strongSelf.centralItemFooterContentNode.set(node.footerContent())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strongSelf.didSetReady {
|
||||||
|
strongSelf._hiddenMedia.set(.single(hiddenItem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ready = self.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak self] _ in
|
||||||
|
self?.didSetReady = true
|
||||||
|
}
|
||||||
|
self._ready.set(ready |> map { true })
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
var nodeAnimatesItself = false
|
||||||
|
|
||||||
|
if let centralItemNode = self.galleryNode.pager.centralItemNode(), let presentationArguments = self.presentationArguments as? WebSearchGalleryControllerPresentationArguments {
|
||||||
|
self.centralItemTitle.set(centralItemNode.title())
|
||||||
|
self.centralItemTitleView.set(centralItemNode.titleView())
|
||||||
|
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
|
||||||
|
self.centralItemFooterContentNode.set(centralItemNode.footerContent())
|
||||||
|
|
||||||
|
if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) {
|
||||||
|
nodeAnimatesItself = true
|
||||||
|
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
|
||||||
|
|
||||||
|
self._hiddenMedia.set(.single(self.entries[centralItemNode.index]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.galleryNode.animateIn(animateContent: !nodeAnimatesItself)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
|
self.galleryNode.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||||
|
self.galleryNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -89,7 +89,6 @@ final class WebSearchItemNode: GridItemNode {
|
|||||||
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
|
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
|
||||||
|
|
||||||
var imageResource: TelegramMediaResource?
|
var imageResource: TelegramMediaResource?
|
||||||
var stickerFile: TelegramMediaFile?
|
|
||||||
var videoFile: TelegramMediaFile?
|
var videoFile: TelegramMediaFile?
|
||||||
var imageDimensions: CGSize?
|
var imageDimensions: CGSize?
|
||||||
switch item.result {
|
switch item.result {
|
||||||
@ -147,7 +146,57 @@ final class WebSearchItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let updateImageSignal = updateImageSignal {
|
if let updateImageSignal = updateImageSignal {
|
||||||
self.imageNode.setSignal(updateImageSignal)
|
let editingContext = item.controllerInteraction.editingContext
|
||||||
|
let editedImageSignal = Signal<UIImage?, NoError> { subscriber in
|
||||||
|
let editableItem = LegacyWebSearchItem(result: item.result, dimensions: CGSize(), thumbnailImage: .complete(), originalImage: .complete())
|
||||||
|
if let signal = editingContext.thumbnailImageSignal(for: editableItem) {
|
||||||
|
let disposable = signal.start(next: { next in
|
||||||
|
if let image = next as? UIImage {
|
||||||
|
subscriber.putNext(image)
|
||||||
|
} else {
|
||||||
|
subscriber.putNext(nil)
|
||||||
|
}
|
||||||
|
}, error: { _ in
|
||||||
|
}, completed: nil)!
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
disposable.dispose()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let editedSignal: Signal<((TransformImageArguments) -> DrawingContext?)?, NoError> = editedImageSignal
|
||||||
|
|> map { image in
|
||||||
|
if let image = image {
|
||||||
|
return { arguments in
|
||||||
|
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
||||||
|
let drawingRect = arguments.drawingRect
|
||||||
|
let fittedSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize)
|
||||||
|
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
|
||||||
|
|
||||||
|
context.withFlippedContext { c in
|
||||||
|
c.setBlendMode(.copy)
|
||||||
|
if let cgImage = image.cgImage {
|
||||||
|
drawImage(context: c, image: cgImage, orientation: .up, in: fittedRect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError> = editedSignal
|
||||||
|
|> mapToSignal { result in
|
||||||
|
if result != nil {
|
||||||
|
return .single(result!)
|
||||||
|
} else {
|
||||||
|
return updateImageSignal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.imageNode.setSignal(imageSignal)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.currentImageResource = imageResource
|
self.currentImageResource = imageResource
|
||||||
@ -170,10 +219,9 @@ final class WebSearchItemNode: GridItemNode {
|
|||||||
strongSelf.updateSelectionState(animated: true)
|
strongSelf.updateSelectionState(animated: true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
|
||||||
self.addSubnode(selectionNode)
|
self.addSubnode(selectionNode)
|
||||||
self.selectionNode = selectionNode
|
self.selectionNode = selectionNode
|
||||||
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let item = self.item {
|
if let item = self.item {
|
||||||
@ -184,6 +232,18 @@ final class WebSearchItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateHiddenMedia() {
|
||||||
|
if let item = self.item {
|
||||||
|
self.imageNode.isHidden = item.controllerInteraction.hiddenMediaId == item.result.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionView() -> UIView {
|
||||||
|
let view = self.imageNode.view.snapshotContentTree(unhide: true)!
|
||||||
|
view.frame = self.convert(self.bounds, to: nil)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
override func layout() {
|
override func layout() {
|
||||||
super.layout()
|
super.layout()
|
||||||
|
|
||||||
@ -195,7 +255,8 @@ final class WebSearchItemNode: GridItemNode {
|
|||||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor))()
|
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor))()
|
||||||
}
|
}
|
||||||
|
|
||||||
self.selectionNode?.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
let checkSize = CGSize(width: 32.0, height: 32.0)
|
||||||
|
self.selectionNode?.frame = CGRect(origin: CGPoint(x: imageFrame.width - checkSize.width, y: 0.0), size: checkSize)
|
||||||
let progressDiameter: CGFloat = 40.0
|
let progressDiameter: CGFloat = 40.0
|
||||||
self.statusNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - progressDiameter) / 2.0), y: floor((imageFrame.size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter))
|
self.statusNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - progressDiameter) / 2.0), y: floor((imageFrame.size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter))
|
||||||
|
|
||||||
@ -203,40 +264,26 @@ final class WebSearchItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||||
// guard let controllerInteraction = self.controllerInteraction, let message = self.item?.message else {
|
guard let item = self.item else {
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// switch recognizer.state {
|
switch recognizer.state {
|
||||||
// case .ended:
|
case .ended:
|
||||||
// if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation {
|
if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation {
|
||||||
// switch gesture {
|
switch gesture {
|
||||||
// case .tap:
|
case .tap:
|
||||||
// if let (account, media, _) = self.currentState {
|
item.controllerInteraction.openResult(item.result)
|
||||||
// if let file = media as? TelegramMediaFile {
|
|
||||||
// if let resourceStatus = self.resourceStatus {
|
case .longTap:
|
||||||
// switch resourceStatus {
|
break
|
||||||
// case .Fetching:
|
default:
|
||||||
// messageMediaFileCancelInteractiveFetch(account: account, messageId: message.id, file: file)
|
break
|
||||||
// case .Local:
|
}
|
||||||
// let _ = controllerInteraction.openMessage(message, .default)
|
}
|
||||||
// case .Remote:
|
default:
|
||||||
// self.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: file, userInitiated: true).start())
|
break
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// let _ = controllerInteraction.openMessage(message, .default)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// case .longTap:
|
|
||||||
// controllerInteraction.openMessageContextMenu(message, false, self, self.bounds)
|
|
||||||
// default:
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// default:
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user