mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-31 01:42:18 +00:00
Web Search improvements
This commit is contained in:
parent
a57e0b34af
commit
81e0345f89
@ -92,6 +92,12 @@
|
||||
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 */; };
|
||||
09F79A0121C8116C00820234 /* CountBadgeNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79A0021C8116C00820234 /* CountBadgeNode.swift */; };
|
||||
09F79A0321C8225600820234 /* WebSearchVideoGalleryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79A0221C8225600820234 /* WebSearchVideoGalleryItem.swift */; };
|
||||
09F79A0721C829BC00820234 /* GalleryNavigationCheckNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79A0621C829BC00820234 /* GalleryNavigationCheckNode.swift */; };
|
||||
09F79A0921C829C700820234 /* GalleryNavigationRecipientNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79A0821C829C700820234 /* GalleryNavigationRecipientNode.swift */; };
|
||||
09F79A0B21C832F400820234 /* WebSearchGalleryFooterContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79A0A21C832F400820234 /* WebSearchGalleryFooterContentNode.swift */; };
|
||||
09F79A0D21C88E8900820234 /* LegacyWebSearchEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F79A0C21C88E8900820234 /* LegacyWebSearchEditor.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 */; };
|
||||
@ -1186,6 +1192,12 @@
|
||||
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>"; };
|
||||
09F79A0021C8116C00820234 /* CountBadgeNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountBadgeNode.swift; sourceTree = "<group>"; };
|
||||
09F79A0221C8225600820234 /* WebSearchVideoGalleryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchVideoGalleryItem.swift; sourceTree = "<group>"; };
|
||||
09F79A0621C829BC00820234 /* GalleryNavigationCheckNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryNavigationCheckNode.swift; sourceTree = "<group>"; };
|
||||
09F79A0821C829C700820234 /* GalleryNavigationRecipientNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GalleryNavigationRecipientNode.swift; sourceTree = "<group>"; };
|
||||
09F79A0A21C832F400820234 /* WebSearchGalleryFooterContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSearchGalleryFooterContentNode.swift; sourceTree = "<group>"; };
|
||||
09F79A0C21C88E8900820234 /* LegacyWebSearchEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyWebSearchEditor.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>"; };
|
||||
@ -2373,6 +2385,10 @@
|
||||
09DD88F221BF907C000766BC /* WebSearchRecentQueryItem.swift */,
|
||||
09DD88F421BF9730000766BC /* WebSearchRecentQueries.swift */,
|
||||
09F799FB21C3FF3000820234 /* WebSearchGalleryController.swift */,
|
||||
09F79A0021C8116C00820234 /* CountBadgeNode.swift */,
|
||||
09F79A0621C829BC00820234 /* GalleryNavigationCheckNode.swift */,
|
||||
09F79A0821C829C700820234 /* GalleryNavigationRecipientNode.swift */,
|
||||
09F79A0A21C832F400820234 /* WebSearchGalleryFooterContentNode.swift */,
|
||||
);
|
||||
name = "Web Search";
|
||||
sourceTree = "<group>";
|
||||
@ -3185,6 +3201,7 @@
|
||||
D0380DAC204ED434000414AB /* LegacyLiveUploadInterface.swift */,
|
||||
D097C26B20DD1EA5007BB4B8 /* OverlayStatusController.swift */,
|
||||
09F799F921C3542D00820234 /* LegacyWebSearchGallery.swift */,
|
||||
09F79A0C21C88E8900820234 /* LegacyWebSearchEditor.swift */,
|
||||
);
|
||||
name = "Legacy Components";
|
||||
sourceTree = "<group>";
|
||||
@ -4516,6 +4533,7 @@
|
||||
D0104F291F471DA6004E4881 /* InstantImageGalleryItem.swift */,
|
||||
D0104F2B1F471EEB004E4881 /* InstantPageGalleryFooterContentNode.swift */,
|
||||
D0A8BBA01F61EE83000F03FD /* UniversalVideoGalleryItem.swift */,
|
||||
09F79A0221C8225600820234 /* WebSearchVideoGalleryItem.swift */,
|
||||
);
|
||||
name = Items;
|
||||
sourceTree = "<group>";
|
||||
@ -5034,6 +5052,7 @@
|
||||
D0B2F76820528E3D00D3BFB9 /* UserInfoEditingPhoneActionItem.swift in Sources */,
|
||||
D0EC6CB71EB9F58800EBF1C3 /* RMIntroPageView.m in Sources */,
|
||||
D0EC6CB81EB9F58800EBF1C3 /* RMIntroViewController.m in Sources */,
|
||||
09F79A0321C8225600820234 /* WebSearchVideoGalleryItem.swift in Sources */,
|
||||
D0EC6CB91EB9F58800EBF1C3 /* RMLoginViewController.m in Sources */,
|
||||
D0E9BA631F055AD200F079A4 /* BotPaymentCardInputItemNode.swift in Sources */,
|
||||
D01848E821A03BDA00B6DEBD /* ChatSearchState.swift in Sources */,
|
||||
@ -5221,6 +5240,7 @@
|
||||
D0EC6D131EB9F58800EBF1C3 /* MediaTrackDecodableFrame.swift in Sources */,
|
||||
D0EC6D141EB9F58800EBF1C3 /* MediaTrackFrame.swift in Sources */,
|
||||
D0B69C3920EBB397003632C7 /* ChatMessageInteractiveInstantVideoNode.swift in Sources */,
|
||||
09F79A0D21C88E8900820234 /* LegacyWebSearchEditor.swift in Sources */,
|
||||
D0EC6D151EB9F58800EBF1C3 /* MediaTrackFrameBuffer.swift in Sources */,
|
||||
D0EC6D161EB9F58800EBF1C3 /* MediaTrackFrameDecoder.swift in Sources */,
|
||||
D056CD701FF147B000880D28 /* IconButtonNode.swift in Sources */,
|
||||
@ -5301,6 +5321,7 @@
|
||||
D087BFB31F748752003FD209 /* ShareControllerRecentPeersGridItem.swift in Sources */,
|
||||
D0EC6D341EB9F58800EBF1C3 /* AvatarNode.swift in Sources */,
|
||||
D08D7E8420A0F6020005D80C /* ExperimentalUISettings.swift in Sources */,
|
||||
09F79A0921C829C700820234 /* GalleryNavigationRecipientNode.swift in Sources */,
|
||||
D0EC6D351EB9F58800EBF1C3 /* SearchBarNode.swift in Sources */,
|
||||
D0EC6D361EB9F58800EBF1C3 /* SearchBarPlaceholderNode.swift in Sources */,
|
||||
D0E8B8B9204477B600605593 /* SecretChatKeyVisualization.swift in Sources */,
|
||||
@ -5638,6 +5659,7 @@
|
||||
D0F0AAE61EC21B68005EE2A5 /* CallControllerButton.swift in Sources */,
|
||||
D0EC6DDE1EB9F58900EBF1C3 /* ChatTextInputAudioRecordingOverlayButton.swift in Sources */,
|
||||
D0E9BAC91F05738600F079A4 /* STPAPIClient+ApplePay.m in Sources */,
|
||||
09F79A0721C829BC00820234 /* GalleryNavigationCheckNode.swift in Sources */,
|
||||
D0EC6DDF1EB9F58900EBF1C3 /* ChatTextInputAudioRecordingTimeNode.swift in Sources */,
|
||||
D0BFAE5B20AB35D200793CF2 /* IconSwitchNode.swift in Sources */,
|
||||
D0EC6DE01EB9F58900EBF1C3 /* ChatTextInputAudioRecordingCancelIndicator.swift in Sources */,
|
||||
@ -5842,6 +5864,7 @@
|
||||
D09250061FE5371D003F693F /* GlobalExperimentalSettings.swift in Sources */,
|
||||
D0A24D281F92C27100584D24 /* DefaultDarkAccentPresentationTheme.swift in Sources */,
|
||||
D025A4231F79344500563950 /* FetchManager.swift in Sources */,
|
||||
09F79A0121C8116C00820234 /* CountBadgeNode.swift in Sources */,
|
||||
D0CB27CF20C17A4A001ACF93 /* TermsOfServiceController.swift in Sources */,
|
||||
D00BDA1F1EE5B69200C64C5E /* ChannelAdminController.swift in Sources */,
|
||||
D0EC6E501EB9F58900EBF1C3 /* ChannelAdminsController.swift in Sources */,
|
||||
@ -5897,6 +5920,7 @@
|
||||
D0EC6E6E1EB9F58900EBF1C3 /* ArchivedStickerPacksController.swift in Sources */,
|
||||
D0DE5805205B202500C356A8 /* ScreenCaptureDetection.swift in Sources */,
|
||||
D0EC6E711EB9F58900EBF1C3 /* ThemeGalleryController.swift in Sources */,
|
||||
09F79A0B21C832F400820234 /* WebSearchGalleryFooterContentNode.swift in Sources */,
|
||||
D0C0B5B11EE1C421000F4D2C /* ChatDateSelectionSheet.swift in Sources */,
|
||||
D0EC6E721EB9F58900EBF1C3 /* ThemeGalleryItem.swift in Sources */,
|
||||
D00781052084DFB100369A39 /* UrlEscaping.swift in Sources */,
|
||||
|
@ -201,6 +201,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public func setPeer(account: Account, peer: Peer, authorOfMessage: MessageReference? = nil, overrideImage: AvatarNodeImageOverride? = nil, emptyColor: UIColor? = nil, synchronousLoad: Bool = false) {
|
||||
var synchronousLoad = synchronousLoad
|
||||
var representation: TelegramMediaImageRepresentation?
|
||||
var icon = AvatarNodeIcon.none
|
||||
if let overrideImage = overrideImage {
|
||||
@ -209,6 +210,7 @@ public final class AvatarNode: ASDisplayNode {
|
||||
representation = nil
|
||||
case let .image(image):
|
||||
representation = image
|
||||
synchronousLoad = false
|
||||
case .savedMessagesIcon:
|
||||
representation = nil
|
||||
icon = .savedMessagesIcon
|
||||
|
@ -628,9 +628,9 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr
|
||||
}))
|
||||
})
|
||||
}, changeProfilePhoto: {
|
||||
let _ = (account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(peerId)
|
||||
} |> deliverOnMainQueue).start(next: { peer in
|
||||
let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
|
||||
return (transaction.getPeer(peerId), currentSearchBotsConfiguration(transaction: transaction))
|
||||
} |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
@ -650,28 +650,38 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr
|
||||
hasPhotos = true
|
||||
}
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)!
|
||||
let completedImpl: (UIImage) -> Void = { image in
|
||||
if let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(.image(representation, true))
|
||||
}
|
||||
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .complete:
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(nil)
|
||||
}
|
||||
case .progress:
|
||||
break
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
|
||||
let _ = currentAvatarMixin.swap(mixin)
|
||||
mixin.requestSearchController = { _ in
|
||||
let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
|
||||
completedImpl(result)
|
||||
}))
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
mixin.didFinishWithImage = { image in
|
||||
if let image = image {
|
||||
if let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(.image(representation, true))
|
||||
}
|
||||
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .complete:
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(nil)
|
||||
}
|
||||
case .progress:
|
||||
break
|
||||
}
|
||||
}))
|
||||
}
|
||||
completedImpl(image)
|
||||
}
|
||||
}
|
||||
mixin.didFinishWithDelete = {
|
||||
|
@ -132,7 +132,6 @@ final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
|
||||
var updatedBackgroundImage: UIImage?
|
||||
if currentTheme != item.presentationData.theme {
|
||||
//let principalGraphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
|
||||
updatedBackgroundImage = PresentationResourcesChat.chatInfoItemBackgroundImage(item.presentationData.theme.theme, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
|
||||
}
|
||||
|
||||
@ -181,9 +180,9 @@ final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
|
||||
override func updateTrailingItemSpace(_ height: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if height.isLessThanOrEqualTo(0.0) {
|
||||
transition.updateBounds(node: self.offsetContainer, bounds: CGRect(origin: CGPoint(), size: self.offsetContainer.bounds.size))
|
||||
transition.updateFrame(node: self.offsetContainer, frame: CGRect(origin: CGPoint(), size: self.offsetContainer.bounds.size))
|
||||
} else {
|
||||
transition.updateBounds(node: self.offsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: floor(height) / 2.0), size: self.offsetContainer.bounds.size))
|
||||
transition.updateFrame(node: self.offsetContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: -floorToScreenPixels(height / 2.0)), size: self.offsetContainer.bounds.size))
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,12 +198,18 @@ final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
let result = super.point(inside: point, with: event)
|
||||
let extra = self.offsetContainer.frame.contains(point)
|
||||
return result || extra
|
||||
}
|
||||
|
||||
func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||
if let item = self.item {
|
||||
var rects: [CGRect]?
|
||||
if let point = point {
|
||||
let textNodeFrame = self.textNode.frame
|
||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - self.offsetContainer.frame.minX - textNodeFrame.minX, y: point.y - self.offsetContainer.frame.minY - textNodeFrame.minY)) {
|
||||
let possibleNames: [String] = [
|
||||
TelegramTextAttributes.URL,
|
||||
TelegramTextAttributes.PeerMention,
|
||||
@ -228,7 +233,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
} else {
|
||||
linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.theme.chat.bubble.incomingLinkHighlightColor)
|
||||
self.linkHighlightingNode = linkHighlightingNode
|
||||
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
||||
self.offsetContainer.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
||||
}
|
||||
linkHighlightingNode.frame = self.textNode.frame
|
||||
linkHighlightingNode.updateRects(rects)
|
||||
@ -243,7 +248,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
|
||||
|
||||
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 (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - self.offsetContainer.frame.minX - textNodeFrame.minX, y: point.y - self.offsetContainer.frame.minY - textNodeFrame.minY)) {
|
||||
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
var concealed = true
|
||||
if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {
|
||||
|
@ -3816,9 +3816,9 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
|
||||
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
|
||||
let controller = WebSearchController(account: strongSelf.account, peer: peer, configuration: searchBotsConfiguration, mode: .media(completion: { results, editingContext in
|
||||
|
||||
})
|
||||
}))
|
||||
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
})
|
||||
@ -3842,7 +3842,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
}
|
||||
|
||||
private func presentWebSearch(editingMessage: Bool) {
|
||||
guard let _ = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -3855,48 +3855,43 @@ 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, editingContext in
|
||||
let controller = WebSearchController(account: strongSelf.account, peer: peer, configuration: configuration, mode: .media(completion: { [weak self] selectionState, editingState 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
|
||||
}
|
||||
for item in selectionState.selectedItems() {
|
||||
if let item = item as? LegacyWebSearchItem {
|
||||
results.append(item.result)
|
||||
}
|
||||
}
|
||||
|
||||
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 editableItem = LegacyWebSearchItem(result: result)
|
||||
if editingState.adjustments(for: editableItem) != nil {
|
||||
if let imageSignal = editingState.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)
|
||||
return legacyAssetPickerItemGenerator()(dict, nil, nil, nil) as Any
|
||||
} else {
|
||||
return SSignal.complete()
|
||||
}
|
||||
}
|
||||
signals.append(signal)
|
||||
signals.append(signal as Any)
|
||||
}
|
||||
} else {
|
||||
strongSelf.enqueueChatContextResult(collection, result, includeViaBot: false)
|
||||
strongSelf.enqueueChatContextResult(nil, result)
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.enqueueMediaMessages(signals: signals)
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
})
|
||||
@ -4177,11 +4172,11 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
|
||||
}))
|
||||
}
|
||||
|
||||
private func enqueueChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult, includeViaBot: Bool = true) {
|
||||
private func enqueueChatContextResult(_ results: ChatContextResultCollection?, _ result: ChatContextResult) {
|
||||
guard case let .peer(peerId) = self.chatLocation else {
|
||||
return
|
||||
}
|
||||
if let message = outgoingMessageWithChatContextResult(to: peerId, results: results, result: result, includeViaBot: includeViaBot), canSendMessagesToChat(self.presentationInterfaceState) {
|
||||
if let message = outgoingMessageWithChatContextResult(to: peerId, results: results, result: result), canSendMessagesToChat(self.presentationInterfaceState) {
|
||||
let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId
|
||||
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
|
||||
if let strongSelf = self {
|
||||
|
@ -226,7 +226,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
mediaAndFlags = (file, [])
|
||||
}
|
||||
} else if let image = mainMedia as? TelegramMediaImage {
|
||||
if let type = webpage.type, ["photo", "video", "embed", "gif", "telegram_album"].contains(type) {
|
||||
if let type = webpage.type, ["photo", "video", "embed", "gif", "document", "telegram_album"].contains(type) {
|
||||
var flags = ChatMessageAttachedContentNodeMediaFlags()
|
||||
if webpage.instantPage != nil, let largest = largestImageRepresentation(image.representations) {
|
||||
if largest.dimensions.width >= 256.0 {
|
||||
|
@ -240,8 +240,6 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
|
||||
|
||||
self.disablesInteractiveTransitionGestureRecognizer = true
|
||||
|
||||
let inputPanelTheme = theme.chat.inputPanel
|
||||
|
||||
self.pallete = legacyInputMicPalette(from: theme)
|
||||
|
||||
self.insertSubview(self.innerIconView, at: 0)
|
||||
@ -348,7 +346,7 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
|
||||
func micButtonInteractionCancelled(_ velocity: CGPoint) {
|
||||
//print("\(CFAbsoluteTimeGetCurrent()) cancelled")
|
||||
self.modeTimeoutTimer?.invalidate()
|
||||
self.stopRecording()
|
||||
self.endRecording(false)
|
||||
}
|
||||
|
||||
func micButtonInteractionCompleted(_ velocity: CGPoint) {
|
||||
|
@ -100,7 +100,21 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
|
||||
var inputActivities: (PeerId, [(Peer, PeerInputActivity)])? {
|
||||
didSet {
|
||||
if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty {
|
||||
var inputActivitiesAllowed = true
|
||||
if let titleContent = self.titleContent {
|
||||
switch titleContent {
|
||||
case let .peer(peerView, _):
|
||||
if let peer = peerViewMainPeer(peerView) {
|
||||
if peer.id == self.account.peerId {
|
||||
inputActivitiesAllowed = false
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty, inputActivitiesAllowed {
|
||||
self.typingNode.isHidden = false
|
||||
self.infoNode.isHidden = true
|
||||
var stringValue = ""
|
||||
|
@ -6,6 +6,7 @@ import LegacyComponents
|
||||
enum CheckNodeStyle {
|
||||
case plain
|
||||
case overlay
|
||||
case navigation
|
||||
}
|
||||
|
||||
final class CheckNode: ASDisplayNode {
|
||||
@ -18,6 +19,9 @@ final class CheckNode: ASDisplayNode {
|
||||
|
||||
private(set) var isChecked: Bool = false
|
||||
|
||||
private weak var target: AnyObject?
|
||||
private var action: Selector?
|
||||
|
||||
init(strokeColor: UIColor, fillColor: UIColor, foregroundColor: UIColor, style: CheckNodeStyle) {
|
||||
self.strokeColor = strokeColor
|
||||
self.fillColor = fillColor
|
||||
@ -31,19 +35,29 @@ final class CheckNode: ASDisplayNode {
|
||||
super.didLoad()
|
||||
|
||||
let style: TGCheckButtonStyle
|
||||
let checkSize: CGSize
|
||||
switch self.checkStyle {
|
||||
case .plain:
|
||||
style = TGCheckButtonStyleDefault
|
||||
checkSize = CGSize(width: 32.0, height: 32.0)
|
||||
case .overlay:
|
||||
style = TGCheckButtonStyleMedia
|
||||
checkSize = CGSize(width: 32.0, height: 32.0)
|
||||
case .navigation:
|
||||
style = TGCheckButtonStyleGallery
|
||||
checkSize = CGSize(width: 39.0, height: 39.0)
|
||||
}
|
||||
let checkView = TGCheckButtonView(style: style, pallete: TGCheckButtonPallete(defaultBackgroundColor: self.fillColor, accentBackgroundColor: self.fillColor, defaultBorderColor: self.strokeColor, mediaBorderColor: self.strokeColor, chatBorderColor: self.strokeColor, check: self.foregroundColor, blueColor: self.fillColor, barBackgroundColor: self.fillColor))!
|
||||
checkView.setSelected(true, animated: false)
|
||||
checkView.layoutSubviews()
|
||||
checkView.setSelected(self.isChecked, animated: false)
|
||||
if let target = self.target, let action = self.action {
|
||||
checkView.addTarget(target, action: action, for: .touchUpInside)
|
||||
}
|
||||
self.checkView = checkView
|
||||
self.view.addSubview(checkView)
|
||||
checkView.frame = self.bounds
|
||||
|
||||
checkView.frame = CGRect(origin: CGPoint(), size: checkSize)
|
||||
}
|
||||
|
||||
func setIsChecked(_ isChecked: Bool, animated: Bool) {
|
||||
@ -52,4 +66,12 @@ final class CheckNode: ASDisplayNode {
|
||||
self.checkView?.setSelected(isChecked, animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
func addTarget(target: AnyObject?, action: Selector) {
|
||||
self.target = target
|
||||
self.action = action
|
||||
if self.isNodeLoaded {
|
||||
self.checkView?.addTarget(target, action: action, for: .touchUpInside)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,18 +43,18 @@ final class ContactsControllerNode: ASDisplayNode {
|
||||
self.addSubnode(self.contactListNode)
|
||||
|
||||
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
inviteImpl = { [weak self] in
|
||||
let _ = (DeviceAccess.contacts
|
||||
|
@ -791,7 +791,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
if let verificationIconNode = self.verificationIconNode {
|
||||
var iconFrame = verificationIconNode.frame
|
||||
iconFrame.origin.x = offset + titleFrame.maxX + 3.0
|
||||
iconFrame.origin.x = titleFrame.maxX + 3.0
|
||||
transition.updateFrame(node: verificationIconNode, frame: iconFrame)
|
||||
}
|
||||
|
||||
|
7
TelegramUI/CountBadgeNode.swift
Normal file
7
TelegramUI/CountBadgeNode.swift
Normal file
@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
class CountBadgeNode: ASDisplayNode {
|
||||
|
||||
}
|
@ -258,56 +258,72 @@ public func createChannelController(account: Account) -> ViewController {
|
||||
}))
|
||||
}
|
||||
}, changeProfilePhoto: {
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
|
||||
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
endEditingImpl?()
|
||||
presentControllerImpl?(legacyController, nil)
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)!
|
||||
let _ = currentAvatarMixin.swap(mixin)
|
||||
mixin.didFinishWithImage = { image in
|
||||
if let image = image, let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
uploadedAvatar.set(uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource))
|
||||
updateState { current in
|
||||
var current = current
|
||||
current.avatar = .image(representation, false)
|
||||
return current
|
||||
let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
|
||||
return (transaction.getPeer(account.peerId), currentSearchBotsConfiguration(transaction: transaction))
|
||||
} |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
|
||||
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
endEditingImpl?()
|
||||
presentControllerImpl?(legacyController, nil)
|
||||
|
||||
let completedImpl: (UIImage) -> Void = { image in
|
||||
if let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
uploadedAvatar.set(uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource))
|
||||
updateState { current in
|
||||
var current = current
|
||||
current.avatar = .image(representation, false)
|
||||
return current
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if stateValue.with({ $0.avatar }) != nil {
|
||||
mixin.didFinishWithDelete = {
|
||||
updateState { current in
|
||||
var current = current
|
||||
current.avatar = nil
|
||||
return current
|
||||
}
|
||||
uploadedAvatar.set(.never())
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
|
||||
let _ = currentAvatarMixin.swap(mixin)
|
||||
mixin.requestSearchController = { _ in
|
||||
let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
|
||||
completedImpl(result)
|
||||
}))
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
}
|
||||
mixin.didDismiss = { [weak legacyController] in
|
||||
let _ = currentAvatarMixin.swap(nil)
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
let menuController = mixin.present()
|
||||
if let menuController = menuController {
|
||||
menuController.customRemoveFromParentViewController = { [weak legacyController] in
|
||||
mixin.didFinishWithImage = { image in
|
||||
if let image = image {
|
||||
completedImpl(image)
|
||||
}
|
||||
}
|
||||
if stateValue.with({ $0.avatar }) != nil {
|
||||
mixin.didFinishWithDelete = {
|
||||
updateState { current in
|
||||
var current = current
|
||||
current.avatar = nil
|
||||
return current
|
||||
}
|
||||
uploadedAvatar.set(.never())
|
||||
}
|
||||
}
|
||||
mixin.didDismiss = { [weak legacyController] in
|
||||
let _ = currentAvatarMixin.swap(nil)
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
}
|
||||
let menuController = mixin.present()
|
||||
if let menuController = menuController {
|
||||
menuController.customRemoveFromParentViewController = { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get())
|
||||
|
@ -277,56 +277,72 @@ public func createGroupController(account: Account, peerIds: [PeerId]) -> ViewCo
|
||||
}))
|
||||
}
|
||||
}, changeProfilePhoto: {
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
|
||||
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
endEditingImpl?()
|
||||
presentControllerImpl?(legacyController, nil)
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)!
|
||||
let _ = currentAvatarMixin.swap(mixin)
|
||||
mixin.didFinishWithImage = { image in
|
||||
if let image = image, let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
uploadedAvatar.set(uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource))
|
||||
updateState { current in
|
||||
var current = current
|
||||
current.avatar = .image(representation, false)
|
||||
return current
|
||||
let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
|
||||
return (transaction.getPeer(account.peerId), currentSearchBotsConfiguration(transaction: transaction))
|
||||
} |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
|
||||
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
endEditingImpl?()
|
||||
presentControllerImpl?(legacyController, nil)
|
||||
|
||||
let completedImpl: (UIImage) -> Void = { image in
|
||||
if let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
uploadedAvatar.set(uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource))
|
||||
updateState { current in
|
||||
var current = current
|
||||
current.avatar = .image(representation, false)
|
||||
return current
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if stateValue.with({ $0.avatar }) != nil {
|
||||
mixin.didFinishWithDelete = {
|
||||
updateState { current in
|
||||
var current = current
|
||||
current.avatar = nil
|
||||
return current
|
||||
}
|
||||
uploadedAvatar.set(.never())
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, hasViewButton: false, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
|
||||
let _ = currentAvatarMixin.swap(mixin)
|
||||
mixin.requestSearchController = { _ in
|
||||
let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
|
||||
completedImpl(result)
|
||||
}))
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
}
|
||||
mixin.didDismiss = { [weak legacyController] in
|
||||
let _ = currentAvatarMixin.swap(nil)
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
let menuController = mixin.present()
|
||||
if let menuController = menuController {
|
||||
menuController.customRemoveFromParentViewController = { [weak legacyController] in
|
||||
mixin.didFinishWithImage = { image in
|
||||
if let image = image {
|
||||
completedImpl(image)
|
||||
}
|
||||
}
|
||||
if stateValue.with({ $0.avatar }) != nil {
|
||||
mixin.didFinishWithDelete = {
|
||||
updateState { current in
|
||||
var current = current
|
||||
current.avatar = nil
|
||||
return current
|
||||
}
|
||||
uploadedAvatar.set(.never())
|
||||
}
|
||||
}
|
||||
mixin.didDismiss = { [weak legacyController] in
|
||||
let _ = currentAvatarMixin.swap(nil)
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
}
|
||||
let menuController = mixin.present()
|
||||
if let menuController = menuController {
|
||||
menuController.customRemoveFromParentViewController = { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), account.postbox.multiplePeersView(peerIds))
|
||||
|
@ -171,15 +171,15 @@ private let bubble = PresentationThemeChatBubble(
|
||||
incomingFileDurationColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5),
|
||||
outgoingFileDurationColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5),
|
||||
shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5)),
|
||||
shareButtonStrokeColor: UIColor(rgb: 0x213040),
|
||||
shareButtonForegroundColor: UIColor(rgb: 0xb2b2b2), //!!!
|
||||
shareButtonStrokeColor: UIColor(rgb: 0x587fa3, alpha: 0.15),
|
||||
shareButtonForegroundColor: UIColor(rgb: 0xb2b2b2),
|
||||
mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), //!!!
|
||||
mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), //!!!
|
||||
actionButtonsIncomingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5)),
|
||||
actionButtonsIncomingStrokeColor: UIColor(rgb: 0x213040),
|
||||
actionButtonsIncomingStrokeColor: UIColor(rgb: 0x587fa3, alpha: 0.15),
|
||||
actionButtonsIncomingTextColor: UIColor(rgb: 0xffffff),
|
||||
actionButtonsOutgoingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5)),
|
||||
actionButtonsOutgoingStrokeColor: UIColor(rgb: 0x213040),
|
||||
actionButtonsOutgoingStrokeColor: UIColor(rgb: 0x587fa3, alpha: 0.15),
|
||||
actionButtonsOutgoingTextColor: UIColor(rgb: 0xffffff),
|
||||
selectionControlBorderColor: .white,
|
||||
selectionControlFillColor: accentColor,
|
||||
|
@ -171,15 +171,15 @@ private let bubble = PresentationThemeChatBubble(
|
||||
incomingFileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5),
|
||||
outgoingFileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5),
|
||||
shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)),
|
||||
shareButtonStrokeColor: UIColor(rgb: 0x1f1f1f),
|
||||
shareButtonStrokeColor: UIColor(rgb: 0xb2b2b2, alpha: 0.18),
|
||||
shareButtonForegroundColor: UIColor(rgb: 0xb2b2b2), //!!!
|
||||
mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), //!!!
|
||||
mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), //!!!
|
||||
actionButtonsIncomingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)),
|
||||
actionButtonsIncomingStrokeColor: UIColor(rgb: 0x1f1f1f),
|
||||
actionButtonsIncomingStrokeColor: UIColor(rgb: 0xb2b2b2, alpha: 0.18),
|
||||
actionButtonsIncomingTextColor: UIColor(rgb: 0xffffff),
|
||||
actionButtonsOutgoingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)),
|
||||
actionButtonsOutgoingStrokeColor: UIColor(rgb: 0x1f1f1f),
|
||||
actionButtonsOutgoingStrokeColor: UIColor(rgb: 0xb2b2b2, alpha: 0.18),
|
||||
actionButtonsOutgoingTextColor: UIColor(rgb: 0xffffff),
|
||||
selectionControlBorderColor: .white,
|
||||
selectionControlFillColor: accentColor,
|
||||
|
@ -420,64 +420,39 @@ func editSettingsController(account: Account, currentName: ItemListAvatarAndName
|
||||
}
|
||||
}
|
||||
changeProfilePhotoImpl = { [weak controller] in
|
||||
let _ = (account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(account.peerId)
|
||||
} |> deliverOnMainQueue).start(next: { peer in
|
||||
controller?.view.endEditing(true)
|
||||
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
|
||||
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
presentControllerImpl?(legacyController, nil)
|
||||
|
||||
var hasPhotos = false
|
||||
if let peer = peer, !peer.profileImageRepresentations.isEmpty {
|
||||
hasPhotos = true
|
||||
}
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, hasViewButton: hasPhotos, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
|
||||
let _ = currentAvatarMixin.swap(mixin)
|
||||
mixin.didFinishWithImage = { image in
|
||||
if let image = image {
|
||||
if let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(.image(representation, true))
|
||||
}
|
||||
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: resource) |> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .complete:
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(nil)
|
||||
}
|
||||
case .progress:
|
||||
break
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
mixin.didFinishWithDelete = {
|
||||
let _ = currentAvatarMixin.swap(nil)
|
||||
let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
|
||||
return (transaction.getPeer(account.peerId), currentSearchBotsConfiguration(transaction: transaction))
|
||||
} |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
|
||||
controller?.view.endEditing(true)
|
||||
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
|
||||
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
presentControllerImpl?(legacyController, nil)
|
||||
|
||||
var hasPhotos = false
|
||||
if let peer = peer, !peer.profileImageRepresentations.isEmpty {
|
||||
hasPhotos = true
|
||||
}
|
||||
|
||||
let completedImpl: (UIImage) -> Void = { image in
|
||||
if let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
updateState {
|
||||
if let profileImage = peer?.smallProfileImage {
|
||||
return $0.withUpdatedUpdatingAvatar(.image(profileImage, false))
|
||||
} else {
|
||||
return $0.withUpdatedUpdatingAvatar(.none)
|
||||
}
|
||||
$0.withUpdatedUpdatingAvatar(.image(representation, true))
|
||||
}
|
||||
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: nil) |> deliverOnMainQueue).start(next: { result in
|
||||
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: resource) |> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .complete:
|
||||
updateState {
|
||||
@ -488,38 +463,73 @@ func editSettingsController(account: Account, currentName: ItemListAvatarAndName
|
||||
}
|
||||
}))
|
||||
}
|
||||
mixin.didFinishWithView = {
|
||||
let _ = currentAvatarMixin.swap(nil)
|
||||
|
||||
let _ = (account.postbox.loadedPeerWithId(account.peerId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if peer.smallProfileImage != nil {
|
||||
let galleryController = AvatarGalleryController(account: account, peer: peer, replaceRootController: { controller, ready in
|
||||
})
|
||||
/*hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in
|
||||
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first
|
||||
updateHiddenAvatarImpl?()
|
||||
}))*/
|
||||
presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in
|
||||
return nil
|
||||
}))
|
||||
} else {
|
||||
changeProfilePhotoImpl?()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: hasPhotos, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
|
||||
let _ = currentAvatarMixin.swap(mixin)
|
||||
mixin.requestSearchController = { _ in
|
||||
let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
|
||||
completedImpl(result)
|
||||
}))
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
mixin.didFinishWithImage = { image in
|
||||
if let image = image {
|
||||
completedImpl(image)
|
||||
}
|
||||
mixin.didDismiss = { [weak legacyController] in
|
||||
let _ = currentAvatarMixin.swap(nil)
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
let menuController = mixin.present()
|
||||
if let menuController = menuController {
|
||||
menuController.customRemoveFromParentViewController = { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
mixin.didFinishWithDelete = {
|
||||
let _ = currentAvatarMixin.swap(nil)
|
||||
updateState {
|
||||
if let profileImage = peer?.smallProfileImage {
|
||||
return $0.withUpdatedUpdatingAvatar(.image(profileImage, false))
|
||||
} else {
|
||||
return $0.withUpdatedUpdatingAvatar(.none)
|
||||
}
|
||||
}
|
||||
})
|
||||
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: nil) |> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .complete:
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(nil)
|
||||
}
|
||||
case .progress:
|
||||
break
|
||||
}
|
||||
}))
|
||||
}
|
||||
mixin.didFinishWithView = {
|
||||
let _ = currentAvatarMixin.swap(nil)
|
||||
|
||||
let _ = (account.postbox.loadedPeerWithId(account.peerId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if peer.smallProfileImage != nil {
|
||||
let galleryController = AvatarGalleryController(account: account, peer: peer, replaceRootController: { controller, ready in
|
||||
})
|
||||
/*hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in
|
||||
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first
|
||||
updateHiddenAvatarImpl?()
|
||||
}))*/
|
||||
presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in
|
||||
return nil
|
||||
}))
|
||||
} else {
|
||||
changeProfilePhotoImpl?()
|
||||
}
|
||||
})
|
||||
}
|
||||
mixin.didDismiss = { [weak legacyController] in
|
||||
let _ = currentAvatarMixin.swap(nil)
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
let menuController = mixin.present()
|
||||
if let menuController = menuController {
|
||||
menuController.customRemoveFromParentViewController = { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return controller
|
||||
|
27
TelegramUI/GalleryNavigationCheckNode.swift
Normal file
27
TelegramUI/GalleryNavigationCheckNode.swift
Normal file
@ -0,0 +1,27 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
|
||||
final class GalleryNavigationCheckNode: ASDisplayNode {
|
||||
private var checkNode: CheckNode
|
||||
|
||||
init(theme: PresentationTheme) {
|
||||
self.checkNode = CheckNode(strokeColor: theme.list.itemCheckColors.strokeColor, fillColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor, style: .navigation)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.checkNode)
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: 39.0, height: 39.0)
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
let size = self.bounds.size
|
||||
let checkSize = CGSize(width: 39.0, height: 39.0)
|
||||
self.checkNode.frame = CGRect(origin: CGPoint(x: floor((size.width - checkSize.width) / 2.0) + 11.0, y: floor((size.height - checkSize.height) / 2.0) + 3.0), size: checkSize)
|
||||
}
|
||||
}
|
37
TelegramUI/GalleryNavigationRecipientNode.swift
Normal file
37
TelegramUI/GalleryNavigationRecipientNode.swift
Normal file
@ -0,0 +1,37 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import LegacyComponents
|
||||
|
||||
final class GalleryNavigationRecipientNode: ASDisplayNode {
|
||||
private var iconNode: ASImageNode
|
||||
private var textNode: ASTextNode
|
||||
|
||||
init(color: UIColor, title: String) {
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.alpha = 0.45
|
||||
self.iconNode.image = TGComponentsImageNamed("PhotoPickerArrow")
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.attributedText = NSAttributedString(string: title, font: Font.bold(13.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.45))
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: 30.0, height: 30.0)
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
if let image = self.iconNode.image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: -2.0, y: 9.0), size: image.size)
|
||||
}
|
||||
|
||||
self.textNode.frame = CGRect(x: self.iconNode.frame.maxX + 6.0, y: 7.0, width: 150.0, height: 20.0)
|
||||
}
|
||||
}
|
@ -1203,9 +1203,9 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
||||
}))
|
||||
})
|
||||
}, changeProfilePhoto: {
|
||||
let _ = (account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(peerId)
|
||||
} |> deliverOnMainQueue).start(next: { peer in
|
||||
let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
|
||||
return (transaction.getPeer(peerId), currentSearchBotsConfiguration(transaction: transaction))
|
||||
} |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
@ -1225,28 +1225,38 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
||||
hasPhotos = true
|
||||
}
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)!
|
||||
let completedImpl: (UIImage) -> Void = { image in
|
||||
if let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(.image(representation, true))
|
||||
}
|
||||
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .complete:
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(nil)
|
||||
}
|
||||
case .progress:
|
||||
break
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
|
||||
let _ = currentAvatarMixin.swap(mixin)
|
||||
mixin.requestSearchController = { _ in
|
||||
let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
|
||||
completedImpl(result)
|
||||
}))
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
mixin.didFinishWithImage = { image in
|
||||
if let image = image {
|
||||
if let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(.image(representation, true))
|
||||
}
|
||||
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .complete:
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(nil)
|
||||
}
|
||||
case .progress:
|
||||
break
|
||||
}
|
||||
}))
|
||||
}
|
||||
completedImpl(image)
|
||||
}
|
||||
}
|
||||
mixin.didFinishWithDelete = {
|
||||
@ -1361,9 +1371,25 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
|
||||
var options: [ContactListAdditionalOption] = []
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
var inviteByLinkImpl: (() -> Void)?
|
||||
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
|
||||
inviteByLinkImpl?()
|
||||
}))
|
||||
|
||||
var canCreateInviteLink = false
|
||||
if let group = groupPeer as? TelegramGroup {
|
||||
if case .creator = group.role {
|
||||
canCreateInviteLink = true
|
||||
}
|
||||
} else if let channel = groupPeer as? TelegramChannel {
|
||||
if channel.hasAdminRights(.canInviteUsers) {
|
||||
canCreateInviteLink = true
|
||||
} else if case let .group(info) = channel.info, info.flags.contains(.everyMemberCanInviteMembers) {
|
||||
canCreateInviteLink = true
|
||||
}
|
||||
}
|
||||
|
||||
if canCreateInviteLink {
|
||||
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
|
||||
inviteByLinkImpl?()
|
||||
}))
|
||||
}
|
||||
|
||||
let contactsController: ViewController
|
||||
if peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
|
@ -195,8 +195,6 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
||||
|
||||
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
|
||||
//messageFileMediaResourceStatus(account: account, file: file, message: message, isRecentActions: isRecentActions)
|
||||
|
||||
|
||||
var imageResource: TelegramMediaResource?
|
||||
var stickerFile: TelegramMediaFile?
|
||||
|
@ -71,11 +71,18 @@ NSData * _Nullable albumArtworkData(NSData * _Nonnull data) {
|
||||
uint32_t pos = 0;
|
||||
while (pos < size) {
|
||||
const uint8_t * const frameBytes = ptr + pos;
|
||||
if (ID3TagOffset + pos + 4 >= data.length) {
|
||||
return nil;
|
||||
}
|
||||
uint32_t frameSize = frameSizeForBytes(frameBytes, version);
|
||||
if (ID3TagOffset + pos + frameSize >= data.length) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (isArtworkFrame(frameBytes, version)) {
|
||||
uint32_t frameOffset = frameOffsetForVersion(version);
|
||||
const uint8_t *ptr = frameBytes + frameOffset;
|
||||
uint32_t start = ID3TagOffset + pos + frameOffset;
|
||||
|
||||
bool isJpg = false;
|
||||
uint32_t imageOffset = UINT32_MAX;
|
||||
@ -91,7 +98,6 @@ NSData * _Nullable albumArtworkData(NSData * _Nonnull data) {
|
||||
}
|
||||
|
||||
if (imageOffset != UINT32_MAX) {
|
||||
uint32_t start = ID3TagOffset + pos + frameOffset;
|
||||
if (isJpg) {
|
||||
NSMutableData *jpgData = [[NSMutableData alloc] initWithCapacity:frameSize + 1024];
|
||||
uint8_t previousByte = 0xff;
|
||||
@ -103,11 +109,7 @@ NSData * _Nullable albumArtworkData(NSData * _Nonnull data) {
|
||||
return nil;
|
||||
}
|
||||
uint8_t byte = (uint8_t)ptr[offset];
|
||||
// if (byte == 0x00 && previousByte == 0xff) {
|
||||
// skippedBytes++;
|
||||
// } else {
|
||||
[jpgData appendBytes:&byte length:1];
|
||||
// }
|
||||
[jpgData appendBytes:&byte length:1];
|
||||
if (byte == 0xd9 && previousByte == 0xff) {
|
||||
break;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SafariServices
|
||||
|
||||
@ -188,8 +189,13 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
|
||||
if self.contentNode == nil || self.contentNode?.frame.width != width {
|
||||
self.contentNode?.removeFromSupernode()
|
||||
|
||||
var media: [MediaId: Media] = [:]
|
||||
if case let .Loaded(content) = self.webPage.content, let instantPage = content.instantPage {
|
||||
media = instantPage.media
|
||||
}
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
let (_, items, contentSize) = layoutTextItemWithString(self.item.attributedString, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset))
|
||||
let (_, items, contentSize) = layoutTextItemWithString(self.item.attributedString, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset), media: media, webpage: self.webPage)
|
||||
let contentNode = InstantPageContentNode(account: self.account, strings: self.presentationData.strings, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, longPressMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in })
|
||||
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: width, height: contentSize.height)))
|
||||
self.contentContainerNode.insertSubnode(contentNode, at: 0)
|
||||
|
@ -508,7 +508,10 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
|
||||
})
|
||||
let delegate = CTRunDelegateCreate(&callbacks, extentBuffer)
|
||||
let attrDictionaryDelegate = [(kCTRunDelegateAttributeName as NSAttributedStringKey): (delegate as Any), NSAttributedStringKey(rawValue: InstantPageMediaIdAttribute): id.id, NSAttributedStringKey(rawValue: InstantPageMediaDimensionsAttribute): dimensions]
|
||||
return NSAttributedString(string: " ", attributes: attrDictionaryDelegate)
|
||||
let mutableAttributedString = attributedStringForRichText(.plain(" "), styleStack: styleStack, url: url).mutableCopy() as! NSMutableAttributedString
|
||||
mutableAttributedString.addAttributes(attrDictionaryDelegate, range: NSMakeRange(0, mutableAttributedString.length))
|
||||
return mutableAttributedString
|
||||
//return NSAttributedString(string: " ", attributes: attrDictionaryDelegate)
|
||||
case let .anchor(text, name):
|
||||
var empty = false
|
||||
var text = text
|
||||
@ -569,7 +572,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
|
||||
var workingLineOrigin = currentLineOrigin
|
||||
|
||||
let currentMaxWidth = boundingWidth - workingLineOrigin.x
|
||||
let lineCharacterCount: CFIndex
|
||||
var lineCharacterCount: CFIndex
|
||||
var hadIndexOffset = false
|
||||
if minimizeWidth {
|
||||
var count = 0
|
||||
@ -584,6 +587,9 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
|
||||
let suggestedLineBreak = CTTypesetterSuggestLineBreak(typesetter, lastIndex, Double(currentMaxWidth))
|
||||
if let offset = indexOffset {
|
||||
lineCharacterCount = suggestedLineBreak + offset
|
||||
if lineCharacterCount <= 0 {
|
||||
lineCharacterCount = suggestedLineBreak
|
||||
}
|
||||
indexOffset = nil
|
||||
hadIndexOffset = true
|
||||
} else {
|
||||
|
@ -44,18 +44,18 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
@ -16,7 +16,7 @@ func presentLegacyAvatarPicker(holder: Atomic<NSObject?>, signup: Bool, theme: P
|
||||
|
||||
present(legacyController, nil)
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: false, hasViewButton: openCurrent != nil, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: signup)!
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: false, hasDeleteButton: false, hasViewButton: openCurrent != nil, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: signup)!
|
||||
let _ = holder.swap(mixin)
|
||||
mixin.didFinishWithImage = { image in
|
||||
guard let image = image else {
|
||||
|
73
TelegramUI/LegacyWebSearchEditor.swift
Normal file
73
TelegramUI/LegacyWebSearchEditor.swift
Normal file
@ -0,0 +1,73 @@
|
||||
import Foundation
|
||||
import LegacyComponents
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import Postbox
|
||||
import SSignalKit
|
||||
import UIKit
|
||||
import Display
|
||||
|
||||
func presentLegacyWebSearchEditor(account: Account, theme: PresentationTheme, result: ChatContextResult, initialLayout: ContainerViewLayout?, updateHiddenMedia: @escaping (String?) -> Void, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (UIImage) -> Void, present: (ViewController, Any?) -> Void) {
|
||||
guard let item = legacyWebSearchItem(account: account, result: result) else {
|
||||
return
|
||||
}
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: theme, initialLayout: initialLayout)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let controller = TGPhotoEditorController(context: legacyController.context, item: item, intent: TGPhotoEditorControllerAvatarIntent, adjustments: nil, caption: nil, screenImage: nil, availableTabs: TGPhotoEditorController.defaultTabsForAvatarIntent(), selectedTab: .cropTab)!
|
||||
legacyController.bind(controller: controller)
|
||||
|
||||
controller.editingContext = TGMediaEditingContext()
|
||||
controller.didFinishEditing = { [weak controller] _, result, _, hasChanges in
|
||||
if !hasChanges {
|
||||
return
|
||||
}
|
||||
if let result = result {
|
||||
completed(result)
|
||||
}
|
||||
controller?.dismiss(animated: true)
|
||||
}
|
||||
controller.requestThumbnailImage = { _ -> SSignal in
|
||||
return item.thumbnailImageSignal()
|
||||
}
|
||||
controller.requestOriginalScreenSizeImage = { _, position -> SSignal in
|
||||
return item.screenImageSignal(position)
|
||||
}
|
||||
controller.requestOriginalFullSizeImage = { _, position -> SSignal in
|
||||
return item.originalImageSignal(position)
|
||||
}
|
||||
|
||||
let fromView = transitionView(result)!
|
||||
let transition = TGMediaAvatarEditorTransition(controller: controller, from: fromView)!
|
||||
transition.transitionHostView = transitionHostView()
|
||||
transition.referenceFrame = {
|
||||
return fromView.frame
|
||||
}
|
||||
transition.referenceImageSize = {
|
||||
return item.dimensions
|
||||
}
|
||||
transition.referenceScreenImageSignal = {
|
||||
return item.screenImageSignal(0.0)
|
||||
}
|
||||
transition.imageReady = {
|
||||
updateHiddenMedia(result.id)
|
||||
}
|
||||
|
||||
controller.beginCustomTransitionOut = { [weak legacyController] outFrame, outView, completion in
|
||||
transition.outReferenceFrame = outFrame
|
||||
transition.repView = outView
|
||||
transition.dismiss(animated: true, completion: {
|
||||
updateHiddenMedia(nil)
|
||||
if let completion = completion {
|
||||
DispatchQueue.main.async {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
legacyController?.dismiss()
|
||||
})
|
||||
}
|
||||
|
||||
present(legacyController, nil)
|
||||
transition.present(animated: true)
|
||||
}
|
@ -20,12 +20,22 @@ class LegacyWebSearchItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem
|
||||
let dimensions: CGSize
|
||||
let thumbnailImage: Signal<UIImage, NoError>
|
||||
let originalImage: Signal<UIImage, NoError>
|
||||
let progress: Signal<Float, NoError>
|
||||
|
||||
init(result: ChatContextResult, dimensions: CGSize, thumbnailImage: Signal<UIImage, NoError>, originalImage: Signal<UIImage, NoError>) {
|
||||
init(result: ChatContextResult) {
|
||||
self.result = result
|
||||
self.dimensions = CGSize()
|
||||
self.thumbnailImage = .complete()
|
||||
self.originalImage = .complete()
|
||||
self.progress = .complete()
|
||||
}
|
||||
|
||||
init(result: ChatContextResult, dimensions: CGSize, thumbnailImage: Signal<UIImage, NoError>, originalImage: Signal<UIImage, NoError>, progress: Signal<Float, NoError>) {
|
||||
self.result = result
|
||||
self.dimensions = dimensions
|
||||
self.thumbnailImage = thumbnailImage
|
||||
self.originalImage = originalImage
|
||||
self.progress = progress
|
||||
}
|
||||
|
||||
var originalSize: CGSize {
|
||||
@ -45,6 +55,30 @@ class LegacyWebSearchItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem
|
||||
})
|
||||
}
|
||||
|
||||
func screenImageAndProgressSignal() -> SSignal {
|
||||
return SSignal { subscriber in
|
||||
let imageDisposable = self.originalImage.start(next: { image in
|
||||
if !image.degraded() {
|
||||
subscriber?.putNext(1.0)
|
||||
}
|
||||
subscriber?.putNext(image)
|
||||
if !image.degraded() {
|
||||
subscriber?.putCompletion()
|
||||
}
|
||||
})
|
||||
|
||||
let progressDisposable = (self.progress
|
||||
|> deliverOnMainQueue).start(next: { next in
|
||||
subscriber?.putNext(next)
|
||||
})
|
||||
|
||||
return SBlockDisposable {
|
||||
imageDisposable.dispose()
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func screenImageSignal(_ position: TimeInterval) -> SSignal! {
|
||||
return self.originalImageSignal(position)
|
||||
}
|
||||
@ -53,7 +87,9 @@ class LegacyWebSearchItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem
|
||||
return SSignal(generator: { subscriber -> SDisposable? in
|
||||
let disposable = self.originalImage.start(next: { image in
|
||||
subscriber?.putNext(image)
|
||||
subscriber?.putCompletion()
|
||||
if !image.degraded() {
|
||||
subscriber?.putCompletion()
|
||||
}
|
||||
})
|
||||
|
||||
return SBlockDisposable(block: {
|
||||
@ -93,14 +129,26 @@ private class LegacyWebSearchGalleryItem: TGModernGalleryImageItem, TGModernGall
|
||||
override func viewClass() -> AnyClass! {
|
||||
return LegacyWebSearchGalleryItemView.self
|
||||
}
|
||||
|
||||
override func isEqual(_ object: Any?) -> Bool {
|
||||
if let item = object as? LegacyWebSearchGalleryItem {
|
||||
return item.item.result.id == self.item.result.id
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGModernGalleryEditableItemView
|
||||
{
|
||||
private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGModernGalleryEditableItemView {
|
||||
private let readyForTransition = SVariable()!
|
||||
|
||||
func setHiddenAsBeingEdited(_ hidden: Bool) {
|
||||
self.imageView.isHidden = hidden
|
||||
}
|
||||
|
||||
override func readyForTransitionIn() -> SSignal! {
|
||||
return self.readyForTransition.signal()!.take(1)
|
||||
}
|
||||
|
||||
override func setItem(_ item: TGModernGalleryItem!, synchronously: Bool) {
|
||||
if let item = item as? LegacyWebSearchGalleryItem {
|
||||
self._setItem(item)
|
||||
@ -110,7 +158,7 @@ private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGMo
|
||||
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)
|
||||
return mediaItem.screenImageAndProgressSignal()
|
||||
} else {
|
||||
return SSignal.complete()
|
||||
}
|
||||
@ -120,6 +168,7 @@ private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGMo
|
||||
if let strongSelf = self, let image = next as? UIImage {
|
||||
strongSelf.imageSize = image.size
|
||||
strongSelf.reset()
|
||||
strongSelf.readyForTransition.set(SSignal.single(true))
|
||||
}
|
||||
}))
|
||||
|
||||
@ -144,63 +193,90 @@ private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGMo
|
||||
}
|
||||
}
|
||||
|
||||
private func galleryItems(account: Account, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext, editingContext: TGMediaEditingContext) -> ([TGModernGalleryItem], TGModernGalleryItem?) {
|
||||
func legacyWebSearchItem(account: Account, result: ChatContextResult) -> LegacyWebSearchItem? {
|
||||
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
|
||||
}
|
||||
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 {
|
||||
let progressSignal = account.postbox.mediaBox.resourceStatus(imageResource)
|
||||
|> map { status -> Float in
|
||||
switch status {
|
||||
case .Local:
|
||||
return 1.0
|
||||
case .Remote:
|
||||
return 0.0
|
||||
case let .Fetching(_, progress):
|
||||
return progress
|
||||
}
|
||||
}
|
||||
|
||||
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: false)
|
||||
|> mapToSignal { (thumbnailData, _, _) -> Signal<UIImage, NoError> in
|
||||
if let data = thumbnailData, let image = UIImage(data: data) {
|
||||
return .single(image)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
originalSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: true)
|
||||
|> mapToSignal { (thumbnailData, fullSizeData, fullSizeComplete) -> Signal<UIImage, NoError> in
|
||||
if fullSizeComplete, let data = fullSizeData, let image = UIImage(data: data) {
|
||||
return .single(image)
|
||||
} else if let data = thumbnailData, let image = UIImage(data: data) {
|
||||
image.setDegraded(true)
|
||||
return .single(image)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
return LegacyWebSearchItem(result: result, dimensions: imageDimensions, thumbnailImage: thumbnailSignal, originalImage: originalSignal, progress: progressSignal)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if let item = legacyWebSearchItem(account: account, result: result) {
|
||||
let galleryItem = LegacyWebSearchGalleryItem(item: item)
|
||||
galleryItem.selectionContext = selectionContext
|
||||
galleryItem.editingContext = editingContext
|
||||
@ -213,11 +289,12 @@ private func galleryItems(account: Account, results: [ChatContextResult], curren
|
||||
return (galleryItems, focusItem)
|
||||
}
|
||||
|
||||
func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: PresentationTheme, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext, editingContext: TGMediaEditingContext, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (ChatContextResult) -> Void, present: (ViewController, Any?) -> Void) {
|
||||
func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: PresentationTheme, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (ChatContextResult) -> Void, present: (ViewController, Any?) -> Void) {
|
||||
let legacyController = LegacyController(presentation: .custom, theme: theme, initialLayout: initialLayout)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let controller = TGModernGalleryController(context: legacyController.context)!
|
||||
controller.asyncTransitionIn = true
|
||||
legacyController.bind(controller: controller)
|
||||
|
||||
let (items, focusItem) = galleryItems(account: account, results: results, current: current, selectionContext: selectionContext, editingContext: editingContext)
|
||||
@ -228,9 +305,6 @@ func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: Present
|
||||
}
|
||||
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)
|
||||
@ -240,17 +314,22 @@ func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: Present
|
||||
editingContext.setAdjustments(adjustments, for: item)
|
||||
}
|
||||
editingContext.setTemporaryRep(representation, for: item)
|
||||
//if let selectionContext = selectionContext, {
|
||||
// selectionContex
|
||||
//}
|
||||
if let selectionContext = selectionContext, adjustments != nil, let item = item as? TGMediaSelectableItem {
|
||||
selectionContext.setItem(item, selected: true)
|
||||
}
|
||||
}
|
||||
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)
|
||||
if let selectionContext = selectionContext, let caption = caption, caption.count > 0, let item = item as? TGMediaSelectableItem {
|
||||
selectionContext.setItem(item, selected: true)
|
||||
}
|
||||
}
|
||||
if let selectionContext = selectionContext {
|
||||
model.interfaceView.updateSelectionInterface(selectionContext.count(), counterVisible: selectionContext.count() > 0, animated: false)
|
||||
}
|
||||
//[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)
|
||||
@ -284,46 +363,4 @@ func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: Present
|
||||
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];
|
||||
// }
|
||||
// };
|
||||
//
|
||||
}
|
||||
|
@ -71,77 +71,84 @@ public enum MessageContentKind: Equatable {
|
||||
|
||||
public func messageContentKind(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> MessageContentKind {
|
||||
for media in message.media {
|
||||
switch media {
|
||||
case let expiredMedia as TelegramMediaExpiredContent:
|
||||
switch expiredMedia.data {
|
||||
case .image:
|
||||
return .expiredImage
|
||||
case .file:
|
||||
return .expiredVideo
|
||||
}
|
||||
case _ as TelegramMediaImage:
|
||||
return .image
|
||||
case let file as TelegramMediaFile:
|
||||
var fileName: String = ""
|
||||
for attribute in file.attributes {
|
||||
switch attribute {
|
||||
case let .Sticker(text, _, _):
|
||||
return .sticker(text)
|
||||
case let .FileName(name):
|
||||
fileName = name
|
||||
case let .Audio(isVoice, _, title, performer, _):
|
||||
if isVoice {
|
||||
return .audioMessage
|
||||
} else {
|
||||
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
|
||||
return .file(title + " — " + performer)
|
||||
} else if let title = title, !title.isEmpty {
|
||||
return .file(title)
|
||||
} else if let performer = performer, !performer.isEmpty {
|
||||
return .file(performer)
|
||||
}
|
||||
}
|
||||
case let .Video(_, _, flags):
|
||||
if file.isAnimated {
|
||||
return .animation
|
||||
} else {
|
||||
if flags.contains(.instantRoundVideo) {
|
||||
return .videoMessage
|
||||
} else {
|
||||
return .video
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return .file(fileName)
|
||||
case _ as TelegramMediaContact:
|
||||
return .contact
|
||||
case let game as TelegramMediaGame:
|
||||
return .game(game.title)
|
||||
case let location as TelegramMediaMap:
|
||||
if location.liveBroadcastingTimeout != nil {
|
||||
return .liveLocation
|
||||
} else {
|
||||
return .location
|
||||
}
|
||||
case _ as TelegramMediaAction:
|
||||
return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) ?? "")
|
||||
case let poll as TelegramMediaPoll:
|
||||
return .text(poll.text)
|
||||
default:
|
||||
break
|
||||
if let kind = mediaContentKind(media, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId) {
|
||||
return kind
|
||||
}
|
||||
}
|
||||
return .text(message.text)
|
||||
}
|
||||
|
||||
func descriptionStringForMessage(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) {
|
||||
if !message.text.isEmpty {
|
||||
return (message.text, false)
|
||||
|
||||
public func mediaContentKind(_ media: Media, message: Message? = nil, strings: PresentationStrings? = nil, nameDisplayOrder: PresentationPersonNameOrder? = nil, accountPeerId: PeerId? = nil) -> MessageContentKind? {
|
||||
switch media {
|
||||
case let expiredMedia as TelegramMediaExpiredContent:
|
||||
switch expiredMedia.data {
|
||||
case .image:
|
||||
return .expiredImage
|
||||
case .file:
|
||||
return .expiredVideo
|
||||
}
|
||||
case _ as TelegramMediaImage:
|
||||
return .image
|
||||
case let file as TelegramMediaFile:
|
||||
var fileName: String = ""
|
||||
for attribute in file.attributes {
|
||||
switch attribute {
|
||||
case let .Sticker(text, _, _):
|
||||
return .sticker(text)
|
||||
case let .FileName(name):
|
||||
fileName = name
|
||||
case let .Audio(isVoice, _, title, performer, _):
|
||||
if isVoice {
|
||||
return .audioMessage
|
||||
} else {
|
||||
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
|
||||
return .file(title + " — " + performer)
|
||||
} else if let title = title, !title.isEmpty {
|
||||
return .file(title)
|
||||
} else if let performer = performer, !performer.isEmpty {
|
||||
return .file(performer)
|
||||
}
|
||||
}
|
||||
case let .Video(_, _, flags):
|
||||
if file.isAnimated {
|
||||
return .animation
|
||||
} else {
|
||||
if flags.contains(.instantRoundVideo) {
|
||||
return .videoMessage
|
||||
} else {
|
||||
return .video
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return .file(fileName)
|
||||
case _ as TelegramMediaContact:
|
||||
return .contact
|
||||
case let game as TelegramMediaGame:
|
||||
return .game(game.title)
|
||||
case let location as TelegramMediaMap:
|
||||
if location.liveBroadcastingTimeout != nil {
|
||||
return .liveLocation
|
||||
} else {
|
||||
return .location
|
||||
}
|
||||
case _ as TelegramMediaAction:
|
||||
if let message = message, let strings = strings, let nameDisplayOrder = nameDisplayOrder, let accountPeerId = accountPeerId {
|
||||
return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) ?? "")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case let poll as TelegramMediaPoll:
|
||||
return .text(poll.text)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
switch messageContentKind(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId) {
|
||||
}
|
||||
|
||||
func stringForMediaKind(_ kind: MessageContentKind, strings: PresentationStrings) -> (String, Bool) {
|
||||
switch kind {
|
||||
case let .text(text):
|
||||
return (text, false)
|
||||
case .image:
|
||||
@ -180,3 +187,10 @@ func descriptionStringForMessage(_ message: Message, strings: PresentationString
|
||||
return (strings.Message_VideoExpired, true)
|
||||
}
|
||||
}
|
||||
|
||||
func descriptionStringForMessage(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) {
|
||||
if !message.text.isEmpty {
|
||||
return (message.text, false)
|
||||
}
|
||||
return stringForMediaKind(messageContentKind(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId), strings: strings)
|
||||
}
|
||||
|
@ -32,7 +32,6 @@ public class NotificationExceptionsController: ViewController {
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
|
||||
|
||||
self.editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.editPressed))
|
||||
self.doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
|
||||
|
||||
@ -72,7 +71,7 @@ public class NotificationExceptionsController: ViewController {
|
||||
private func updateThemeAndStrings() {
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
self.title = self.presentationData.strings.Settings_AppLanguage
|
||||
self.title = self.presentationData.strings.Notifications_ExceptionsTitle
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
self.controllerNode.updatePresentationData(self.presentationData)
|
||||
|
||||
|
@ -60,6 +60,20 @@ public final class PeerSelectionController: ViewController {
|
||||
strongSelf.peerSelectionNode.scrollToTop()
|
||||
}
|
||||
}
|
||||
|
||||
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.updateThemeAndStrings()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -68,6 +82,14 @@ public final class PeerSelectionController: ViewController {
|
||||
|
||||
deinit {
|
||||
self.openMessageFromSearchDisposable.dispose()
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings() {
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
self.title = self.presentationData.strings.Conversation_ForwardTitle
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
|
@ -120,6 +120,10 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
|
||||
self.searchDisplayController?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
||||
self.chatListNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)
|
||||
|
||||
self.toolbarBackgroundNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor
|
||||
self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
||||
self.segmentedControl?.tintColor = self.presentationData.theme.rootController.navigationBar.accentTextColor
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
@ -140,10 +144,6 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
transition.updateFrame(view: segmentedControl, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: layout.size.height - toolbarHeight + floor((44.0 - controlSize.height) / 2.0)), size: controlSize))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
|
||||
insets.bottom = max(insets.bottom, cleanInsets.bottom)
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -443,13 +443,13 @@ class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
|
||||
func transitionOut(to node: SearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
let targetTextBackgroundFrame = node.convert(node.backgroundNode.frame, to: self)
|
||||
|
||||
let duration: Double = 0.5
|
||||
let duration: Double = transition.isAnimated ? 0.5 : 0.0
|
||||
let timingFunction = kCAMediaTimingFunctionSpring
|
||||
|
||||
node.isHidden = true
|
||||
self.clearButton.isHidden = true
|
||||
self.textField.text = ""
|
||||
|
||||
|
||||
var backgroundCompleted = false
|
||||
var separatorCompleted = false
|
||||
var textBackgroundCompleted = false
|
||||
|
@ -616,9 +616,9 @@ public func settingsController(account: Account, accountManager: AccountManager)
|
||||
})
|
||||
|
||||
changeProfilePhotoImpl = {
|
||||
let _ = (account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(account.peerId)
|
||||
} |> deliverOnMainQueue).start(next: { peer in
|
||||
let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
|
||||
return (transaction.getPeer(account.peerId), currentSearchBotsConfiguration(transaction: transaction))
|
||||
} |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
@ -638,28 +638,38 @@ public func settingsController(account: Account, accountManager: AccountManager)
|
||||
hasPhotos = true
|
||||
}
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false)!
|
||||
let completedImpl: (UIImage) -> Void = { image in
|
||||
if let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(.image(representation, true))
|
||||
}
|
||||
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: resource) |> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .complete:
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(nil)
|
||||
}
|
||||
case .progress:
|
||||
break
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
|
||||
let _ = currentAvatarMixin.swap(mixin)
|
||||
mixin.requestSearchController = { _ in
|
||||
let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
|
||||
completedImpl(result)
|
||||
}))
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}
|
||||
mixin.didFinishWithImage = { image in
|
||||
if let image = image {
|
||||
if let data = UIImageJPEGRepresentation(image, 0.6) {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
|
||||
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(.image(representation, true))
|
||||
}
|
||||
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: resource) |> deliverOnMainQueue).start(next: { result in
|
||||
switch result {
|
||||
case .complete:
|
||||
updateState {
|
||||
$0.withUpdatedUpdatingAvatar(nil)
|
||||
}
|
||||
case .progress:
|
||||
break
|
||||
}
|
||||
}))
|
||||
}
|
||||
completedImpl(image)
|
||||
}
|
||||
}
|
||||
mixin.didFinishWithDelete = {
|
||||
|
@ -93,7 +93,6 @@ class SetupTwoStepVerificationController: ViewController {
|
||||
private func updateThemeAndStrings() {
|
||||
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
|
||||
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
self.title = self.presentationData.strings.Settings_AppLanguage
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
self.controllerNode.updatePresentationData(self.presentationData)
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ private final class UserInfoControllerArguments {
|
||||
let displayCopyContextMenu: (UserInfoEntryTag, String) -> Void
|
||||
let call: () -> Void
|
||||
let openCallMenu: (String) -> Void
|
||||
let aboutLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void
|
||||
let displayAboutContextMenu: (String) -> Void
|
||||
let openEncryptionKey: (SecretChatKeyFingerprint) -> Void
|
||||
let addBotToGroup: () -> Void
|
||||
@ -34,7 +35,7 @@ private final class UserInfoControllerArguments {
|
||||
let botPrivacy: () -> Void
|
||||
let report: () -> Void
|
||||
|
||||
init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, tapAvatarAction: @escaping () -> Void, openChat: @escaping () -> Void, addContact: @escaping () -> Void, shareContact: @escaping () -> Void, shareMyContact: @escaping () -> Void, startSecretChat: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, changeNotificationSoundSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openGroupsInCommon: @escaping () -> Void, updatePeerBlocked: @escaping (Bool) -> Void, deleteContact: @escaping () -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayCopyContextMenu: @escaping (UserInfoEntryTag, String) -> Void, call: @escaping () -> Void, openCallMenu: @escaping (String) -> Void, displayAboutContextMenu: @escaping (String) -> Void, openEncryptionKey: @escaping (SecretChatKeyFingerprint) -> Void, addBotToGroup: @escaping () -> Void, shareBot: @escaping () -> Void, botSettings: @escaping () -> Void, botHelp: @escaping () -> Void, botPrivacy: @escaping () -> Void, report: @escaping () -> Void) {
|
||||
init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, tapAvatarAction: @escaping () -> Void, openChat: @escaping () -> Void, addContact: @escaping () -> Void, shareContact: @escaping () -> Void, shareMyContact: @escaping () -> Void, startSecretChat: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, changeNotificationSoundSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openGroupsInCommon: @escaping () -> Void, updatePeerBlocked: @escaping (Bool) -> Void, deleteContact: @escaping () -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayCopyContextMenu: @escaping (UserInfoEntryTag, String) -> Void, call: @escaping () -> Void, openCallMenu: @escaping (String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, displayAboutContextMenu: @escaping (String) -> Void, openEncryptionKey: @escaping (SecretChatKeyFingerprint) -> Void, addBotToGroup: @escaping () -> Void, shareBot: @escaping () -> Void, botSettings: @escaping () -> Void, botHelp: @escaping () -> Void, botPrivacy: @escaping () -> Void, report: @escaping () -> Void) {
|
||||
self.account = account
|
||||
self.avatarAndNameInfoContext = avatarAndNameInfoContext
|
||||
self.updateEditingName = updateEditingName
|
||||
@ -55,6 +56,7 @@ private final class UserInfoControllerArguments {
|
||||
self.displayCopyContextMenu = displayCopyContextMenu
|
||||
self.call = call
|
||||
self.openCallMenu = openCallMenu
|
||||
self.aboutLinkAction = aboutLinkAction
|
||||
self.displayAboutContextMenu = displayAboutContextMenu
|
||||
self.openEncryptionKey = openEncryptionKey
|
||||
self.addBotToGroup = addBotToGroup
|
||||
@ -93,7 +95,7 @@ private func areMessagesEqual(_ lhsMessage: Message, _ rhsMessage: Message) -> B
|
||||
private enum UserInfoEntry: ItemListNodeEntry {
|
||||
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer?, presence: PeerPresence?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, displayCall: Bool)
|
||||
case calls(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, messages: [Message])
|
||||
case about(PresentationTheme, String, String)
|
||||
case about(PresentationTheme, Peer, String, String)
|
||||
case phoneNumber(PresentationTheme, Int, String, String, Bool)
|
||||
case userName(PresentationTheme, String, String)
|
||||
case sendMessage(PresentationTheme, String)
|
||||
@ -192,8 +194,8 @@ private enum UserInfoEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .about(lhsTheme, lhsText, lhsValue):
|
||||
if case let .about(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
case let .about(lhsTheme, lhsPeer, lhsText, lhsValue):
|
||||
if case let .about(rhsTheme, rhsPeer, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsPeer.isEqual(rhsPeer), lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -380,9 +382,15 @@ private enum UserInfoEntry: ItemListNodeEntry {
|
||||
} : nil)
|
||||
case let .calls(theme, strings, dateTimeFormat, messages):
|
||||
return ItemListCallListItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, messages: messages, sectionId: self.section, style: .plain)
|
||||
case let .about(theme, text, value):
|
||||
return ItemListTextWithLabelItem(theme: theme, label: text, text: value, enabledEntitiyTypes: [], multiline: true, sectionId: self.section, action: {
|
||||
case let .about(theme, peer, text, value):
|
||||
var enabledEntitiyTypes: EnabledEntityTypes = []
|
||||
if let peer = peer as? TelegramUser, let _ = peer.botInfo {
|
||||
enabledEntitiyTypes = [.url, .mention, .hashtag]
|
||||
}
|
||||
return ItemListTextWithLabelItem(theme: theme, label: text, text: value, enabledEntitiyTypes: enabledEntitiyTypes, multiline: true, sectionId: self.section, action: nil, longTapAction: {
|
||||
arguments.displayAboutContextMenu(value)
|
||||
}, linkItemAction: { action, itemLink in
|
||||
arguments.aboutLinkAction(action, itemLink)
|
||||
}, tag: UserInfoEntryTag.about)
|
||||
case let .phoneNumber(theme, _, label, value, isMain):
|
||||
return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: isMain ? .highlighted : .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: {
|
||||
@ -618,7 +626,7 @@ private func userInfoEntries(account: Account, presentationData: PresentationDat
|
||||
} else {
|
||||
title = presentationData.strings.Profile_About
|
||||
}
|
||||
entries.append(UserInfoEntry.about(presentationData.theme, title, about))
|
||||
entries.append(UserInfoEntry.about(presentationData.theme, peer, title, about))
|
||||
}
|
||||
|
||||
if !isEditing {
|
||||
@ -764,10 +772,14 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC
|
||||
let createSecretChatDisposable = MetaDisposable()
|
||||
actionsDisposable.add(createSecretChatDisposable)
|
||||
|
||||
let navigateDisposable = MetaDisposable()
|
||||
actionsDisposable.add(navigateDisposable)
|
||||
|
||||
var avatarGalleryTransitionArguments: ((AvatarGalleryEntry) -> GalleryTransitionArguments?)?
|
||||
let avatarAndNameInfoContext = ItemListAvatarAndNameInfoItemContext()
|
||||
var updateHiddenAvatarImpl: (() -> Void)?
|
||||
|
||||
var aboutLinkActionImpl: ((TextLinkItemActionType, TextLinkItem) -> Void)?
|
||||
var displayAboutContextMenuImpl: ((String) -> Void)?
|
||||
var displayCopyContextMenuImpl: ((UserInfoEntryTag, String) -> Void)?
|
||||
|
||||
@ -915,7 +927,7 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC
|
||||
}
|
||||
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
if let user = peer as? TelegramUser, user.botInfo != nil {
|
||||
if let peer = peer as? TelegramUser, let _ = peer.botInfo {
|
||||
updatePeerBlockedDisposable.set(requestUpdatePeerIsBlocked(account: account, peerId: peer.id, isBlocked: value).start())
|
||||
if !value {
|
||||
let _ = enqueueMessages(account: account, peerId: peer.id, messages: [.message(text: "/start", attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)]).start()
|
||||
@ -989,6 +1001,8 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC
|
||||
account.telegramApplicationContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(number).replacingOccurrences(of: " ", with: ""))")
|
||||
}
|
||||
})
|
||||
}, aboutLinkAction: { action, itemLink in
|
||||
aboutLinkActionImpl?(action, itemLink)
|
||||
}, displayAboutContextMenu: { text in
|
||||
displayAboutContextMenuImpl?(text)
|
||||
}, openEncryptionKey: { fingerprint in
|
||||
@ -1311,6 +1325,11 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC
|
||||
}
|
||||
}
|
||||
}
|
||||
aboutLinkActionImpl = { [weak controller] action, itemLink in
|
||||
if let controller = controller {
|
||||
handlePeerInfoAboutTextAction(account: account, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
|
||||
}
|
||||
}
|
||||
displayAboutContextMenuImpl = { [weak controller] text in
|
||||
if let strongController = controller {
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
|
@ -26,23 +26,56 @@ private func requestContextResults(account: Account, botId: PeerId, query: Strin
|
||||
}
|
||||
}
|
||||
|
||||
enum WebSearchMode {
|
||||
case media
|
||||
case avatar
|
||||
}
|
||||
|
||||
enum WebSearchControllerMode {
|
||||
case media(completion: (TGMediaSelectionContext, TGMediaEditingContext) -> Void)
|
||||
case avatar(completion: (UIImage) -> Void)
|
||||
|
||||
var mode: WebSearchMode {
|
||||
switch self {
|
||||
case .media:
|
||||
return .media
|
||||
case .avatar:
|
||||
return .avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class WebSearchControllerInteraction {
|
||||
let openResult: (ChatContextResult) -> Void
|
||||
let setSearchQuery: (String) -> Void
|
||||
let deleteRecentQuery: (String) -> Void
|
||||
let toggleSelection: ([String], Bool) -> Void
|
||||
let sendSelected: (ChatContextResultCollection, ChatContextResult?) -> Void
|
||||
var selectionState: WebSearchSelectionState?
|
||||
let toggleSelection: (ChatContextResult, Bool) -> Void
|
||||
let sendSelected: (ChatContextResult?) -> Void
|
||||
let avatarCompleted: (UIImage) -> Void
|
||||
var selectionState: TGMediaSelectionContext?
|
||||
let editingState: TGMediaEditingContext
|
||||
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, ChatContextResult?) -> Void, editingContext: TGMediaEditingContext) {
|
||||
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Void, sendSelected: @escaping (ChatContextResult?) -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
self.openResult = openResult
|
||||
self.setSearchQuery = setSearchQuery
|
||||
self.deleteRecentQuery = deleteRecentQuery
|
||||
self.toggleSelection = toggleSelection
|
||||
self.sendSelected = sendSelected
|
||||
self.editingContext = editingContext
|
||||
self.avatarCompleted = avatarCompleted
|
||||
self.selectionState = selectionState
|
||||
self.editingState = editingState
|
||||
}
|
||||
}
|
||||
|
||||
private func selectionChangedSignal(selectionState: TGMediaSelectionContext) -> Signal<Void, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = selectionState.selectionChangedSignal()?.start(next: { next in
|
||||
subscriber.putNext(Void())
|
||||
}, completed: {})
|
||||
return ActionDisposable {
|
||||
disposable?.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +83,8 @@ final class WebSearchController: ViewController {
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private let account: Account
|
||||
private let chatLocation: ChatLocation
|
||||
private let mode: WebSearchControllerMode
|
||||
private let peer: Peer?
|
||||
private let configuration: SearchBotsConfiguration
|
||||
|
||||
private var controllerNode: WebSearchControllerNode {
|
||||
@ -70,17 +104,19 @@ final class WebSearchController: ViewController {
|
||||
|
||||
private var disposable: Disposable?
|
||||
private let resultsDisposable = MetaDisposable()
|
||||
private var selectionDisposable: Disposable?
|
||||
|
||||
private var navigationContentNode: WebSearchNavigationContentNode?
|
||||
|
||||
init(account: Account, chatLocation: ChatLocation, configuration: SearchBotsConfiguration, sendSelected: @escaping ([String], ChatContextResultCollection, TGMediaEditingContext) -> Void) {
|
||||
init(account: Account, peer: Peer?, configuration: SearchBotsConfiguration, mode: WebSearchControllerMode) {
|
||||
self.account = account
|
||||
self.chatLocation = chatLocation
|
||||
self.mode = mode
|
||||
self.peer = peer
|
||||
self.configuration = configuration
|
||||
|
||||
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
|
||||
self.interfaceState = WebSearchInterfaceState(presentationData: presentationData)
|
||||
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme).withUpdatedSeparatorColor(presentationData.theme.rootController.navigationBar.backgroundColor), strings: NavigationBarStrings(presentationStrings: presentationData.strings)))
|
||||
self.statusBar.statusBarStyle = presentationData.theme.rootController.statusBar.style.style
|
||||
|
||||
@ -101,8 +137,8 @@ final class WebSearchController: ViewController {
|
||||
}
|
||||
strongSelf.updateInterfaceState { current -> WebSearchInterfaceState in
|
||||
var updated = current
|
||||
if current.state?.mode != settings.mode {
|
||||
updated = updated.withUpdatedMode(settings.mode)
|
||||
if case .media = mode, current.state?.scope != settings.scope {
|
||||
updated = updated.withUpdatedScope(settings.scope)
|
||||
}
|
||||
if current.presentationData !== presentationData {
|
||||
updated = updated.withUpdatedPresentationData(presentationData)
|
||||
@ -121,12 +157,19 @@ final class WebSearchController: ViewController {
|
||||
}
|
||||
self.navigationBar?.setContentNode(navigationContentNode, animated: false)
|
||||
|
||||
let editingContext = TGMediaEditingContext()
|
||||
let selectionState: TGMediaSelectionContext?
|
||||
switch self.mode {
|
||||
case .media:
|
||||
selectionState = TGMediaSelectionContext()
|
||||
case .avatar:
|
||||
selectionState = nil
|
||||
}
|
||||
let editingState = 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)
|
||||
strongSelf.present(viewController, in: .window(.root), with: arguments, blockInteraction: true)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -140,19 +183,35 @@ final class WebSearchController: ViewController {
|
||||
if let strongSelf = self {
|
||||
_ = removeRecentWebSearchQuery(postbox: strongSelf.account.postbox, string: query).start()
|
||||
}
|
||||
}, toggleSelection: { [weak self] ids, value in
|
||||
}, toggleSelection: { [weak self] result, value in
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateInterfaceState { $0.withToggledSelectedMessages(ids, value: value) }
|
||||
let item = LegacyWebSearchItem(result: result)
|
||||
strongSelf.controllerInteraction?.selectionState?.setItem(item, selected: value)
|
||||
}
|
||||
}, sendSelected: { [weak self] collection, current in
|
||||
if let strongSelf = self, let state = strongSelf.interfaceState.state {
|
||||
var selectedIds = state.selectionState.selectedIds
|
||||
}, sendSelected: { current in
|
||||
if let selectionState = selectionState {
|
||||
if let current = current {
|
||||
selectedIds.insert(current.id)
|
||||
let currentItem = LegacyWebSearchItem(result: current)
|
||||
selectionState.setItem(currentItem, selected: true)
|
||||
}
|
||||
if case let .media(sendSelected) = mode {
|
||||
sendSelected(selectionState, editingState)
|
||||
}
|
||||
sendSelected(Array(selectedIds), collection, editingContext)
|
||||
}
|
||||
}, editingContext: editingContext)
|
||||
}, avatarCompleted: { result in
|
||||
if case let .avatar(avatarCompleted) = mode {
|
||||
avatarCompleted(result)
|
||||
}
|
||||
}, selectionState: selectionState, editingState: editingState)
|
||||
|
||||
if let selectionState = selectionState {
|
||||
self.selectionDisposable = (selectionChangedSignal(selectionState: selectionState)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerNode.updateSelectionState(animated: true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -161,6 +220,8 @@ final class WebSearchController: ViewController {
|
||||
|
||||
deinit {
|
||||
self.disposable?.dispose()
|
||||
self.resultsDisposable.dispose()
|
||||
self.selectionDisposable?.dispose()
|
||||
}
|
||||
|
||||
public override func viewDidAppear(_ animated: Bool) {
|
||||
@ -181,7 +242,7 @@ final class WebSearchController: ViewController {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = WebSearchControllerNode(account: self.account, theme: self.interfaceState.presentationData.theme, strings: interfaceState.presentationData.strings, controllerInteraction: self.controllerInteraction!)
|
||||
self.displayNode = WebSearchControllerNode(account: self.account, theme: self.interfaceState.presentationData.theme, strings: interfaceState.presentationData.strings, controllerInteraction: self.controllerInteraction!, peer: self.peer, mode: self.mode.mode)
|
||||
self.controllerNode.requestUpdateInterfaceState = { [weak self] animated, f in
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateInterfaceState(f)
|
||||
@ -212,8 +273,6 @@ final class WebSearchController: ViewController {
|
||||
self.interfaceState = updatedInterfaceState
|
||||
self.interfaceStatePromise.set(updatedInterfaceState)
|
||||
|
||||
self.controllerInteraction?.selectionState = updatedInterfaceState.state?.selectionState
|
||||
|
||||
if self.isNodeLoaded {
|
||||
if previousTheme !== updatedInterfaceState.presentationData.theme || previousStrings !== updatedInterfaceState.presentationData.strings {
|
||||
self.controllerNode.updatePresentationData(theme: updatedInterfaceState.presentationData.theme, strings: updatedInterfaceState.presentationData.strings)
|
||||
@ -229,29 +288,26 @@ final class WebSearchController: ViewController {
|
||||
let _ = addRecentWebSearchQuery(postbox: self.account.postbox, string: query).start()
|
||||
}
|
||||
|
||||
let mode = self.interfaceStatePromise.get()
|
||||
|> map { state -> WebSearchMode? in
|
||||
return state.state?.mode
|
||||
let scope: Signal<WebSearchScope?, NoError>
|
||||
switch self.mode {
|
||||
case .media:
|
||||
scope = self.interfaceStatePromise.get()
|
||||
|> map { state -> WebSearchScope? in
|
||||
return state.state?.scope
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
case .avatar:
|
||||
scope = .single(.images)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
self.updateInterfaceState { $0.withUpdatedQuery(query) }
|
||||
|
||||
var results = mode
|
||||
|> mapToSignal { mode -> (Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>) in
|
||||
if let mode = mode {
|
||||
return self.signalForQuery(query, mode: mode)
|
||||
|> deliverOnMainQueue
|
||||
|> beforeStarted { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.navigationContentNode?.setActivity(true)
|
||||
}
|
||||
}
|
||||
|> afterCompleted { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.navigationContentNode?.setActivity(false)
|
||||
}
|
||||
}
|
||||
let scopes: [WebSearchScope: Promise<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?>] = [.images: Promise(initializeOnFirstAccess: self.signalForQuery(query, scope: .images)), .gifs: Promise(initializeOnFirstAccess: self.signalForQuery(query, scope: .gifs))]
|
||||
|
||||
var results = scope
|
||||
|> mapToSignal { scope -> (Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>) in
|
||||
if let scope = scope, let scopeResults = scopes[scope] {
|
||||
return scopeResults.get()
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
@ -276,29 +332,16 @@ final class WebSearchController: ViewController {
|
||||
}))
|
||||
}
|
||||
|
||||
private func signalForQuery(_ query: String, mode: WebSearchMode) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> {
|
||||
private func signalForQuery(_ query: String, scope: WebSearchScope) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> {
|
||||
var delayRequest = true
|
||||
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete()
|
||||
// if let previousQuery = previousQuery {
|
||||
// switch previousQuery {
|
||||
// case let .contextRequest(currentAddressName, currentContextQuery) where currentAddressName == addressName:
|
||||
// if query.isEmpty && !currentContextQuery.isEmpty {
|
||||
// delayRequest = false
|
||||
// }
|
||||
// default:
|
||||
// delayRequest = false
|
||||
// signal = .single({ _ in return .contextRequestResult(nil, nil) })
|
||||
// }
|
||||
// } else {
|
||||
signal = .single({ _ in return .contextRequestResult(nil, nil) })
|
||||
// }
|
||||
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .single({ _ in return .contextRequestResult(nil, nil) })
|
||||
|
||||
guard case let .peer(peerId) = self.chatLocation else {
|
||||
guard let peerId = self.peer?.id else {
|
||||
return .single({ _ in return .contextRequestResult(nil, nil) })
|
||||
}
|
||||
|
||||
let botName: String?
|
||||
switch mode {
|
||||
switch scope {
|
||||
case .images:
|
||||
botName = self.configuration.imageBotUsername
|
||||
case .gifs:
|
||||
@ -354,7 +397,18 @@ final class WebSearchController: ViewController {
|
||||
return .single({ _ in return nil })
|
||||
}
|
||||
}
|
||||
return signal |> then(contextBot)
|
||||
return (signal |> then(contextBot))
|
||||
|> deliverOnMainQueue
|
||||
|> beforeStarted { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.navigationContentNode?.setActivity(true)
|
||||
}
|
||||
}
|
||||
|> afterCompleted { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.navigationContentNode?.setActivity(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
|
@ -113,8 +113,10 @@ private func preparedWebSearchRecentTransition(from fromEntries: [WebSearchRecen
|
||||
|
||||
class WebSearchControllerNode: ASDisplayNode {
|
||||
private let account: Account
|
||||
private let peer: Peer?
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
private let mode: WebSearchMode
|
||||
|
||||
private let controllerInteraction: WebSearchControllerInteraction
|
||||
private var webSearchInterfaceState: WebSearchInterfaceState
|
||||
@ -145,8 +147,6 @@ 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?
|
||||
|
||||
@ -163,11 +163,13 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
var cancel: (() -> Void)?
|
||||
var dismissInput: (() -> Void)?
|
||||
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: WebSearchControllerInteraction) {
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: WebSearchControllerInteraction, peer: Peer?, mode: WebSearchMode) {
|
||||
self.account = account
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.peer = peer
|
||||
self.mode = mode
|
||||
|
||||
self.webSearchInterfaceState = WebSearchInterfaceState(presentationData: account.telegramApplicationContext.currentPresentationData.with { $0 })
|
||||
self.webSearchInterfaceStatePromise = ValuePromise(self.webSearchInterfaceState, ignoreRepeated: true)
|
||||
@ -204,7 +206,9 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
self.addSubnode(self.recentQueriesNode)
|
||||
self.addSubnode(self.segmentedBackgroundNode)
|
||||
self.addSubnode(self.segmentedSeparatorNode)
|
||||
self.view.addSubview(self.segmentedControl)
|
||||
if case .media = mode {
|
||||
self.view.addSubview(self.segmentedControl)
|
||||
}
|
||||
self.addSubnode(self.toolbarBackgroundNode)
|
||||
self.addSubnode(self.toolbarSeparatorNode)
|
||||
self.addSubnode(self.cancelButton)
|
||||
@ -247,7 +251,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in
|
||||
if let strongSelf = self, let bottom = visibleItems.bottom, let entries = strongSelf.currentEntries {
|
||||
if bottom.0 <= entries.count {
|
||||
strongSelf.loadMore()
|
||||
//strongSelf.loadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -291,7 +295,12 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
|
||||
func applyPresentationData(themeUpdated: Bool = true) {
|
||||
self.cancelButton.setTitle(self.strings.Common_Cancel, with: Font.regular(17.0), with: self.theme.rootController.navigationBar.accentTextColor, for: .normal)
|
||||
self.sendButton.setTitle(self.strings.MediaPicker_Send, with: Font.medium(17.0), with: self.theme.rootController.navigationBar.accentTextColor, for: .normal)
|
||||
|
||||
if let selectionState = self.controllerInteraction.selectionState {
|
||||
let sendEnabled = selectionState.count() > 0
|
||||
let color = sendEnabled ? self.theme.rootController.navigationBar.accentTextColor : self.theme.rootController.navigationBar.disabledButtonColor
|
||||
self.sendButton.setTitle(self.strings.MediaPicker_Send, with: Font.medium(17.0), with: color, for: .normal)
|
||||
}
|
||||
|
||||
if themeUpdated {
|
||||
self.backgroundColor = self.theme.chatList.backgroundColor
|
||||
@ -322,7 +331,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
var insets = layout.insets(options: [.input])
|
||||
insets.top += navigationBarHeight
|
||||
|
||||
let segmentedHeight: CGFloat = 40.0
|
||||
let segmentedHeight: CGFloat = self.segmentedControl.superview != nil ? 40.0 : 5.0
|
||||
let panelY: CGFloat = insets.top - UIScreenPixel - 4.0
|
||||
|
||||
transition.updateFrame(node: self.segmentedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: segmentedHeight)))
|
||||
@ -342,7 +351,7 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
|
||||
if let image = self.attributionNode.image {
|
||||
transition.updateFrame(node: self.attributionNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - image.size.width) / 2.0), y: toolbarY + floor((toolbarHeight - image.size.height) / 2.0)), size: image.size))
|
||||
transition.updateAlpha(node: self.attributionNode, alpha: self.webSearchInterfaceState.state?.mode == .gifs ? 1.0 : 0.0)
|
||||
transition.updateAlpha(node: self.attributionNode, alpha: self.webSearchInterfaceState.state?.scope == .gifs ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
let toolbarPadding: CGFloat = 15.0
|
||||
@ -352,6 +361,20 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
let sendSize = self.sendButton.measure(CGSize(width: layout.size.width, height: toolbarHeight))
|
||||
transition.updateFrame(node: self.sendButton, frame: CGRect(origin: CGPoint(x: layout.size.width - toolbarPadding - layout.safeInsets.right - sendSize.width, y: toolbarY), size: CGSize(width: sendSize.width, height: toolbarHeight)))
|
||||
|
||||
if let selectionState = self.controllerInteraction.selectionState {
|
||||
self.sendButton.isHidden = false
|
||||
|
||||
let previousSendEnabled = self.sendButton.isEnabled
|
||||
let sendEnabled = selectionState.count() > 0
|
||||
self.sendButton.isEnabled = sendEnabled
|
||||
if sendEnabled != previousSendEnabled {
|
||||
let color = sendEnabled ? self.theme.rootController.navigationBar.accentTextColor : self.theme.rootController.navigationBar.disabledButtonColor
|
||||
self.sendButton.setTitle(self.strings.MediaPicker_Send, with: Font.medium(17.0), with: color, for: .normal)
|
||||
}
|
||||
} else {
|
||||
self.sendButton.isHidden = true
|
||||
}
|
||||
|
||||
let previousBounds = self.gridNode.bounds
|
||||
self.gridNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height)
|
||||
self.gridNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
|
||||
@ -396,7 +419,19 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
self.webSearchInterfaceStatePromise.set(self.webSearchInterfaceState)
|
||||
|
||||
if let state = interfaceState.state {
|
||||
self.segmentedControl.selectedSegmentIndex = Int(state.mode.rawValue)
|
||||
self.segmentedControl.selectedSegmentIndex = Int(state.scope.rawValue)
|
||||
}
|
||||
|
||||
if let validLayout = self.containerLayout {
|
||||
self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSelectionState(animated: Bool) {
|
||||
self.gridNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? WebSearchItemNode {
|
||||
itemNode.updateSelectionState(animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
if let validLayout = self.containerLayout {
|
||||
@ -432,12 +467,8 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
}
|
||||
strongSelf.isLoadingMore = false
|
||||
var results: [ChatContextResult] = []
|
||||
for result in currentProcessedResults.results {
|
||||
results.append(result)
|
||||
}
|
||||
for result in nextResults.results {
|
||||
results.append(result)
|
||||
}
|
||||
results.append(contentsOf: currentProcessedResults.results)
|
||||
results.append(contentsOf: nextResults.results.suffix(from: 1))
|
||||
let mergedResults = ChatContextResultCollection(botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, geoPoint: currentProcessedResults.geoPoint, queryId: nextResults.queryId, nextOffset: nextResults.nextOffset, presentation: currentProcessedResults.presentation, switchPeer: currentProcessedResults.switchPeer, results: results, cacheTimeout: currentProcessedResults.cacheTimeout)
|
||||
strongSelf.currentProcessedResults = mergedResults
|
||||
strongSelf.results.set(mergedResults)
|
||||
@ -527,11 +558,13 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
@objc private func indexChanged() {
|
||||
self.requestUpdateInterfaceState(true) { current in
|
||||
if let mode = WebSearchMode(rawValue: Int32(self.segmentedControl.selectedSegmentIndex)) {
|
||||
return current.withUpdatedMode(mode)
|
||||
if let scope = WebSearchScope(rawValue: Int32(self.segmentedControl.selectedSegmentIndex)) {
|
||||
let _ = updateWebSearchSettingsInteractively(postbox: self.account.postbox) { _ -> WebSearchSettings in
|
||||
return WebSearchSettings(scope: scope)
|
||||
}.start()
|
||||
self.requestUpdateInterfaceState(true) { current in
|
||||
return current.withUpdatedScope(scope)
|
||||
}
|
||||
return current
|
||||
}
|
||||
}
|
||||
|
||||
@ -540,67 +573,80 @@ class WebSearchControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
@objc private func sendPressed() {
|
||||
if let results = self.currentProcessedResults {
|
||||
self.controllerInteraction.sendSelected(results, nil)
|
||||
}
|
||||
self.controllerInteraction.sendSelected(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))
|
||||
}, initialLayout: self.containerLayout?.0, 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
|
||||
}
|
||||
if self.controllerInteraction.selectionState != nil {
|
||||
if let state = self.webSearchInterfaceState.state, state.scope == .images {
|
||||
if let results = self.currentProcessedResults?.results {
|
||||
presentLegacyWebSearchGallery(account: self.account, peer: self.peer, theme: self.theme, results: results, current: currentResult, selectionContext: self.controllerInteraction.selectionState, editingContext: self.controllerInteraction.editingState, updateHiddenMedia: { [weak self] id in
|
||||
self?.hiddenMediaId.set(.single(id))
|
||||
}, initialLayout: self.containerLayout?.0, 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 {
|
||||
strongSelf.controllerInteraction.sendSelected(result)
|
||||
strongSelf.cancel?()
|
||||
}
|
||||
}, present: present)
|
||||
}
|
||||
|
||||
let controller = WebSearchGalleryController(account: self.account, entries: entries, centralIndex: centralIndex, replaceRootController: { (controller, _) in
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
}, 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
|
||||
let controller = WebSearchGalleryController(account: self.account, peer: self.peer, 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
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
|
||||
}))
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
presentLegacyWebSearchEditor(account: self.account, theme: self.theme, result: currentResult, initialLayout: self.containerLayout?.0, 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 {
|
||||
strongSelf.controllerInteraction.avatarCompleted(result)
|
||||
strongSelf.cancel?()
|
||||
}
|
||||
}, present: present)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,14 +16,14 @@ struct WebSearchGalleryEntry: Equatable {
|
||||
|
||||
func item(account: Account, presentationData: PresentationData) -> GalleryItem {
|
||||
switch self.result {
|
||||
case let .externalReference(queryId, id, type, _, _, url, content, thumbnail, _):
|
||||
case let .externalReference(_, _, type, _, _, _, 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 })
|
||||
return WebSearchVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: fileReference, streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true))
|
||||
}
|
||||
case let .internalReference(queryId, id, _, _, _, _, file, _):
|
||||
case let .internalReference(_, _, _, _, _, _, 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 })
|
||||
return WebSearchVideoGalleryItem(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))
|
||||
}
|
||||
}
|
||||
preconditionFailure()
|
||||
@ -31,9 +31,11 @@ struct WebSearchGalleryEntry: Equatable {
|
||||
}
|
||||
|
||||
final class WebSearchGalleryControllerPresentationArguments {
|
||||
let animated: Bool
|
||||
let transitionArguments: (WebSearchGalleryEntry) -> GalleryTransitionArguments?
|
||||
|
||||
init(transitionArguments: @escaping (WebSearchGalleryEntry) -> GalleryTransitionArguments?) {
|
||||
init(animated: Bool = true, transitionArguments: @escaping (WebSearchGalleryEntry) -> GalleryTransitionArguments?) {
|
||||
self.animated = animated
|
||||
self.transitionArguments = transitionArguments
|
||||
}
|
||||
}
|
||||
@ -63,6 +65,8 @@ class WebSearchGalleryController: ViewController {
|
||||
private let centralItemFooterContentNode = Promise<GalleryFooterContentNode?>()
|
||||
private let centralItemAttributesDisposable = DisposableSet();
|
||||
|
||||
private var checkNode: GalleryNavigationCheckNode?
|
||||
|
||||
private let _hiddenMedia = Promise<WebSearchGalleryEntry?>(nil)
|
||||
var hiddenMedia: Signal<WebSearchGalleryEntry?, NoError> {
|
||||
return self._hiddenMedia.get()
|
||||
@ -71,7 +75,7 @@ class WebSearchGalleryController: ViewController {
|
||||
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?) {
|
||||
init(account: Account, peer: Peer?, entries: [WebSearchGalleryEntry], centralIndex: Int, replaceRootController: @escaping (ViewController, ValuePromise<Bool>?) -> Void, baseNavigationController: NavigationController?) {
|
||||
self.account = account
|
||||
self.replaceRootController = replaceRootController
|
||||
self.baseNavigationController = baseNavigationController
|
||||
@ -80,8 +84,16 @@ class WebSearchGalleryController: ViewController {
|
||||
|
||||
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
|
||||
if let title = peer?.displayTitle {
|
||||
let recipientNode = GalleryNavigationRecipientNode(color: .white, title: title)
|
||||
let leftItem = UIBarButtonItem(customDisplayNode: recipientNode)
|
||||
self.navigationItem.leftBarButtonItem = leftItem
|
||||
}
|
||||
|
||||
let checkNode = GalleryNavigationCheckNode(theme: self.presentationData.theme)
|
||||
let rightItem = UIBarButtonItem(customDisplayNode: checkNode)
|
||||
self.navigationItem.rightBarButtonItem = rightItem
|
||||
self.checkNode = checkNode
|
||||
|
||||
self.statusBar.statusBarStyle = .White
|
||||
|
||||
@ -236,8 +248,12 @@ class WebSearchGalleryController: ViewController {
|
||||
|
||||
if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) {
|
||||
nodeAnimatesItself = true
|
||||
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
|
||||
centralItemNode.activateAsInitial()
|
||||
|
||||
if presentationArguments.animated {
|
||||
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
|
||||
}
|
||||
|
||||
self._hiddenMedia.set(.single(self.entries[centralItemNode.index]))
|
||||
}
|
||||
}
|
||||
|
81
TelegramUI/WebSearchGalleryFooterContentNode.swift
Normal file
81
TelegramUI/WebSearchGalleryFooterContentNode.swift
Normal file
@ -0,0 +1,81 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import LegacyComponents
|
||||
|
||||
final class WebSearchGalleryFooterContentNode: GalleryFooterContentNode {
|
||||
private let account: Account
|
||||
private var theme: PresentationTheme
|
||||
private var strings: PresentationStrings
|
||||
|
||||
private let cancelButton: HighlightableButtonNode
|
||||
private let sendButton: HighlightableButtonNode
|
||||
|
||||
|
||||
init(account: Account, presentationData: PresentationData) {
|
||||
self.account = account
|
||||
self.theme = presentationData.theme
|
||||
self.strings = presentationData.strings
|
||||
|
||||
self.cancelButton = HighlightableButtonNode()
|
||||
self.cancelButton.setImage(TGComponentsImageNamed("PhotoPickerBackIcon"), for: [.normal])
|
||||
self.sendButton = HighlightableButtonNode()
|
||||
self.sendButton.setImage(PresentationResourcesChat.chatInputPanelSendButtonImage(self.theme), for: [.normal])
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.cancelButton)
|
||||
self.addSubnode(self.sendButton)
|
||||
|
||||
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.sendButton.addTarget(self, action: #selector(self.sendButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
func setCaption(_ caption: String) {
|
||||
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let panelSize: CGFloat = 49.0
|
||||
var panelHeight: CGFloat = panelSize + bottomInset
|
||||
panelHeight += contentInset
|
||||
var textFrame = CGRect()
|
||||
// if !self.textNode.isHidden {
|
||||
// let sideInset: CGFloat = 8.0 + leftInset
|
||||
// let topInset: CGFloat = 8.0
|
||||
// let textBottomInset: CGFloat = 8.0
|
||||
// let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
// panelHeight += textSize.height + topInset + textBottomInset
|
||||
// textFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: textSize)
|
||||
// }
|
||||
|
||||
//self.textNode.frame = textFrame
|
||||
|
||||
self.cancelButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - panelSize), size: CGSize(width: panelSize, height: panelSize))
|
||||
self.sendButton.frame = CGRect(origin: CGPoint(x: width - panelSize - rightInset, y: panelHeight - bottomInset - panelSize), size: CGSize(width: panelSize, height: panelSize))
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {
|
||||
self.cancelButton.alpha = 1.0
|
||||
self.sendButton.alpha = 1.0
|
||||
}
|
||||
|
||||
override func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
self.cancelButton.alpha = 0.0
|
||||
self.sendButton.alpha = 0.0
|
||||
completion()
|
||||
}
|
||||
|
||||
@objc func cancelButtonPressed() {
|
||||
|
||||
}
|
||||
|
||||
@objc func sendButtonPressed() {
|
||||
|
||||
}
|
||||
}
|
@ -1,26 +1,13 @@
|
||||
import Foundation
|
||||
|
||||
struct WebSearchSelectionState: Equatable {
|
||||
let selectedIds: Set<String>
|
||||
|
||||
static func ==(lhs: WebSearchSelectionState, rhs: WebSearchSelectionState) -> Bool {
|
||||
return lhs.selectedIds == rhs.selectedIds
|
||||
}
|
||||
|
||||
init(selectedIds: Set<String>) {
|
||||
self.selectedIds = selectedIds
|
||||
}
|
||||
}
|
||||
|
||||
enum WebSearchMode: Int32 {
|
||||
enum WebSearchScope: Int32 {
|
||||
case images
|
||||
case gifs
|
||||
}
|
||||
|
||||
struct WebSearchInterfaceInnerState: Equatable {
|
||||
let mode: WebSearchMode
|
||||
let scope: WebSearchScope
|
||||
let query: String
|
||||
let selectionState: WebSearchSelectionState
|
||||
}
|
||||
|
||||
struct WebSearchInterfaceState: Equatable {
|
||||
@ -37,27 +24,12 @@ struct WebSearchInterfaceState: Equatable {
|
||||
self.presentationData = presentationData
|
||||
}
|
||||
|
||||
func withUpdatedMode(_ mode: WebSearchMode) -> WebSearchInterfaceState {
|
||||
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(mode: mode, query: self.state?.query ?? "", selectionState: self.state?.selectionState ?? WebSearchSelectionState(selectedIds: [])), presentationData: self.presentationData)
|
||||
func withUpdatedScope(_ scope: WebSearchScope) -> WebSearchInterfaceState {
|
||||
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(scope: scope, query: self.state?.query ?? ""), presentationData: self.presentationData)
|
||||
}
|
||||
|
||||
func withUpdatedQuery(_ query: String) -> WebSearchInterfaceState {
|
||||
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(mode: self.state?.mode ?? .images, query: query, selectionState: self.state?.selectionState ?? WebSearchSelectionState(selectedIds: [])), presentationData: self.presentationData)
|
||||
}
|
||||
|
||||
func withToggledSelectedMessages(_ ids: [String], value: Bool) -> WebSearchInterfaceState {
|
||||
var selectedIds = Set<String>()
|
||||
if let selectionState = self.state?.selectionState {
|
||||
selectedIds.formUnion(selectionState.selectedIds)
|
||||
}
|
||||
for id in ids {
|
||||
if value {
|
||||
selectedIds.insert(id)
|
||||
} else {
|
||||
selectedIds.remove(id)
|
||||
}
|
||||
}
|
||||
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(mode: self.state?.mode ?? .images, query: self.state?.query ?? "", selectionState: WebSearchSelectionState(selectedIds: selectedIds)), presentationData: self.presentationData)
|
||||
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(scope: self.state?.scope ?? .images, query: query), presentationData: self.presentationData)
|
||||
}
|
||||
|
||||
func withUpdatedPresentationData(_ presentationData: PresentationData) -> WebSearchInterfaceState {
|
||||
|
@ -40,7 +40,7 @@ final class WebSearchItem: GridItem {
|
||||
final class WebSearchItemNode: GridItemNode {
|
||||
private let imageNodeBackground: ASDisplayNode
|
||||
private let imageNode: TransformImageNode
|
||||
private var selectionNode: GridMessageSelectionNode?
|
||||
private var checkNode: CheckNode?
|
||||
|
||||
private var currentImageResource: TelegramMediaResource?
|
||||
private var currentVideoFile: TelegramMediaFile?
|
||||
@ -52,8 +52,6 @@ final class WebSearchItemNode: GridItemNode {
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
private var resourceStatus: MediaResourceStatus?
|
||||
|
||||
private let statusNode: RadialStatusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
|
||||
|
||||
override init() {
|
||||
self.imageNodeBackground = ASDisplayNode()
|
||||
self.imageNodeBackground.isLayerBacked = true
|
||||
@ -86,13 +84,14 @@ final class WebSearchItemNode: GridItemNode {
|
||||
func setup(item: WebSearchItem) {
|
||||
if self.item !== item {
|
||||
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
|
||||
|
||||
var thumbnailDimensions: CGSize?
|
||||
var thumbnailResource: TelegramMediaResource?
|
||||
var imageResource: TelegramMediaResource?
|
||||
var videoFile: TelegramMediaFile?
|
||||
var imageDimensions: CGSize?
|
||||
switch item.result {
|
||||
case let .externalReference(_, _, type, title, _, url, content, thumbnail, _):
|
||||
case let .externalReference(_, _, type, _, _, _, content, thumbnail, _):
|
||||
if let content = content {
|
||||
imageResource = content.resource
|
||||
} else if let thumbnail = thumbnail {
|
||||
@ -103,18 +102,16 @@ final class WebSearchItemNode: GridItemNode {
|
||||
videoFile = 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: [])])
|
||||
imageResource = nil
|
||||
}
|
||||
|
||||
if let file = videoFile {
|
||||
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource)
|
||||
} else if let imageResource = imageResource {
|
||||
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
|
||||
}
|
||||
case let .internalReference(_, _, _, title, _, image, file, _):
|
||||
case let .internalReference(_, _, _, _, _, image, file, _):
|
||||
if let image = image {
|
||||
if let largestRepresentation = largestImageRepresentation(image.representations) {
|
||||
imageDimensions = largestRepresentation.dimensions
|
||||
}
|
||||
imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 100.0))?.resource
|
||||
if let thumbnailRepresentation = smallestImageRepresentation(image.representations) {
|
||||
thumbnailDimensions = thumbnailRepresentation.dimensions
|
||||
thumbnailResource = thumbnailRepresentation.resource
|
||||
}
|
||||
} else if let file = file {
|
||||
if let dimensions = file.dimensions {
|
||||
imageDimensions = dimensions
|
||||
@ -123,32 +120,24 @@ final class WebSearchItemNode: GridItemNode {
|
||||
}
|
||||
imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource
|
||||
}
|
||||
|
||||
// if let file = file {
|
||||
// if file.isVideo && file.isAnimated {
|
||||
// videoFile = file
|
||||
// imageResource = nil
|
||||
// updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource)
|
||||
// } else if let imageResource = imageResource {
|
||||
// updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
|
||||
// }
|
||||
// } else if let imageResource = imageResource {
|
||||
// updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
|
||||
// }
|
||||
}
|
||||
|
||||
if let imageResource = imageResource, let imageDimensions = imageDimensions {
|
||||
let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource)
|
||||
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], reference: nil, partialReference: nil)
|
||||
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)
|
||||
updateImageSignal = mediaGridMessagePhoto(account: item.account, photoReference: .standalone(media: tmpImage))
|
||||
} else {
|
||||
updateImageSignal = .complete()
|
||||
}
|
||||
|
||||
if let updateImageSignal = updateImageSignal {
|
||||
let editingContext = item.controllerInteraction.editingContext
|
||||
let editingContext = item.controllerInteraction.editingState
|
||||
let editedImageSignal = Signal<UIImage?, NoError> { subscriber in
|
||||
let editableItem = LegacyWebSearchItem(result: item.result, dimensions: CGSize(), thumbnailImage: .complete(), originalImage: .complete())
|
||||
let editableItem = LegacyWebSearchItem(result: item.result)
|
||||
if let signal = editingContext.thumbnailImageSignal(for: editableItem) {
|
||||
let disposable = signal.start(next: { next in
|
||||
if let image = next as? UIImage {
|
||||
@ -205,41 +194,45 @@ final class WebSearchItemNode: GridItemNode {
|
||||
if let _ = imageDimensions {
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
self.updateHiddenMedia()
|
||||
}
|
||||
|
||||
self.item = item
|
||||
self.updateSelectionState(animated: false)
|
||||
}
|
||||
|
||||
@objc func toggleSelection() {
|
||||
if let checkNode = self.checkNode, let item = self.item {
|
||||
checkNode.setIsChecked(!checkNode.isChecked, animated: true)
|
||||
item.controllerInteraction.toggleSelection(item.result, checkNode.isChecked)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSelectionState(animated: Bool) {
|
||||
if self.selectionNode == nil, let item = self.item {
|
||||
let selectionNode = GridMessageSelectionNode(theme: item.theme, toggle: { [weak self] value in
|
||||
if let strongSelf = self, let item = strongSelf.item {
|
||||
item.controllerInteraction.toggleSelection([item.result.id], value)
|
||||
strongSelf.updateSelectionState(animated: true)
|
||||
}
|
||||
})
|
||||
self.addSubnode(selectionNode)
|
||||
self.selectionNode = selectionNode
|
||||
if self.checkNode == nil, let item = self.item, let _ = item.controllerInteraction.selectionState {
|
||||
let checkNode = CheckNode(strokeColor: item.theme.list.itemCheckColors.strokeColor, fillColor: item.theme.list.itemCheckColors.fillColor, foregroundColor: item.theme.list.itemCheckColors.foregroundColor, style: .overlay)
|
||||
checkNode.addTarget(target: self, action: #selector(self.toggleSelection))
|
||||
self.addSubnode(checkNode)
|
||||
self.checkNode = checkNode
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
if let item = self.item {
|
||||
if let selectionState = item.controllerInteraction.selectionState {
|
||||
let selected = selectionState.selectedIds.contains(item.result.id)
|
||||
self.selectionNode?.updateSelected(selected, animated: animated)
|
||||
let selected = selectionState.isIdentifierSelected(item.result.id)
|
||||
self.checkNode?.setIsChecked(selected, animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateHiddenMedia() {
|
||||
if let item = self.item {
|
||||
self.imageNode.isHidden = item.controllerInteraction.hiddenMediaId == item.result.id
|
||||
self.isHidden = item.controllerInteraction.hiddenMediaId == item.result.id
|
||||
}
|
||||
}
|
||||
|
||||
func transitionView() -> UIView {
|
||||
let view = self.imageNode.view.snapshotContentTree(unhide: true)!
|
||||
let view = self.view.snapshotContentTree(unhide: true, keepTransform: true)!
|
||||
view.frame = self.convert(self.bounds, to: nil)
|
||||
return view
|
||||
}
|
||||
@ -256,11 +249,7 @@ final class WebSearchItemNode: GridItemNode {
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
//self.videoAccessoryNode.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - self.videoAccessoryNode.contentSize.width - 5, y: imageFrame.maxY - self.videoAccessoryNode.contentSize.height - 5), size: self.videoAccessoryNode.contentSize)
|
||||
self.checkNode?.frame = CGRect(origin: CGPoint(x: imageFrame.width - checkSize.width, y: 0.0), size: checkSize)
|
||||
}
|
||||
|
||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
@ -274,9 +263,6 @@ final class WebSearchItemNode: GridItemNode {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
item.controllerInteraction.openResult(item.result)
|
||||
|
||||
case .longTap:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
|
||||
private var textNode: TextNode?
|
||||
|
||||
private var item: WebSearchRecentQueryItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
|
||||
required init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
@ -152,6 +153,7 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
|
||||
return (nil, { _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
strongSelf.layoutParams = params
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
|
||||
@ -205,95 +207,16 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
|
||||
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
super.updateRevealOffset(offset: offset, transition: transition)
|
||||
|
||||
// if let _ = self.item, let params = self.layoutParams?.5 {
|
||||
// let editingOffset: CGFloat
|
||||
// if let selectableControlNode = self.selectableControlNode {
|
||||
// editingOffset = selectableControlNode.bounds.size.width
|
||||
// var selectableControlFrame = selectableControlNode.frame
|
||||
// selectableControlFrame.origin.x = params.leftInset + offset
|
||||
// transition.updateFrame(node: selectableControlNode, frame: selectableControlFrame)
|
||||
// } else {
|
||||
// editingOffset = 0.0
|
||||
// }
|
||||
//
|
||||
// if let reorderControlNode = self.reorderControlNode {
|
||||
// var reorderControlFrame = reorderControlNode.frame
|
||||
// reorderControlFrame.origin.x = params.width - params.rightInset - reorderControlFrame.size.width + offset
|
||||
// transition.updateFrame(node: reorderControlNode, frame: reorderControlFrame)
|
||||
// }
|
||||
//
|
||||
// let leftInset: CGFloat = params.leftInset + 78.0
|
||||
//
|
||||
// let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: 8.0), size: CGSize(width: params.width - leftInset - params.rightInset - 10.0 - 1.0 - editingOffset, height: itemHeight - 12.0 - 9.0))
|
||||
//
|
||||
// let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + offset, dy: 0.0)
|
||||
//
|
||||
// var avatarFrame = self.avatarNode.frame
|
||||
// avatarFrame.origin.x = leftInset - 78.0 + editingOffset + 10.0 + offset
|
||||
// transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
|
||||
// if let multipleAvatarsNode = self.multipleAvatarsNode {
|
||||
// transition.updateFrame(node: multipleAvatarsNode, frame: avatarFrame)
|
||||
// }
|
||||
//
|
||||
// var titleOffset: CGFloat = 0.0
|
||||
// if let secretIconNode = self.secretIconNode, let image = secretIconNode.image {
|
||||
// transition.updateFrame(node: secretIconNode, frame: CGRect(origin: CGPoint(x: contentRect.minX, y: secretIconNode.frame.minY), size: image.size))
|
||||
// titleOffset += image.size.width + 3.0
|
||||
// }
|
||||
//
|
||||
// let titleFrame = self.titleNode.frame
|
||||
// transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + titleOffset, y: titleFrame.origin.y), size: titleFrame.size))
|
||||
//
|
||||
// let authorFrame = self.authorNode.frame
|
||||
// transition.updateFrame(node: self.authorNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: authorFrame.origin.y), size: authorFrame.size))
|
||||
//
|
||||
// transition.updateFrame(node: self.inputActivitiesNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: self.inputActivitiesNode.frame.minY), size: self.inputActivitiesNode.bounds.size))
|
||||
//
|
||||
// let textFrame = self.textNode.frame
|
||||
// transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: textFrame.origin.y), size: textFrame.size))
|
||||
//
|
||||
// let dateFrame = self.dateNode.frame
|
||||
// transition.updateFrame(node: self.dateNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateFrame.size.width, y: dateFrame.minY), size: dateFrame.size))
|
||||
//
|
||||
// let statusFrame = self.statusNode.frame
|
||||
// transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateFrame.size.width - 2.0 - statusFrame.size.width, y: statusFrame.minY), size: statusFrame.size))
|
||||
//
|
||||
// var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleFrame.size.width + 3.0 + titleOffset
|
||||
//
|
||||
// if let verificationIconNode = self.verificationIconNode {
|
||||
// transition.updateFrame(node: verificationIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: verificationIconNode.frame.origin.y), size: verificationIconNode.bounds.size))
|
||||
// nextTitleIconOrigin += verificationIconNode.bounds.size.width + 5.0
|
||||
// }
|
||||
//
|
||||
// let mutedIconFrame = self.mutedIconNode.frame
|
||||
// transition.updateFrame(node: self.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: contentRect.origin.y + 6.0), size: mutedIconFrame.size))
|
||||
// nextTitleIconOrigin += mutedIconFrame.size.width + 3.0
|
||||
//
|
||||
// let badgeBackgroundFrame = self.badgeBackgroundNode.frame
|
||||
// let updatedBadgeBackgroundFrame = CGRect(origin: CGPoint(x: contentRect.maxX - badgeBackgroundFrame.size.width, y: contentRect.maxY - badgeBackgroundFrame.size.height - 2.0), size: badgeBackgroundFrame.size)
|
||||
// transition.updateFrame(node: self.badgeBackgroundNode, frame: updatedBadgeBackgroundFrame)
|
||||
//
|
||||
// if self.mentionBadgeNode.supernode != nil {
|
||||
// let mentionBadgeSize = self.mentionBadgeNode.bounds.size
|
||||
// let mentionBadgeOffset: CGFloat
|
||||
// if updatedBadgeBackgroundFrame.size.width.isZero || self.badgeBackgroundNode.image == nil {
|
||||
// mentionBadgeOffset = contentRect.maxX - mentionBadgeSize.width
|
||||
// } else {
|
||||
// mentionBadgeOffset = contentRect.maxX - updatedBadgeBackgroundFrame.size.width - 6.0 - mentionBadgeSize.width
|
||||
// }
|
||||
//
|
||||
// let badgeBackgroundWidth = mentionBadgeSize.width
|
||||
// let badgeBackgroundFrame = CGRect(x: mentionBadgeOffset, y: self.mentionBadgeNode.frame.origin.y, width: badgeBackgroundWidth, height: mentionBadgeSize.height)
|
||||
// transition.updateFrame(node: self.mentionBadgeNode, frame: badgeBackgroundFrame)
|
||||
// }
|
||||
//
|
||||
// let badgeTextFrame = self.badgeTextNode.frame
|
||||
// transition.updateFrame(node: self.badgeTextNode, frame: CGRect(origin: CGPoint(x: updatedBadgeBackgroundFrame.midX - badgeTextFrame.size.width / 2.0, y: badgeTextFrame.minY), size: badgeTextFrame.size))
|
||||
// }
|
||||
if let params = self.layoutParams, let textNode = self.textNode {
|
||||
let leftInset: CGFloat = 15.0 + params.leftInset
|
||||
|
||||
var textFrame = textNode.frame
|
||||
textFrame.origin.x = leftInset + offset
|
||||
transition.updateFrame(node: textNode, frame: textFrame)
|
||||
}
|
||||
}
|
||||
|
||||
override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
|
||||
var close = true
|
||||
if let item = self.item {
|
||||
switch option.key {
|
||||
case RevealOptionKey.delete.rawValue:
|
||||
@ -302,9 +225,7 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
|
||||
break
|
||||
}
|
||||
}
|
||||
if close {
|
||||
self.setRevealOptionsOpened(false, animated: true)
|
||||
self.revealOptionsInteractivelyClosed()
|
||||
}
|
||||
self.setRevealOptionsOpened(false, animated: true)
|
||||
self.revealOptionsInteractivelyClosed()
|
||||
}
|
||||
}
|
||||
|
@ -3,22 +3,22 @@ import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
struct WebSearchSettings: Equatable, PreferencesEntry {
|
||||
var mode: WebSearchMode
|
||||
var scope: WebSearchScope
|
||||
|
||||
static var defaultSettings: WebSearchSettings {
|
||||
return WebSearchSettings(mode: .images)
|
||||
return WebSearchSettings(scope: .images)
|
||||
}
|
||||
|
||||
init(mode: WebSearchMode) {
|
||||
self.mode = mode
|
||||
init(scope: WebSearchScope) {
|
||||
self.scope = scope
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.mode = WebSearchMode(rawValue: decoder.decodeInt32ForKey("mode", orElse: 0)) ?? .images
|
||||
self.scope = WebSearchScope(rawValue: decoder.decodeInt32ForKey("scope", orElse: 0)) ?? .images
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.mode.rawValue, forKey: "mode")
|
||||
encoder.encodeInt32(self.scope.rawValue, forKey: "scope")
|
||||
}
|
||||
|
||||
func isEqual(to: PreferencesEntry) -> Bool {
|
||||
|
505
TelegramUI/WebSearchVideoGalleryItem.swift
Normal file
505
TelegramUI/WebSearchVideoGalleryItem.swift
Normal file
@ -0,0 +1,505 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import Display
|
||||
import Postbox
|
||||
|
||||
class WebSearchVideoGalleryItem: GalleryItem {
|
||||
let account: Account
|
||||
let presentationData: PresentationData
|
||||
let content: UniversalVideoContent
|
||||
|
||||
init(account: Account, presentationData: PresentationData, content: UniversalVideoContent) {
|
||||
self.account = account
|
||||
self.presentationData = presentationData
|
||||
self.content = content
|
||||
}
|
||||
|
||||
func node() -> GalleryItemNode {
|
||||
let node = WebSearchVideoGalleryItemNode(account: self.account, presentationData: self.presentationData)
|
||||
node.setupItem(self)
|
||||
return node
|
||||
}
|
||||
|
||||
func updateNode(node: GalleryItemNode) {
|
||||
if let node = node as? WebSearchVideoGalleryItemNode {
|
||||
node.setupItem(self)
|
||||
}
|
||||
}
|
||||
|
||||
func thumbnailItem() -> (Int64, GalleryThumbnailItem)? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private struct FetchControls {
|
||||
let fetch: () -> Void
|
||||
let cancel: () -> Void
|
||||
}
|
||||
|
||||
final class WebSearchVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private let account: Account
|
||||
private let strings: PresentationStrings
|
||||
|
||||
fileprivate let _ready = Promise<Void>()
|
||||
|
||||
private let footerContentNode: WebSearchGalleryFooterContentNode
|
||||
|
||||
private var videoNode: UniversalVideoNode?
|
||||
private let statusButtonNode: HighlightableButtonNode
|
||||
private let statusNode: RadialStatusNode
|
||||
|
||||
private var isCentral = false
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
private var didPause = false
|
||||
private var isPaused = true
|
||||
|
||||
private var requiresDownload = false
|
||||
|
||||
private var item: WebSearchVideoGalleryItem?
|
||||
|
||||
private let statusDisposable = MetaDisposable()
|
||||
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
private var fetchStatus: MediaResourceStatus?
|
||||
private var fetchControls: FetchControls?
|
||||
|
||||
var playbackCompleted: (() -> Void)?
|
||||
|
||||
init(account: Account, presentationData: PresentationData) {
|
||||
self.account = account
|
||||
self.strings = presentationData.strings
|
||||
|
||||
self.footerContentNode = WebSearchGalleryFooterContentNode(account: account, presentationData: presentationData)
|
||||
|
||||
self.statusButtonNode = HighlightableButtonNode()
|
||||
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
|
||||
|
||||
super.init()
|
||||
|
||||
self.statusButtonNode.addSubnode(self.statusNode)
|
||||
self.statusButtonNode.addTarget(self, action: #selector(statusButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.addSubnode(self.statusButtonNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.statusDisposable.dispose()
|
||||
}
|
||||
|
||||
override func ready() -> Signal<Void, NoError> {
|
||||
return self._ready.get()
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
|
||||
self.validLayout = (layout, navigationBarHeight)
|
||||
|
||||
let statusDiameter: CGFloat = 50.0
|
||||
let statusFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - statusDiameter) / 2.0), y: floor((layout.size.height - statusDiameter) / 2.0)), size: CGSize(width: statusDiameter, height: statusDiameter))
|
||||
transition.updateFrame(node: self.statusButtonNode, frame: statusFrame)
|
||||
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusFrame.size))
|
||||
}
|
||||
|
||||
func setupItem(_ item: WebSearchVideoGalleryItem) {
|
||||
if self.item?.content.id != item.content.id {
|
||||
var isAnimated = false
|
||||
var mediaResource: MediaResource?
|
||||
if let content = item.content as? NativeVideoContent {
|
||||
isAnimated = content.fileReference.media.isAnimated
|
||||
mediaResource = content.fileReference.media.resource
|
||||
}
|
||||
|
||||
if let videoNode = self.videoNode {
|
||||
videoNode.canAttachContent = false
|
||||
videoNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
guard let mediaManager = item.account.telegramApplicationContext.mediaManager else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
let videoNode = UniversalVideoNode(postbox: item.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .gallery)
|
||||
let videoSize = CGSize(width: item.content.dimensions.width * 2.0, height: item.content.dimensions.height * 2.0)
|
||||
videoNode.updateLayout(size: videoSize, transition: .immediate)
|
||||
self.videoNode = videoNode
|
||||
videoNode.isUserInteractionEnabled = false
|
||||
videoNode.backgroundColor = videoNode.ownsContentNode ? UIColor.black : UIColor(rgb: 0x333335)
|
||||
videoNode.canAttachContent = true
|
||||
|
||||
self.requiresDownload = true
|
||||
var mediaFileStatus: Signal<MediaResourceStatus?, NoError> = .single(nil)
|
||||
if let mediaResource = mediaResource {
|
||||
mediaFileStatus = item.account.postbox.mediaBox.resourceStatus(mediaResource)
|
||||
|> map(Optional.init)
|
||||
}
|
||||
|
||||
self.statusDisposable.set((combineLatest(videoNode.status, mediaFileStatus)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value, fetchStatus in
|
||||
if let strongSelf = self {
|
||||
var initialBuffering = false
|
||||
var isPaused = true
|
||||
if let value = value {
|
||||
if let zoomableContent = strongSelf.zoomableContent, !value.dimensions.width.isZero && !value.dimensions.height.isZero {
|
||||
let videoSize = CGSize(width: value.dimensions.width * 2.0, height: value.dimensions.height * 2.0)
|
||||
if !zoomableContent.0.equalTo(videoSize) {
|
||||
strongSelf.zoomableContent = (videoSize, zoomableContent.1)
|
||||
strongSelf.videoNode?.updateLayout(size: videoSize, transition: .immediate)
|
||||
}
|
||||
}
|
||||
switch value.status {
|
||||
case .playing:
|
||||
isPaused = false
|
||||
case let .buffering(_, whilePlaying):
|
||||
initialBuffering = true
|
||||
isPaused = !whilePlaying
|
||||
var isStreaming = false
|
||||
if let fetchStatus = strongSelf.fetchStatus {
|
||||
switch fetchStatus {
|
||||
case .Local:
|
||||
break
|
||||
default:
|
||||
isStreaming = true
|
||||
}
|
||||
}
|
||||
if let content = item.content as? NativeVideoContent, !isStreaming {
|
||||
initialBuffering = false
|
||||
if !content.enableSound {
|
||||
isPaused = false
|
||||
}
|
||||
}
|
||||
default:
|
||||
if let content = item.content as? NativeVideoContent, !content.streamVideo {
|
||||
if !content.enableSound {
|
||||
isPaused = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fetching = false
|
||||
if initialBuffering {
|
||||
strongSelf.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: nil, cancelEnabled: false), animated: false, completion: {})
|
||||
} else {
|
||||
var state: RadialStatusNodeState = .play(.white)
|
||||
|
||||
if let fetchStatus = fetchStatus {
|
||||
if strongSelf.requiresDownload {
|
||||
switch fetchStatus {
|
||||
case .Remote:
|
||||
state = .download(.white)
|
||||
case let .Fetching(_, progress):
|
||||
fetching = true
|
||||
isPaused = true
|
||||
state = .progress(color: .white, lineWidth: nil, value: CGFloat(progress), cancelEnabled: false)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
strongSelf.statusNode.transitionToState(state, animated: false, completion: {})
|
||||
}
|
||||
|
||||
strongSelf.isPaused = isPaused
|
||||
strongSelf.fetchStatus = fetchStatus
|
||||
|
||||
strongSelf.statusButtonNode.isHidden = !initialBuffering && !isPaused && !fetching
|
||||
}
|
||||
}))
|
||||
|
||||
self.zoomableContent = (videoSize, videoNode)
|
||||
|
||||
|
||||
videoNode.playbackCompleted = { [weak videoNode] in
|
||||
Queue.mainQueue().async {
|
||||
//item.playbackCompleted()
|
||||
if !isAnimated {
|
||||
videoNode?.seek(0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self._ready.set(videoNode.ready)
|
||||
}
|
||||
|
||||
self.item = item
|
||||
}
|
||||
|
||||
override func centralityUpdated(isCentral: Bool) {
|
||||
super.centralityUpdated(isCentral: isCentral)
|
||||
|
||||
if self.isCentral != isCentral {
|
||||
self.isCentral = isCentral
|
||||
|
||||
if let videoNode = self.videoNode {
|
||||
if isCentral {
|
||||
} else if videoNode.ownsContentNode {
|
||||
videoNode.pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func activateAsInitial() {
|
||||
if self.isCentral {
|
||||
self.videoNode?.play()
|
||||
}
|
||||
}
|
||||
|
||||
override func animateIn(from node: (ASDisplayNode, () -> UIView?), addToTransitionSurface: (UIView) -> Void) {
|
||||
guard let videoNode = self.videoNode else {
|
||||
return
|
||||
}
|
||||
|
||||
if let node = node.0 as? OverlayMediaItemNode {
|
||||
var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view)
|
||||
let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview)
|
||||
|
||||
videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
transformedFrame.origin = CGPoint()
|
||||
|
||||
let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0)
|
||||
videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25)
|
||||
|
||||
self.account.telegramApplicationContext.mediaManager?.setOverlayVideoNode(nil)
|
||||
} else {
|
||||
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view)
|
||||
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view.superview)
|
||||
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)
|
||||
let transformedCopyViewFinalFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view)
|
||||
|
||||
let surfaceCopyView = node.1()!
|
||||
let copyView = node.1()!
|
||||
|
||||
addToTransitionSurface(surfaceCopyView)
|
||||
|
||||
var transformedSurfaceFrame: CGRect?
|
||||
var transformedSurfaceFinalFrame: CGRect?
|
||||
if let contentSurface = surfaceCopyView.superview {
|
||||
transformedSurfaceFrame = node.0.view.convert(node.0.view.bounds, to: contentSurface)
|
||||
transformedSurfaceFinalFrame = videoNode.view.convert(videoNode.view.bounds, to: contentSurface)
|
||||
}
|
||||
|
||||
if let transformedSurfaceFrame = transformedSurfaceFrame {
|
||||
surfaceCopyView.frame = transformedSurfaceFrame
|
||||
}
|
||||
|
||||
self.view.insertSubview(copyView, belowSubview: self.scrollNode.view)
|
||||
copyView.frame = transformedSelfFrame
|
||||
|
||||
copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false)
|
||||
|
||||
surfaceCopyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
|
||||
copyView.layer.animatePosition(from: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak copyView] _ in
|
||||
copyView?.removeFromSuperview()
|
||||
})
|
||||
let scale = CGSize(width: transformedCopyViewFinalFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewFinalFrame.size.height / transformedSelfFrame.size.height)
|
||||
copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
|
||||
|
||||
if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedSurfaceFinalFrame = transformedSurfaceFinalFrame {
|
||||
surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), to: CGPoint(x: transformedCopyViewFinalFrame.midX, y: transformedCopyViewFinalFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak surfaceCopyView] _ in
|
||||
surfaceCopyView?.removeFromSuperview()
|
||||
})
|
||||
let scale = CGSize(width: transformedSurfaceFinalFrame.size.width / transformedSurfaceFrame.size.width, height: transformedSurfaceFinalFrame.size.height / transformedSurfaceFrame.size.height)
|
||||
surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
videoNode.allowsGroupOpacity = true
|
||||
videoNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak videoNode] _ in
|
||||
videoNode?.allowsGroupOpacity = false
|
||||
})
|
||||
videoNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: videoNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
transformedFrame.origin = CGPoint()
|
||||
|
||||
let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0)
|
||||
videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25)
|
||||
|
||||
self.statusButtonNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.statusButtonNode.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.statusButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.statusButtonNode.layer.animateScale(from: 0.5, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
}
|
||||
|
||||
override func animateOut(to node: (ASDisplayNode, () -> UIView?), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
|
||||
guard let videoNode = self.videoNode else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view)
|
||||
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view.superview)
|
||||
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)
|
||||
let transformedCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view)
|
||||
|
||||
var positionCompleted = false
|
||||
var boundsCompleted = false
|
||||
var copyCompleted = false
|
||||
|
||||
let copyView = node.1()!
|
||||
let surfaceCopyView = node.1()!
|
||||
|
||||
addToTransitionSurface(surfaceCopyView)
|
||||
|
||||
var transformedSurfaceFrame: CGRect?
|
||||
var transformedSurfaceCopyViewInitialFrame: CGRect?
|
||||
if let contentSurface = surfaceCopyView.superview {
|
||||
transformedSurfaceFrame = node.0.view.convert(node.0.view.bounds, to: contentSurface)
|
||||
transformedSurfaceCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: contentSurface)
|
||||
}
|
||||
|
||||
self.view.insertSubview(copyView, belowSubview: self.scrollNode.view)
|
||||
copyView.frame = transformedSelfFrame
|
||||
|
||||
let intermediateCompletion = { [weak copyView, weak surfaceCopyView] in
|
||||
if positionCompleted && boundsCompleted && copyCompleted {
|
||||
copyView?.removeFromSuperview()
|
||||
surfaceCopyView?.removeFromSuperview()
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false)
|
||||
surfaceCopyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, removeOnCompletion: false)
|
||||
|
||||
copyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSelfFrame.size.height)
|
||||
copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
copyCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
if let transformedSurfaceFrame = transformedSurfaceFrame, let transformedCopyViewInitialFrame = transformedSurfaceCopyViewInitialFrame {
|
||||
surfaceCopyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSurfaceFrame.midX, y: transformedSurfaceFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSurfaceFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSurfaceFrame.size.height)
|
||||
surfaceCopyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
videoNode.layer.animatePosition(from: videoNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
positionCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
videoNode.allowsGroupOpacity = true
|
||||
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak videoNode] _ in
|
||||
videoNode?.allowsGroupOpacity = false
|
||||
})
|
||||
|
||||
self.statusButtonNode.layer.animatePosition(from: self.statusButtonNode.layer.position, to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
//positionCompleted = true
|
||||
//intermediateCompletion()
|
||||
})
|
||||
self.statusButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
self.statusButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false)
|
||||
|
||||
transformedFrame.origin = CGPoint()
|
||||
|
||||
let transform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0)
|
||||
videoNode.layer.animate(from: NSValue(caTransform3D: videoNode.layer.transform), to: NSValue(caTransform3D: transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
boundsCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
}
|
||||
|
||||
func animateOut(toOverlay node: ASDisplayNode, completion: @escaping () -> Void) {
|
||||
guard let videoNode = self.videoNode else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view)
|
||||
let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview)
|
||||
let transformedSelfFrame = node.view.convert(node.view.bounds, to: self.view)
|
||||
let transformedCopyViewInitialFrame = videoNode.view.convert(videoNode.view.bounds, to: self.view)
|
||||
let transformedSelfTargetSuperFrame = videoNode.view.convert(videoNode.view.bounds, to: node.view.superview)
|
||||
|
||||
var positionCompleted = false
|
||||
var boundsCompleted = false
|
||||
var copyCompleted = false
|
||||
var nodeCompleted = false
|
||||
|
||||
let copyView = node.view.snapshotContentTree()!
|
||||
|
||||
videoNode.isHidden = true
|
||||
copyView.frame = transformedSelfFrame
|
||||
|
||||
let intermediateCompletion = { [weak copyView] in
|
||||
if positionCompleted && boundsCompleted && copyCompleted && nodeCompleted {
|
||||
copyView?.removeFromSuperview()
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
copyView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, removeOnCompletion: false)
|
||||
|
||||
copyView.layer.animatePosition(from: CGPoint(x: transformedCopyViewInitialFrame.midX, y: transformedCopyViewInitialFrame.midY), to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
let scale = CGSize(width: transformedCopyViewInitialFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewInitialFrame.size.height / transformedSelfFrame.size.height)
|
||||
copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), to: NSValue(caTransform3D: CATransform3DIdentity), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
copyCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
videoNode.layer.animatePosition(from: videoNode.layer.position, to: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
positionCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
videoNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
|
||||
self.statusButtonNode.layer.animatePosition(from: self.statusButtonNode.layer.position, to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
//positionCompleted = true
|
||||
//intermediateCompletion()
|
||||
})
|
||||
self.statusButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
self.statusButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false)
|
||||
|
||||
transformedFrame.origin = CGPoint()
|
||||
|
||||
let videoTransform = CATransform3DScale(videoNode.layer.transform, transformedFrame.size.width / videoNode.layer.bounds.size.width, transformedFrame.size.height / videoNode.layer.bounds.size.height, 1.0)
|
||||
videoNode.layer.animate(from: NSValue(caTransform3D: videoNode.layer.transform), to: NSValue(caTransform3D: videoTransform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
boundsCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
let nodeTransform = CATransform3DScale(node.layer.transform, videoNode.layer.bounds.size.width / transformedFrame.size.width, videoNode.layer.bounds.size.height / transformedFrame.size.height, 1.0)
|
||||
node.layer.animatePosition(from: CGPoint(x: transformedSelfTargetSuperFrame.midX, y: transformedSelfTargetSuperFrame.midY), to: node.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
node.layer.animate(from: NSValue(caTransform3D: nodeTransform), to: NSValue(caTransform3D: node.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
nodeCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
}
|
||||
|
||||
@objc func statusButtonPressed() {
|
||||
if let videoNode = self.videoNode {
|
||||
if let fetchStatus = self.fetchStatus, case .Local = fetchStatus {
|
||||
self.toggleControlsVisibility()
|
||||
}
|
||||
|
||||
if let fetchStatus = self.fetchStatus {
|
||||
switch fetchStatus {
|
||||
case .Local:
|
||||
videoNode.togglePlayPause()
|
||||
case .Remote:
|
||||
if self.requiresDownload {
|
||||
self.fetchControls?.fetch()
|
||||
} else {
|
||||
videoNode.togglePlayPause()
|
||||
}
|
||||
case .Fetching:
|
||||
self.fetchControls?.cancel()
|
||||
}
|
||||
} else {
|
||||
videoNode.togglePlayPause()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func footerContent() -> Signal<GalleryFooterContentNode?, NoError> {
|
||||
return .single(self.footerContentNode)
|
||||
}
|
||||
}
|
@ -72,11 +72,11 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
}
|
||||
|
||||
if let text = self.titleString?.string {
|
||||
titleString = NSAttributedString(string: text, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)
|
||||
self.titleString = NSAttributedString(string: text, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)
|
||||
}
|
||||
|
||||
if let text = self.textString?.string {
|
||||
textString = NSAttributedString(string: text, font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.primaryTextColor)
|
||||
self.textString = NSAttributedString(string: text, font: Font.regular(15.0), textColor: self.theme.chat.inputPanel.primaryTextColor)
|
||||
}
|
||||
|
||||
self.updateWebpage()
|
||||
@ -101,6 +101,16 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
authorName = self.strings.Channel_NotificationLoading
|
||||
text = self.url
|
||||
case let .Loaded(content):
|
||||
if let contentText = content.text {
|
||||
text = contentText
|
||||
} else {
|
||||
if let file = content.file, let mediaKind = mediaContentKind(file) {
|
||||
text = stringForMediaKind(mediaKind, strings: self.strings).0
|
||||
} else if let _ = content.image {
|
||||
text = stringForMediaKind(.image, strings: self.strings).0
|
||||
}
|
||||
}
|
||||
|
||||
if let title = content.title {
|
||||
authorName = title
|
||||
} else if let websiteName = content.websiteName {
|
||||
@ -108,7 +118,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
|
||||
} else {
|
||||
authorName = content.displayUrl
|
||||
}
|
||||
text = content.text ?? ""
|
||||
|
||||
}
|
||||
|
||||
self.titleString = NSAttributedString(string: authorName, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)
|
||||
|
Loading…
x
Reference in New Issue
Block a user