mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-28 19:05:49 +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 */; };
|
||||
09DD88F521BF9730000766BC /* WebSearchRecentQueries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88F421BF9730000766BC /* WebSearchRecentQueries.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 */; };
|
||||
9F06830921A404AB001D8EDB /* NotificationExceptionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
@ -2360,6 +2364,7 @@
|
||||
0962E67E21BA786A00245FD9 /* WebSearchItem.swift */,
|
||||
09DD88F221BF907C000766BC /* WebSearchRecentQueryItem.swift */,
|
||||
09DD88F421BF9730000766BC /* WebSearchRecentQueries.swift */,
|
||||
09F799FB21C3FF3000820234 /* WebSearchGalleryController.swift */,
|
||||
);
|
||||
name = "Web Search";
|
||||
sourceTree = "<group>";
|
||||
@ -3171,6 +3176,7 @@
|
||||
D07ABBAA202A1BD1003671DE /* LegacyWallpaperEditor.swift */,
|
||||
D0380DAC204ED434000414AB /* LegacyLiveUploadInterface.swift */,
|
||||
D097C26B20DD1EA5007BB4B8 /* OverlayStatusController.swift */,
|
||||
09F799F921C3542D00820234 /* LegacyWebSearchGallery.swift */,
|
||||
);
|
||||
name = "Legacy Components";
|
||||
sourceTree = "<group>";
|
||||
@ -5035,6 +5041,7 @@
|
||||
D01776B51F1D6CCC0044446D /* RadialStatusContentNode.swift in Sources */,
|
||||
D02F4AF01FD4C46D004DFBAE /* SystemVideoContent.swift in Sources */,
|
||||
D0477D1F1F619E0700412B44 /* GalleryVideoDecoration.swift in Sources */,
|
||||
09F799FA21C3542D00820234 /* LegacyWebSearchGallery.swift in Sources */,
|
||||
D01C99781F4F382C00DCFAF6 /* InstantPageSettingsItemTheme.swift in Sources */,
|
||||
D0CAD8FB20AE1D1B00ACD96E /* ChannelMemberCategoryListContext.swift in Sources */,
|
||||
D0EC6CC21EB9F58800EBF1C3 /* LegacyEmptyController.swift in Sources */,
|
||||
@ -5585,6 +5592,7 @@
|
||||
D0EC6DD41EB9F58900EBF1C3 /* CommandChatInputContextPanelNode.swift in Sources */,
|
||||
D0EC6DD51EB9F58900EBF1C3 /* CommandChatInputPanelItem.swift in Sources */,
|
||||
D0EC6DD61EB9F58900EBF1C3 /* VerticalListContextResultsChatInputContextPanelNode.swift in Sources */,
|
||||
09F799FC21C3FF3000820234 /* WebSearchGalleryController.swift in Sources */,
|
||||
D0EC6DD71EB9F58900EBF1C3 /* VerticalListContextResultsChatInputPanelItem.swift in Sources */,
|
||||
D0EC6DD81EB9F58900EBF1C3 /* VerticalListContextResultsChatInputPanelButtonItem.swift in Sources */,
|
||||
D04281F4200E5AB0009DDE36 /* ChatRecentActionsController.swift in Sources */,
|
||||
|
||||
@ -2,6 +2,7 @@ import Foundation
|
||||
import SwiftSignalKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
|
||||
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) {
|
||||
let _ = rateCall(account: account, report: report, starsCount: Int32(starsCount), comment: comment).start()
|
||||
private func rateCallAndSendLogs(account: Account, report: ReportCallRating, starsCount: Int, comment: String, includeLogs: Bool) -> Signal<Void, NoError> {
|
||||
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 {
|
||||
@ -338,13 +350,13 @@ func callRatingController(account: Account, report: ReportCallRating, present: @
|
||||
if let contentNode = contentNode, let rating = contentNode.rating {
|
||||
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: {
|
||||
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: {
|
||||
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)
|
||||
} 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)
|
||||
}
|
||||
|
||||
func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
||||
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||
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 url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
@ -206,7 +206,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
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 {
|
||||
case .none:
|
||||
break
|
||||
|
||||
@ -7,6 +7,7 @@ import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
import SafariServices
|
||||
import MobileCoreServices
|
||||
import LegacyComponents
|
||||
|
||||
public enum ChatControllerPeekActions {
|
||||
case standard
|
||||
@ -3747,11 +3748,12 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
}
|
||||
|
||||
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
|
||||
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 {
|
||||
return
|
||||
}
|
||||
@ -3766,7 +3768,14 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
legacyController.bind(controller: controller)
|
||||
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.completionBlock = { [weak legacyController] signals in
|
||||
if let legacyController = legacyController {
|
||||
@ -3800,19 +3809,45 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] configuration in
|
||||
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 {
|
||||
var results: [ChatContextResult] = []
|
||||
for id in ids {
|
||||
var result: ChatContextResult?
|
||||
for r in collection.results {
|
||||
if r.id == id {
|
||||
result = r
|
||||
results.append(r)
|
||||
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
|
||||
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.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 {
|
||||
disableAnimations = false
|
||||
} else {
|
||||
var previousPinnedCount = 0
|
||||
var updatedPinnedCount = 0
|
||||
var previousPinnedChats: [PeerId] = []
|
||||
var updatedPinnedChats: [PeerId] = []
|
||||
|
||||
var didIncludeRemovingPeerId = false
|
||||
if let previous = previousView {
|
||||
for entry in previous.filteredEntries {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if index.pinningIndex != nil {
|
||||
previousPinnedCount += 1
|
||||
previousPinnedChats.append(index.messageIndex.id.peerId)
|
||||
}
|
||||
if index.messageIndex.id.peerId == removingPeerId {
|
||||
didIncludeRemovingPeerId = true
|
||||
@ -563,14 +564,14 @@ final class ChatListNode: ListView {
|
||||
for entry in processedView.filteredEntries {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if index.pinningIndex != nil {
|
||||
updatedPinnedCount += 1
|
||||
updatedPinnedChats.append(index.messageIndex.id.peerId)
|
||||
}
|
||||
if index.messageIndex.id.peerId == removingPeerId {
|
||||
doesIncludeRemovingPeerId = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if previousPinnedCount != updatedPinnedCount {
|
||||
if previousPinnedChats != updatedPinnedChats {
|
||||
disableAnimations = false
|
||||
}
|
||||
if previousState.selectedPeerIds != state.selectedPeerIds {
|
||||
@ -580,6 +581,7 @@ final class ChatListNode: ListView {
|
||||
disableAnimations = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var searchMode = false
|
||||
if case .peers = mode {
|
||||
|
||||
@ -29,16 +29,16 @@ enum UnreadSearchBadge : Equatable {
|
||||
|
||||
var count: Int32 {
|
||||
switch self {
|
||||
case let .muted(count), let .unmuted(count):
|
||||
return count
|
||||
case let .muted(count), let .unmuted(count):
|
||||
return count
|
||||
}
|
||||
}
|
||||
var isMuted: Bool {
|
||||
switch self {
|
||||
case .muted:
|
||||
return true
|
||||
case .unmuted:
|
||||
return false
|
||||
case .muted:
|
||||
return true
|
||||
case .unmuted:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,6 +46,7 @@ enum UnreadSearchBadge : Equatable {
|
||||
private struct ChatListSearchRecentPeersEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
let peer: Peer
|
||||
let presence: PeerPresence?
|
||||
let unreadBadge: UnreadSearchBadge?
|
||||
let itemCustomWidth: CGFloat?
|
||||
var stableId: PeerId {
|
||||
@ -62,6 +63,13 @@ private struct ChatListSearchRecentPeersEntry: Comparable, Identifiable {
|
||||
if !lhs.peer.isEqual(rhs.peer) {
|
||||
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 {
|
||||
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 {
|
||||
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)
|
||||
}, isPeerSelected: isPeerSelected, customWidth: itemCustomWidth)
|
||||
}
|
||||
@ -114,7 +122,7 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
||||
private let isPeerSelected: (PeerId) -> Bool
|
||||
|
||||
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 queuedTransitions: [ChatListSearchRecentNodeTransition] = []
|
||||
@ -140,8 +148,7 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
||||
|
||||
let peersDisposable = DisposableSet()
|
||||
|
||||
|
||||
let recent: Signal<([Peer], [PeerId : UnreadSearchBadge]), NoError> = recentPeers(account: account)
|
||||
let recent: Signal<([Peer], [PeerId: UnreadSearchBadge], [PeerId : PeerPresence]), NoError> = recentPeers(account: account)
|
||||
|> filter { value -> Bool in
|
||||
switch value {
|
||||
case .disabled:
|
||||
@ -151,47 +158,51 @@ final class ChatListSearchRecentPeersNode: ASDisplayNode {
|
||||
}
|
||||
} |> mapToSignal { recent in
|
||||
switch recent {
|
||||
case .disabled:
|
||||
return .single(([], [:]))
|
||||
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 account.postbox.unreadMessageCountsView(items: peerViews.map {.peer($0.peerId)}) |> map { values in
|
||||
|
||||
var peers:[Peer] = []
|
||||
var unread:[PeerId: UnreadSearchBadge] = [:]
|
||||
for peerView in peerViews {
|
||||
if let peer = peerViewMainPeer(peerView) {
|
||||
var isMuted: Bool = false
|
||||
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
|
||||
switch notificationSettings.muteState {
|
||||
case .muted:
|
||||
isMuted = true
|
||||
default:
|
||||
break
|
||||
case .disabled:
|
||||
return .single(([], [:], [:]))
|
||||
case let .peers(peers):
|
||||
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
|
||||
var peers: [Peer] = []
|
||||
var unread: [PeerId: UnreadSearchBadge] = [:]
|
||||
var presences: [PeerId: PeerPresence] = [:]
|
||||
for peerView in peerViews {
|
||||
if let peer = peerViewMainPeer(peerView) {
|
||||
var isMuted: Bool = false
|
||||
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
|
||||
switch notificationSettings.muteState {
|
||||
case .muted:
|
||||
isMuted = true
|
||||
default:
|
||||
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 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 {
|
||||
var entries: [ChatListSearchRecentPeersEntry] = []
|
||||
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)
|
||||
@ -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.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 })
|
||||
itemCustomWidthValuePromise.set(itemCustomWidth)
|
||||
self.itemCustomWidthValuePromise.set(itemCustomWidth)
|
||||
}
|
||||
|
||||
func viewAndPeerAtPoint(_ point: CGPoint) -> (UIView, PeerId)? {
|
||||
|
||||
@ -377,7 +377,7 @@ private func universalServiceMessageString(theme: ChatPresentationThemeData?, st
|
||||
}
|
||||
attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor)
|
||||
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):
|
||||
attributedString = NSAttributedString(string: strings.AuthSessions_Message(domain).0, font: titleFont, textColor: primaryTextColor)
|
||||
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
|
||||
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 {
|
||||
var concealed = true
|
||||
if let attributeText = self.labelNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
|
||||
@ -906,7 +906,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode {
|
||||
return false
|
||||
}
|
||||
|
||||
func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
||||
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||
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 url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
|
||||
@ -143,7 +143,7 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
|
||||
func updateAutomaticMediaDownloadSettings(_ settings: AutomaticMediaDownloadSettings) {
|
||||
}
|
||||
|
||||
func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
||||
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||
return .none
|
||||
}
|
||||
|
||||
|
||||
@ -221,7 +221,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
return .waitForSingleTap
|
||||
}
|
||||
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 {
|
||||
case .none:
|
||||
break
|
||||
@ -1570,7 +1570,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
}
|
||||
var foundTapAction = false
|
||||
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 {
|
||||
case .none, .ignore:
|
||||
break
|
||||
@ -1629,7 +1629,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
||||
selectAll = true
|
||||
}
|
||||
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 {
|
||||
case .none, .ignore:
|
||||
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) {
|
||||
return .ignore
|
||||
} 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)
|
||||
}
|
||||
|
||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
||||
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||
if self.buttonNode.frame.contains(point) {
|
||||
return .openMessage
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble
|
||||
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 let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
||||
if content.instantPage != nil {
|
||||
|
||||
@ -77,7 +77,7 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent
|
||||
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 let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
||||
if content.instantPage != nil {
|
||||
|
||||
@ -82,10 +82,10 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
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 let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
||||
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)
|
||||
}
|
||||
|
||||
if let statusFrameValue = statusFrame, descriptionFrame.intersects(statusFrameValue) {
|
||||
if let statusFrameValue = statusFrame, descriptionFrame.intersects(statusFrameValue) {
|
||||
fittedLayoutSize.height += 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 {
|
||||
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)
|
||||
}
|
||||
|
||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
||||
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||
if self.bounds.contains(point) {
|
||||
/*if let webPage = self.webPage, case let .Loaded(content) = webPage.content {
|
||||
if content.instantPage != nil {
|
||||
|
||||
@ -445,7 +445,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
return mediaHidden
|
||||
}
|
||||
|
||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
||||
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||
return .none
|
||||
}
|
||||
|
||||
|
||||
@ -260,7 +260,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
return mediaHidden
|
||||
}
|
||||
|
||||
override func tapActionAtPoint(_ point: CGPoint) -> ChatMessageBubbleContentTapAction {
|
||||
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
|
||||
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))
|
||||
|
||||
|
||||
@ -277,7 +277,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
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
|
||||
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 {
|
||||
|
||||
@ -308,10 +308,10 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
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) {
|
||||
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 {
|
||||
case .none:
|
||||
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: {
|
||||
openCreateNewChannelImpl?()
|
||||
})
|
||||
]))
|
||||
]), displayPermissionPlaceholder: false)
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
@ -683,11 +683,13 @@ final class ContactListNode: ASDisplayNode {
|
||||
private var authorizationDisposable: Disposable?
|
||||
|
||||
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.presentation = presentation
|
||||
self.filters = filters
|
||||
self.displayPermissionPlaceholder = displayPermissionPlaceholder
|
||||
|
||||
self.presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -1005,21 +1007,19 @@ final class ContactListNode: ASDisplayNode {
|
||||
fixSearchableListNodeScrolling(strongSelf.listNode)
|
||||
}
|
||||
|
||||
authorizeImpl = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let _ = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { status in
|
||||
switch status {
|
||||
case .notDetermined:
|
||||
DeviceAccess.authorizeAccess(to: .contacts)
|
||||
case .denied, .restricted:
|
||||
account.telegramApplicationContext.applicationBindings.openSettings()
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
authorizeImpl = {
|
||||
let _ = (DeviceAccess.authorizationStatus(account: account, subject: .contacts)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { status in
|
||||
switch status {
|
||||
case .notDetermined:
|
||||
DeviceAccess.authorizeAccess(to: .contacts)
|
||||
case .denied, .restricted:
|
||||
account.telegramApplicationContext.applicationBindings.openSettings()
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
openPrivacyPolicyImpl = { [weak self] in
|
||||
@ -1133,8 +1133,8 @@ final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
})
|
||||
|
||||
self.listNode.isHidden = transition.isEmpty
|
||||
self.authorizationNode.isHidden = !transition.isEmpty
|
||||
self.listNode.isHidden = self.displayPermissionPlaceholder && 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.isHidden = true
|
||||
})
|
||||
videoAccessoryNode.isHidden = true
|
||||
self.videoAccessoryNode.isHidden = true
|
||||
self.resourceStatus = nil
|
||||
} else if let file = media as? TelegramMediaFile, file.isVideo {
|
||||
mediaDimensions = file.dimensions
|
||||
self.imageNode.setSignal(mediaGridMessageVideo(postbox: account.postbox, videoReference: .message(message: MessageReference(item.message), media: file)))
|
||||
|
||||
if let duration = file.duration {
|
||||
videoAccessoryNode.setup(stringForDuration(duration))
|
||||
videoAccessoryNode.isHidden = false
|
||||
self.videoAccessoryNode.setup(stringForDuration(duration))
|
||||
self.videoAccessoryNode.isHidden = false
|
||||
} else {
|
||||
videoAccessoryNode.isHidden = true
|
||||
self.videoAccessoryNode.isHidden = true
|
||||
}
|
||||
|
||||
|
||||
self.resourceStatus = nil
|
||||
self.fetchStatusDisposable.set((messageMediaFileStatus(account: account, messageId: messageId, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self {
|
||||
|
||||
@ -22,8 +22,9 @@ final class HorizontalPeerItem: ListViewItem {
|
||||
let longTapAction: (Peer) -> Void
|
||||
let isPeerSelected: (PeerId) -> Bool
|
||||
let customWidth: CGFloat?
|
||||
let presence: PeerPresence?
|
||||
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.strings = strings
|
||||
self.mode = mode
|
||||
@ -33,15 +34,14 @@ final class HorizontalPeerItem: ListViewItem {
|
||||
self.longTapAction = longTapAction
|
||||
self.isPeerSelected = isPeerSelected
|
||||
self.customWidth = customWidth
|
||||
self.presence = presence
|
||||
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) {
|
||||
async {
|
||||
let node = HorizontalPeerItemNode()
|
||||
|
||||
let (nodeLayout, apply) = node.asyncLayout()(self, params)
|
||||
|
||||
node.insets = nodeLayout.insets
|
||||
node.contentSize = nodeLayout.contentSize
|
||||
|
||||
@ -77,6 +77,7 @@ final class HorizontalPeerItemNode: ListViewItemNode {
|
||||
private(set) var peerNode: SelectablePeerNode
|
||||
let badgeBackgroundNode: ASImageNode
|
||||
let badgeTextNode: TextNode
|
||||
let statusNode: ASImageNode
|
||||
private(set) var item: HorizontalPeerItem?
|
||||
|
||||
init() {
|
||||
@ -86,16 +87,22 @@ final class HorizontalPeerItemNode: ListViewItemNode {
|
||||
self.badgeBackgroundNode.displaysAsynchronously = false
|
||||
self.badgeBackgroundNode.displayWithoutProcessing = true
|
||||
|
||||
|
||||
self.badgeTextNode = TextNode()
|
||||
self.badgeTextNode.isUserInteractionEnabled = false
|
||||
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)
|
||||
|
||||
self.addSubnode(self.peerNode)
|
||||
addSubnode(badgeBackgroundNode)
|
||||
addSubnode(badgeTextNode)
|
||||
self.addSubnode(self.badgeBackgroundNode)
|
||||
self.addSubnode(self.badgeTextNode)
|
||||
self.addSubnode(self.statusNode)
|
||||
self.peerNode.toggleSelection = { [weak self] in
|
||||
if let item = self?.item {
|
||||
item.action(item.peer)
|
||||
@ -106,7 +113,6 @@ final class HorizontalPeerItemNode: ListViewItemNode {
|
||||
item.longTapAction(item.peer)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -157,13 +163,28 @@ final class HorizontalPeerItemNode: ListViewItemNode {
|
||||
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()))
|
||||
|
||||
var badgeSize: CGFloat = 0.0
|
||||
if let currentBadgeBackgroundImage = currentBadgeBackgroundImage {
|
||||
badgeSize += max(currentBadgeBackgroundImage.size.width, badgeLayout.size.width + 10.0) + 5.0
|
||||
}
|
||||
|
||||
|
||||
return (itemLayout, { animated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
@ -189,6 +210,17 @@ final class HorizontalPeerItemNode: ListViewItemNode {
|
||||
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()
|
||||
}
|
||||
})
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
func legacyPasteMenu(account: Account, peer: Peer, saveEditedPhotos: Bool, allowGrouping: Bool, theme: PresentationTheme, strings: PresentationStrings, images: [UIImage], sendMessagesWithSignals: @escaping ([Any]?) -> Void) -> ViewController {
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: theme)
|
||||
legacyController.statusBar.statusBarStyle = .Hide
|
||||
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)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
legacyController.controllerLoaded = { [weak legacyController] in
|
||||
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
|
||||
if (peer is TelegramUser) && peer.id != account.peerId {
|
||||
hasTimer = true
|
||||
@ -159,20 +162,15 @@ func legacyPasteMenu(account: Account, peer: Peer, saveEditedPhotos: Bool, allow
|
||||
let recipientName = peer.displayTitle
|
||||
|
||||
legacyController.enableSizeClassSignal = true
|
||||
legacyController.presentationCompleted = { [weak legacyController, weak baseController] in
|
||||
if let strongLegacyController = legacyController, let baseController = baseController {
|
||||
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())
|
||||
sendMessagesWithSignals(signals)
|
||||
}, dismissed: {
|
||||
if let strongLegacyController = legacyController {
|
||||
strongLegacyController.dismiss()
|
||||
}
|
||||
}, sourceView: baseController.view, sourceRect: nil)
|
||||
controller?.customRemoveFromParentViewController = { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
let controller = TGClipboardMenu.present(inParentController: emptyController, context: legacyController.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())
|
||||
sendMessagesWithSignals(signals)
|
||||
}, dismissed: { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
}, sourceView: emptyController.view, sourceRect: nil)!
|
||||
controller.customRemoveFromParentViewController = { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
present(legacyController, nil)
|
||||
controller.present(in: emptyController, sourceView: nil, animated: true)
|
||||
|
||||
return legacyController
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ func guessMimeTypeByFileExtension(_ ext: String) -> String {
|
||||
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.inhibitDocumentCaptions = false
|
||||
controller.suggestionContext = legacySuggestionContext(account: account, peerId: peer.id)
|
||||
@ -21,11 +21,11 @@ func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, account:
|
||||
controller.hasTimer = true
|
||||
}
|
||||
controller.dismissalBlock = {
|
||||
|
||||
}
|
||||
controller.localMediaCacheEnabled = false
|
||||
controller.shouldStoreAssets = storeCreatedAssets
|
||||
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> {
|
||||
|
||||
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 layoutParams: ListViewItemLayoutParams?
|
||||
private var contentSizeValue: CGSize?
|
||||
private var currentLeftOffet: CGFloat = 0.0
|
||||
private var currentLeftOffset: CGFloat = 0.0
|
||||
|
||||
override var canBeLongTapped: Bool {
|
||||
return true
|
||||
@ -305,7 +305,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
updatedTheme = item.theme
|
||||
}
|
||||
|
||||
var leftInset: CGFloat = 60.0 + params.leftInset
|
||||
var leftInset: CGFloat = 65.0 + params.leftInset
|
||||
let rightInset: CGFloat = 8.0 + params.rightInset
|
||||
|
||||
var leftOffset: CGFloat = 0.0
|
||||
@ -483,7 +483,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
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
|
||||
if let strongSelf = self {
|
||||
@ -500,7 +500,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
strongSelf.appliedItem = item
|
||||
strongSelf.layoutParams = params
|
||||
strongSelf.contentSizeValue = nodeLayout.contentSize
|
||||
strongSelf.currentLeftOffet = leftOffset
|
||||
strongSelf.currentLeftOffset = leftOffset
|
||||
|
||||
if let _ = updatedTheme {
|
||||
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.linearProgressNode.backgroundColor = item.theme.list.itemAccentColor
|
||||
|
||||
}
|
||||
|
||||
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 iconFrame: CGRect
|
||||
@ -565,7 +564,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
}
|
||||
transition.updateFrame(node: strongSelf.extensionIconNode, frame: iconFrame)
|
||||
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()
|
||||
|
||||
@ -787,7 +786,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
|
||||
switch maybeFetchStatus {
|
||||
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 {
|
||||
self.addSubnode(self.linearProgressNode)
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import TelegramCore
|
||||
enum NativeVideoContentId: Hashable {
|
||||
case message(MessageId, UInt32, MediaId)
|
||||
case instantPage(MediaId, MediaId)
|
||||
case contextResult(Int64, String)
|
||||
|
||||
static func ==(lhs: NativeVideoContentId, rhs: NativeVideoContentId) -> Bool {
|
||||
switch lhs {
|
||||
@ -23,6 +24,12 @@ enum NativeVideoContentId: Hashable {
|
||||
} else {
|
||||
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
|
||||
case let .instantPage(pageId, mediaId):
|
||||
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 {
|
||||
galleryMedia = image
|
||||
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, !excludeWebPageMedia {
|
||||
if let file = content.file {
|
||||
galleryMedia = file
|
||||
} else if let image = content.image {
|
||||
galleryMedia = image
|
||||
if ["photo", "document", "video", "gif", "audio", "voice"].contains(content.type) {
|
||||
if let file = content.file {
|
||||
galleryMedia = file
|
||||
} else if let image = content.image {
|
||||
galleryMedia = image
|
||||
}
|
||||
}
|
||||
if let instantPage = content.instantPage, let galleryMedia = galleryMedia {
|
||||
switch instantPageType(of: content) {
|
||||
|
||||
@ -136,7 +136,7 @@ func openResolvedUrl(_ resolvedUrl: ResolvedUrl, account: Account, context: Open
|
||||
|
||||
let textInputState: ChatTextInputState
|
||||
if let text = text, !text.isEmpty {
|
||||
let urlString = NSMutableAttributedString(string: "\(text)\n")
|
||||
let urlString = NSMutableAttributedString(string: "\(url)\n")
|
||||
let textString = NSAttributedString(string: "\(text)")
|
||||
let selectionRange: Range<Int> = urlString.length ..< (urlString.length + textString.length)
|
||||
urlString.append(textString)
|
||||
|
||||
@ -115,6 +115,9 @@ public class PeerMediaCollectionController: TelegramController {
|
||||
}
|
||||
}, openPeerMention: { _ in
|
||||
}, openMessageContextMenu: { [weak self] message, _, _, _ in
|
||||
var messageIds = Set<MessageId>()
|
||||
messageIds.insert(message.id)
|
||||
|
||||
if let strongSelf = self, strongSelf.isNodeLoaded {
|
||||
if let message = strongSelf.mediaCollectionDisplayNode.messageForGallery(message.id)?.message {
|
||||
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 {
|
||||
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: [
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
|
||||
@ -244,63 +259,8 @@ public class PeerMediaCollectionController: TelegramController {
|
||||
}, setupEditMessage: { _ in
|
||||
}, beginMessageSelection: { _ in
|
||||
}, deleteSelectedMessages: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
||||
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))
|
||||
}
|
||||
}))
|
||||
}
|
||||
if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds {
|
||||
strongSelf.deleteMessages(messageIds)
|
||||
}
|
||||
}, reportSelectedMessages: { [weak self] in
|
||||
if let strongSelf = self, let messageIds = strongSelf.interfaceState.selectionState?.selectedIds, !messageIds.isEmpty {
|
||||
@ -313,37 +273,7 @@ public class PeerMediaCollectionController: TelegramController {
|
||||
}, forwardSelectedMessages: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if let forwardMessageIdsSet = strongSelf.interfaceState.selectionState?.selectedIds {
|
||||
let forwardMessageIds = Array(forwardMessageIdsSet).sorted()
|
||||
|
||||
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))
|
||||
strongSelf.forwardMessages(forwardMessageIdsSet)
|
||||
}
|
||||
}
|
||||
}, 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 topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
|
||||
private let closeButton: HighlightableButtonNode
|
||||
|
||||
let badgeNode: ASImageNode
|
||||
let labelNode: TextNode
|
||||
let titleNode: TextNode
|
||||
@ -140,6 +141,10 @@ class PermissionInfoItemNode: ListViewItemNode {
|
||||
|
||||
self.textNode = TextNode()
|
||||
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)
|
||||
|
||||
@ -147,6 +152,7 @@ class PermissionInfoItemNode: ListViewItemNode {
|
||||
self.addSubnode(self.labelNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.closeButton)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: PermissionInfoItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors?) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
@ -163,10 +169,13 @@ class PermissionInfoItemNode: ListViewItemNode {
|
||||
var updatedTheme: PresentationTheme?
|
||||
var updatedBadgeImage: UIImage?
|
||||
|
||||
var updatedCloseIcon: UIImage?
|
||||
|
||||
let badgeDiameter: CGFloat = 20.0
|
||||
if currentItem?.theme !== item.theme {
|
||||
updatedTheme = item.theme
|
||||
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: item.theme.list.itemDestructiveColor)
|
||||
updatedCloseIcon = PresentationResourcesItemList.itemListCloseIconImage(item.theme)
|
||||
}
|
||||
|
||||
let insets: UIEdgeInsets
|
||||
@ -256,6 +265,15 @@ class PermissionInfoItemNode: ListViewItemNode {
|
||||
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.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))
|
||||
@ -267,6 +285,10 @@ class PermissionInfoItemNode: ListViewItemNode {
|
||||
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.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.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))
|
||||
}
|
||||
|
||||
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) {
|
||||
let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
|
||||
|
||||
|
||||
@ -52,6 +52,8 @@ enum PresentationResourceKey: Int32 {
|
||||
|
||||
case itemListCloudFetchIcon
|
||||
|
||||
case itemListCloseIconImage
|
||||
|
||||
case chatListLockTopLockedImage
|
||||
case chatListLockBottomLockedImage
|
||||
case chatListLockTopUnlockedImage
|
||||
@ -66,6 +68,7 @@ enum PresentationResourceKey: Int32 {
|
||||
case chatListMutedIcon
|
||||
case chatListVerifiedIcon
|
||||
case chatListSecretIcon
|
||||
case chatListRecentStatusOnlineIcon
|
||||
|
||||
case chatTitleLockIcon
|
||||
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? {
|
||||
return theme.image(PresentationResourceKey.chatListBadgeBackgroundActive.rawValue, { theme in
|
||||
return generateBadgeBackgroundImage(theme: theme, active: true)
|
||||
|
||||
@ -135,4 +135,22 @@ struct PresentationResourcesItemList {
|
||||
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 AsyncDisplayKit
|
||||
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> {
|
||||
return requestChatContextResults(account: account, botId: botId, peerId: peerId, query: query, offset: offset)
|
||||
@ -30,15 +31,18 @@ final class WebSearchControllerInteraction {
|
||||
let setSearchQuery: (String) -> Void
|
||||
let deleteRecentQuery: (String) -> Void
|
||||
let toggleSelection: ([String], Bool) -> Void
|
||||
let sendSelected: (ChatContextResultCollection) -> Void
|
||||
let sendSelected: (ChatContextResultCollection, ChatContextResult?) -> Void
|
||||
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.setSearchQuery = setSearchQuery
|
||||
self.deleteRecentQuery = deleteRecentQuery
|
||||
self.toggleSelection = toggleSelection
|
||||
self.sendSelected = sendSelected
|
||||
self.editingContext = editingContext
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +73,7 @@ final class WebSearchController: ViewController {
|
||||
|
||||
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.chatLocation = chatLocation
|
||||
self.configuration = configuration
|
||||
@ -117,8 +121,15 @@ final class WebSearchController: ViewController {
|
||||
}
|
||||
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
|
||||
if let strongSelf = self {
|
||||
strongSelf.navigationContentNode?.setQuery(query)
|
||||
@ -133,11 +144,15 @@ final class WebSearchController: ViewController {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateInterfaceState { $0.withToggledSelectedMessages(ids, value: value) }
|
||||
}
|
||||
}, sendSelected: { [weak self] collection in
|
||||
if let strongSelf = self, let state = strongSelf.interfaceState.state, !state.selectionState.selectedIds.isEmpty {
|
||||
sendSelected(Array(state.selectionState.selectedIds), collection)
|
||||
}, sendSelected: { [weak self] collection, current in
|
||||
if let strongSelf = self, let state = strongSelf.interfaceState.state {
|
||||
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) {
|
||||
|
||||
@ -4,6 +4,7 @@ import Postbox
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import LegacyComponents
|
||||
|
||||
private struct WebSearchContextResultStableId: Hashable {
|
||||
let result: ChatContextResult
|
||||
@ -144,12 +145,17 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
private var hasMore = 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 disposable = MetaDisposable()
|
||||
private let loadMoreDisposable = MetaDisposable()
|
||||
|
||||
private let recentDisposable = MetaDisposable()
|
||||
private var recentDisposable: Disposable?
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
@ -219,7 +225,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
}))
|
||||
|
||||
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
|
||||
if let strongSelf = self {
|
||||
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)
|
||||
strongSelf.enqueueRecentTransition(transition, firstTime: previousEntries == nil)
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in
|
||||
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?.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 {
|
||||
self.disposable.dispose()
|
||||
self.recentDisposable?.dispose()
|
||||
self.loadMoreDisposable.dispose()
|
||||
self.hiddenMediaDisposable?.dispose()
|
||||
}
|
||||
|
||||
func updatePresentationData(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
@ -519,8 +541,79 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
|
||||
@objc private func sendPressed() {
|
||||
if let results = self.currentProcessedResults {
|
||||
self.controllerInteraction.sendSelected(results)
|
||||
self.controllerInteraction.sendSelected(results, nil)
|
||||
}
|
||||
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 imageResource: TelegramMediaResource?
|
||||
var stickerFile: TelegramMediaFile?
|
||||
var videoFile: TelegramMediaFile?
|
||||
var imageDimensions: CGSize?
|
||||
switch item.result {
|
||||
@ -147,7 +146,57 @@ final class WebSearchItemNode: GridItemNode {
|
||||
}
|
||||
|
||||
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
|
||||
@ -170,10 +219,9 @@ final class WebSearchItemNode: GridItemNode {
|
||||
strongSelf.updateSelectionState(animated: true)
|
||||
}
|
||||
})
|
||||
|
||||
selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||
self.addSubnode(selectionNode)
|
||||
self.selectionNode = selectionNode
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
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() {
|
||||
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.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
|
||||
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) {
|
||||
// guard let controllerInteraction = self.controllerInteraction, let message = self.item?.message else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// switch recognizer.state {
|
||||
// case .ended:
|
||||
// if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation {
|
||||
// switch gesture {
|
||||
// case .tap:
|
||||
// if let (account, media, _) = self.currentState {
|
||||
// if let file = media as? TelegramMediaFile {
|
||||
// if let resourceStatus = self.resourceStatus {
|
||||
// switch resourceStatus {
|
||||
// case .Fetching:
|
||||
// messageMediaFileCancelInteractiveFetch(account: account, messageId: message.id, file: file)
|
||||
// case .Local:
|
||||
// let _ = controllerInteraction.openMessage(message, .default)
|
||||
// case .Remote:
|
||||
// self.fetchDisposable.set(messageMediaFileInteractiveFetched(account: account, message: message, file: file, userInitiated: true).start())
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// let _ = controllerInteraction.openMessage(message, .default)
|
||||
// }
|
||||
// }
|
||||
// case .longTap:
|
||||
// controllerInteraction.openMessageContextMenu(message, false, self, self.bounds)
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// default:
|
||||
// break
|
||||
// }
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
item.controllerInteraction.openResult(item.result)
|
||||
|
||||
case .longTap:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user