Web Search improvements

This commit is contained in:
Ilya Laktyushin 2018-12-18 11:52:33 +04:00
parent a57e0b34af
commit 81e0345f89
48 changed files with 4113 additions and 3127 deletions

View File

@ -92,6 +92,12 @@
09DD88FA21BFD70B000766BC /* ThemedTextAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88F921BFD70B000766BC /* ThemedTextAlertController.swift */; }; 09DD88FA21BFD70B000766BC /* ThemedTextAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88F921BFD70B000766BC /* ThemedTextAlertController.swift */; };
09F799FA21C3542D00820234 /* LegacyWebSearchGallery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F799F921C3542D00820234 /* LegacyWebSearchGallery.swift */; }; 09F799FA21C3542D00820234 /* LegacyWebSearchGallery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F799F921C3542D00820234 /* LegacyWebSearchGallery.swift */; };
09F799FC21C3FF3000820234 /* WebSearchGalleryController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F799FB21C3FF3000820234 /* WebSearchGalleryController.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 */; }; 09FE756D2153F5F900A3120F /* CallRouteActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */; };
9F06830921A404AB001D8EDB /* NotificationExceptionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.swift */; }; 9F06830921A404AB001D8EDB /* NotificationExceptionControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.swift */; };
9F06830B21A404C4001D8EDB /* NotificationExcetionSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830A21A404C4001D8EDB /* NotificationExcetionSettingsController.swift */; }; 9F06830B21A404C4001D8EDB /* NotificationExcetionSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830A21A404C4001D8EDB /* NotificationExcetionSettingsController.swift */; };
@ -1186,6 +1192,12 @@
09DD88F921BFD70B000766BC /* ThemedTextAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedTextAlertController.swift; sourceTree = "<group>"; }; 09DD88F921BFD70B000766BC /* ThemedTextAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedTextAlertController.swift; sourceTree = "<group>"; };
09F799F921C3542D00820234 /* LegacyWebSearchGallery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyWebSearchGallery.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 09FE756C2153F5F900A3120F /* CallRouteActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallRouteActionSheetItem.swift; sourceTree = "<group>"; };
9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExceptionControllerNode.swift; sourceTree = "<group>"; }; 9F06830821A404AB001D8EDB /* NotificationExceptionControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExceptionControllerNode.swift; sourceTree = "<group>"; };
9F06830A21A404C4001D8EDB /* NotificationExcetionSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExcetionSettingsController.swift; sourceTree = "<group>"; }; 9F06830A21A404C4001D8EDB /* NotificationExcetionSettingsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationExcetionSettingsController.swift; sourceTree = "<group>"; };
@ -2373,6 +2385,10 @@
09DD88F221BF907C000766BC /* WebSearchRecentQueryItem.swift */, 09DD88F221BF907C000766BC /* WebSearchRecentQueryItem.swift */,
09DD88F421BF9730000766BC /* WebSearchRecentQueries.swift */, 09DD88F421BF9730000766BC /* WebSearchRecentQueries.swift */,
09F799FB21C3FF3000820234 /* WebSearchGalleryController.swift */, 09F799FB21C3FF3000820234 /* WebSearchGalleryController.swift */,
09F79A0021C8116C00820234 /* CountBadgeNode.swift */,
09F79A0621C829BC00820234 /* GalleryNavigationCheckNode.swift */,
09F79A0821C829C700820234 /* GalleryNavigationRecipientNode.swift */,
09F79A0A21C832F400820234 /* WebSearchGalleryFooterContentNode.swift */,
); );
name = "Web Search"; name = "Web Search";
sourceTree = "<group>"; sourceTree = "<group>";
@ -3185,6 +3201,7 @@
D0380DAC204ED434000414AB /* LegacyLiveUploadInterface.swift */, D0380DAC204ED434000414AB /* LegacyLiveUploadInterface.swift */,
D097C26B20DD1EA5007BB4B8 /* OverlayStatusController.swift */, D097C26B20DD1EA5007BB4B8 /* OverlayStatusController.swift */,
09F799F921C3542D00820234 /* LegacyWebSearchGallery.swift */, 09F799F921C3542D00820234 /* LegacyWebSearchGallery.swift */,
09F79A0C21C88E8900820234 /* LegacyWebSearchEditor.swift */,
); );
name = "Legacy Components"; name = "Legacy Components";
sourceTree = "<group>"; sourceTree = "<group>";
@ -4516,6 +4533,7 @@
D0104F291F471DA6004E4881 /* InstantImageGalleryItem.swift */, D0104F291F471DA6004E4881 /* InstantImageGalleryItem.swift */,
D0104F2B1F471EEB004E4881 /* InstantPageGalleryFooterContentNode.swift */, D0104F2B1F471EEB004E4881 /* InstantPageGalleryFooterContentNode.swift */,
D0A8BBA01F61EE83000F03FD /* UniversalVideoGalleryItem.swift */, D0A8BBA01F61EE83000F03FD /* UniversalVideoGalleryItem.swift */,
09F79A0221C8225600820234 /* WebSearchVideoGalleryItem.swift */,
); );
name = Items; name = Items;
sourceTree = "<group>"; sourceTree = "<group>";
@ -5034,6 +5052,7 @@
D0B2F76820528E3D00D3BFB9 /* UserInfoEditingPhoneActionItem.swift in Sources */, D0B2F76820528E3D00D3BFB9 /* UserInfoEditingPhoneActionItem.swift in Sources */,
D0EC6CB71EB9F58800EBF1C3 /* RMIntroPageView.m in Sources */, D0EC6CB71EB9F58800EBF1C3 /* RMIntroPageView.m in Sources */,
D0EC6CB81EB9F58800EBF1C3 /* RMIntroViewController.m in Sources */, D0EC6CB81EB9F58800EBF1C3 /* RMIntroViewController.m in Sources */,
09F79A0321C8225600820234 /* WebSearchVideoGalleryItem.swift in Sources */,
D0EC6CB91EB9F58800EBF1C3 /* RMLoginViewController.m in Sources */, D0EC6CB91EB9F58800EBF1C3 /* RMLoginViewController.m in Sources */,
D0E9BA631F055AD200F079A4 /* BotPaymentCardInputItemNode.swift in Sources */, D0E9BA631F055AD200F079A4 /* BotPaymentCardInputItemNode.swift in Sources */,
D01848E821A03BDA00B6DEBD /* ChatSearchState.swift in Sources */, D01848E821A03BDA00B6DEBD /* ChatSearchState.swift in Sources */,
@ -5221,6 +5240,7 @@
D0EC6D131EB9F58800EBF1C3 /* MediaTrackDecodableFrame.swift in Sources */, D0EC6D131EB9F58800EBF1C3 /* MediaTrackDecodableFrame.swift in Sources */,
D0EC6D141EB9F58800EBF1C3 /* MediaTrackFrame.swift in Sources */, D0EC6D141EB9F58800EBF1C3 /* MediaTrackFrame.swift in Sources */,
D0B69C3920EBB397003632C7 /* ChatMessageInteractiveInstantVideoNode.swift in Sources */, D0B69C3920EBB397003632C7 /* ChatMessageInteractiveInstantVideoNode.swift in Sources */,
09F79A0D21C88E8900820234 /* LegacyWebSearchEditor.swift in Sources */,
D0EC6D151EB9F58800EBF1C3 /* MediaTrackFrameBuffer.swift in Sources */, D0EC6D151EB9F58800EBF1C3 /* MediaTrackFrameBuffer.swift in Sources */,
D0EC6D161EB9F58800EBF1C3 /* MediaTrackFrameDecoder.swift in Sources */, D0EC6D161EB9F58800EBF1C3 /* MediaTrackFrameDecoder.swift in Sources */,
D056CD701FF147B000880D28 /* IconButtonNode.swift in Sources */, D056CD701FF147B000880D28 /* IconButtonNode.swift in Sources */,
@ -5301,6 +5321,7 @@
D087BFB31F748752003FD209 /* ShareControllerRecentPeersGridItem.swift in Sources */, D087BFB31F748752003FD209 /* ShareControllerRecentPeersGridItem.swift in Sources */,
D0EC6D341EB9F58800EBF1C3 /* AvatarNode.swift in Sources */, D0EC6D341EB9F58800EBF1C3 /* AvatarNode.swift in Sources */,
D08D7E8420A0F6020005D80C /* ExperimentalUISettings.swift in Sources */, D08D7E8420A0F6020005D80C /* ExperimentalUISettings.swift in Sources */,
09F79A0921C829C700820234 /* GalleryNavigationRecipientNode.swift in Sources */,
D0EC6D351EB9F58800EBF1C3 /* SearchBarNode.swift in Sources */, D0EC6D351EB9F58800EBF1C3 /* SearchBarNode.swift in Sources */,
D0EC6D361EB9F58800EBF1C3 /* SearchBarPlaceholderNode.swift in Sources */, D0EC6D361EB9F58800EBF1C3 /* SearchBarPlaceholderNode.swift in Sources */,
D0E8B8B9204477B600605593 /* SecretChatKeyVisualization.swift in Sources */, D0E8B8B9204477B600605593 /* SecretChatKeyVisualization.swift in Sources */,
@ -5638,6 +5659,7 @@
D0F0AAE61EC21B68005EE2A5 /* CallControllerButton.swift in Sources */, D0F0AAE61EC21B68005EE2A5 /* CallControllerButton.swift in Sources */,
D0EC6DDE1EB9F58900EBF1C3 /* ChatTextInputAudioRecordingOverlayButton.swift in Sources */, D0EC6DDE1EB9F58900EBF1C3 /* ChatTextInputAudioRecordingOverlayButton.swift in Sources */,
D0E9BAC91F05738600F079A4 /* STPAPIClient+ApplePay.m in Sources */, D0E9BAC91F05738600F079A4 /* STPAPIClient+ApplePay.m in Sources */,
09F79A0721C829BC00820234 /* GalleryNavigationCheckNode.swift in Sources */,
D0EC6DDF1EB9F58900EBF1C3 /* ChatTextInputAudioRecordingTimeNode.swift in Sources */, D0EC6DDF1EB9F58900EBF1C3 /* ChatTextInputAudioRecordingTimeNode.swift in Sources */,
D0BFAE5B20AB35D200793CF2 /* IconSwitchNode.swift in Sources */, D0BFAE5B20AB35D200793CF2 /* IconSwitchNode.swift in Sources */,
D0EC6DE01EB9F58900EBF1C3 /* ChatTextInputAudioRecordingCancelIndicator.swift in Sources */, D0EC6DE01EB9F58900EBF1C3 /* ChatTextInputAudioRecordingCancelIndicator.swift in Sources */,
@ -5842,6 +5864,7 @@
D09250061FE5371D003F693F /* GlobalExperimentalSettings.swift in Sources */, D09250061FE5371D003F693F /* GlobalExperimentalSettings.swift in Sources */,
D0A24D281F92C27100584D24 /* DefaultDarkAccentPresentationTheme.swift in Sources */, D0A24D281F92C27100584D24 /* DefaultDarkAccentPresentationTheme.swift in Sources */,
D025A4231F79344500563950 /* FetchManager.swift in Sources */, D025A4231F79344500563950 /* FetchManager.swift in Sources */,
09F79A0121C8116C00820234 /* CountBadgeNode.swift in Sources */,
D0CB27CF20C17A4A001ACF93 /* TermsOfServiceController.swift in Sources */, D0CB27CF20C17A4A001ACF93 /* TermsOfServiceController.swift in Sources */,
D00BDA1F1EE5B69200C64C5E /* ChannelAdminController.swift in Sources */, D00BDA1F1EE5B69200C64C5E /* ChannelAdminController.swift in Sources */,
D0EC6E501EB9F58900EBF1C3 /* ChannelAdminsController.swift in Sources */, D0EC6E501EB9F58900EBF1C3 /* ChannelAdminsController.swift in Sources */,
@ -5897,6 +5920,7 @@
D0EC6E6E1EB9F58900EBF1C3 /* ArchivedStickerPacksController.swift in Sources */, D0EC6E6E1EB9F58900EBF1C3 /* ArchivedStickerPacksController.swift in Sources */,
D0DE5805205B202500C356A8 /* ScreenCaptureDetection.swift in Sources */, D0DE5805205B202500C356A8 /* ScreenCaptureDetection.swift in Sources */,
D0EC6E711EB9F58900EBF1C3 /* ThemeGalleryController.swift in Sources */, D0EC6E711EB9F58900EBF1C3 /* ThemeGalleryController.swift in Sources */,
09F79A0B21C832F400820234 /* WebSearchGalleryFooterContentNode.swift in Sources */,
D0C0B5B11EE1C421000F4D2C /* ChatDateSelectionSheet.swift in Sources */, D0C0B5B11EE1C421000F4D2C /* ChatDateSelectionSheet.swift in Sources */,
D0EC6E721EB9F58900EBF1C3 /* ThemeGalleryItem.swift in Sources */, D0EC6E721EB9F58900EBF1C3 /* ThemeGalleryItem.swift in Sources */,
D00781052084DFB100369A39 /* UrlEscaping.swift in Sources */, D00781052084DFB100369A39 /* UrlEscaping.swift in Sources */,

View File

@ -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) { 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 representation: TelegramMediaImageRepresentation?
var icon = AvatarNodeIcon.none var icon = AvatarNodeIcon.none
if let overrideImage = overrideImage { if let overrideImage = overrideImage {
@ -209,6 +210,7 @@ public final class AvatarNode: ASDisplayNode {
representation = nil representation = nil
case let .image(image): case let .image(image):
representation = image representation = image
synchronousLoad = false
case .savedMessagesIcon: case .savedMessagesIcon:
representation = nil representation = nil
icon = .savedMessagesIcon icon = .savedMessagesIcon

View File

@ -628,9 +628,9 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr
})) }))
}) })
}, changeProfilePhoto: { }, changeProfilePhoto: {
let _ = (account.postbox.transaction { transaction -> Peer? in let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
return transaction.getPeer(peerId) return (transaction.getPeer(peerId), currentSearchBotsConfiguration(transaction: transaction))
} |> deliverOnMainQueue).start(next: { peer in } |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
@ -650,10 +650,7 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr
hasPhotos = true hasPhotos = true
} }
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)! let completedImpl: (UIImage) -> Void = { image in
let _ = currentAvatarMixin.swap(mixin)
mixin.didFinishWithImage = { image in
if let image = image {
if let data = UIImageJPEGRepresentation(image, 0.6) { if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64()) let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data) account.postbox.mediaBox.storeResourceData(resource.id, data: data)
@ -673,6 +670,19 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr
})) }))
} }
} }
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 {
completedImpl(image)
}
} }
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {
let _ = currentAvatarMixin.swap(nil) let _ = currentAvatarMixin.swap(nil)

View File

@ -132,7 +132,6 @@ final class ChatBotInfoItemNode: ListViewItemNode {
var updatedBackgroundImage: UIImage? var updatedBackgroundImage: UIImage?
if currentTheme != item.presentationData.theme { 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) 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) { override func updateTrailingItemSpace(_ height: CGFloat, transition: ContainedViewLayoutTransition) {
if height.isLessThanOrEqualTo(0.0) { 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 { } 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) 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?) { func updateTouchesAtPoint(_ point: CGPoint?) {
if let item = self.item { if let item = self.item {
var rects: [CGRect]? var rects: [CGRect]?
if let point = point { if let point = point {
let textNodeFrame = self.textNode.frame let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - self.offsetContainer.frame.minX - textNodeFrame.minX, y: point.y - self.offsetContainer.frame.minY - textNodeFrame.minY)) {
let possibleNames: [String] = [ let possibleNames: [String] = [
TelegramTextAttributes.URL, TelegramTextAttributes.URL,
TelegramTextAttributes.PeerMention, TelegramTextAttributes.PeerMention,
@ -228,7 +233,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
} else { } else {
linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.theme.chat.bubble.incomingLinkHighlightColor) linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.theme.chat.bubble.incomingLinkHighlightColor)
self.linkHighlightingNode = linkHighlightingNode self.linkHighlightingNode = linkHighlightingNode
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode) self.offsetContainer.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
} }
linkHighlightingNode.frame = self.textNode.frame linkHighlightingNode.frame = self.textNode.frame
linkHighlightingNode.updateRects(rects) linkHighlightingNode.updateRects(rects)
@ -243,7 +248,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction { func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
let textNodeFrame = self.textNode.frame let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - 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 { if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
var concealed = true var concealed = true
if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) { if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {

View File

@ -3816,9 +3816,9 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
configureLegacyAssetPicker(controller, account: strongSelf.account, peer: peer, presentWebSearch: { [weak self] in configureLegacyAssetPicker(controller, account: strongSelf.account, peer: peer, presentWebSearch: { [weak self] in
if let strongSelf = self { 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)) 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) { private func presentWebSearch(editingMessage: Bool) {
guard let _ = self.presentationInterfaceState.renderedPeer?.peer else { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
return return
} }
@ -3855,48 +3855,43 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
} }
|> deliverOnMainQueue).start(next: { [weak self] configuration in |> deliverOnMainQueue).start(next: { [weak self] configuration in
if let strongSelf = self { if let strongSelf = self {
let controller = WebSearchController(account: strongSelf.account, chatLocation: strongSelf.chatLocation, configuration: configuration, sendSelected: { [weak self] ids, collection, editingContext in let controller = WebSearchController(account: strongSelf.account, peer: peer, configuration: configuration, mode: .media(completion: { [weak self] selectionState, editingState in
if let strongSelf = self { if let strongSelf = self {
var results: [ChatContextResult] = [] var results: [ChatContextResult] = []
for id in ids { for item in selectionState.selectedItems() {
var result: ChatContextResult? if let item = item as? LegacyWebSearchItem {
for r in collection.results { results.append(item.result)
if r.id == id {
result = r
results.append(r)
break
}
} }
} }
if !results.isEmpty { if !results.isEmpty {
var signals: [Any] = [] var signals: [Any] = []
for result in results { for result in results {
let editableItem = LegacyWebSearchItem(result: result, dimensions: CGSize(), thumbnailImage: .complete(), originalImage: .complete()) let editableItem = LegacyWebSearchItem(result: result)
if editingContext.adjustments(for: editableItem) != nil { if editingState.adjustments(for: editableItem) != nil {
if let imageSignal = editingContext.imageSignal(for: editableItem) { if let imageSignal = editingState.imageSignal(for: editableItem) {
let signal = imageSignal.map { image -> Any in let signal = imageSignal.map { image -> Any in
if let image = image as? UIImage { if let image = image as? UIImage {
let dict: [AnyHashable: Any] = [ let dict: [AnyHashable: Any] = [
"type": "editedPhoto", "type": "editedPhoto",
"image": image "image": image
] ]
return legacyAssetPickerItemGenerator()(dict, nil, nil, nil) return legacyAssetPickerItemGenerator()(dict, nil, nil, nil) as Any
} else { } else {
return SSignal.complete() return SSignal.complete()
} }
} }
signals.append(signal) signals.append(signal as Any)
} }
} else { } else {
strongSelf.enqueueChatContextResult(collection, result, includeViaBot: false) strongSelf.enqueueChatContextResult(nil, result)
} }
} }
strongSelf.enqueueMediaMessages(signals: signals) strongSelf.enqueueMediaMessages(signals: signals)
} }
} }
}) }))
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) 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 { guard case let .peer(peerId) = self.chatLocation else {
return 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 let replyMessageId = self.presentationInterfaceState.interfaceState.replyMessageId
self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in
if let strongSelf = self { if let strongSelf = self {

View File

@ -226,7 +226,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
mediaAndFlags = (file, []) mediaAndFlags = (file, [])
} }
} else if let image = mainMedia as? TelegramMediaImage { } 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() var flags = ChatMessageAttachedContentNodeMediaFlags()
if webpage.instantPage != nil, let largest = largestImageRepresentation(image.representations) { if webpage.instantPage != nil, let largest = largestImageRepresentation(image.representations) {
if largest.dimensions.width >= 256.0 { if largest.dimensions.width >= 256.0 {

View File

@ -240,8 +240,6 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
self.disablesInteractiveTransitionGestureRecognizer = true self.disablesInteractiveTransitionGestureRecognizer = true
let inputPanelTheme = theme.chat.inputPanel
self.pallete = legacyInputMicPalette(from: theme) self.pallete = legacyInputMicPalette(from: theme)
self.insertSubview(self.innerIconView, at: 0) self.insertSubview(self.innerIconView, at: 0)
@ -348,7 +346,7 @@ final class ChatTextInputMediaRecordingButton: TGModernConversationInputMicButto
func micButtonInteractionCancelled(_ velocity: CGPoint) { func micButtonInteractionCancelled(_ velocity: CGPoint) {
//print("\(CFAbsoluteTimeGetCurrent()) cancelled") //print("\(CFAbsoluteTimeGetCurrent()) cancelled")
self.modeTimeoutTimer?.invalidate() self.modeTimeoutTimer?.invalidate()
self.stopRecording() self.endRecording(false)
} }
func micButtonInteractionCompleted(_ velocity: CGPoint) { func micButtonInteractionCompleted(_ velocity: CGPoint) {

View File

@ -100,7 +100,21 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
var inputActivities: (PeerId, [(Peer, PeerInputActivity)])? { var inputActivities: (PeerId, [(Peer, PeerInputActivity)])? {
didSet { 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.typingNode.isHidden = false
self.infoNode.isHidden = true self.infoNode.isHidden = true
var stringValue = "" var stringValue = ""

View File

@ -6,6 +6,7 @@ import LegacyComponents
enum CheckNodeStyle { enum CheckNodeStyle {
case plain case plain
case overlay case overlay
case navigation
} }
final class CheckNode: ASDisplayNode { final class CheckNode: ASDisplayNode {
@ -18,6 +19,9 @@ final class CheckNode: ASDisplayNode {
private(set) var isChecked: Bool = false private(set) var isChecked: Bool = false
private weak var target: AnyObject?
private var action: Selector?
init(strokeColor: UIColor, fillColor: UIColor, foregroundColor: UIColor, style: CheckNodeStyle) { init(strokeColor: UIColor, fillColor: UIColor, foregroundColor: UIColor, style: CheckNodeStyle) {
self.strokeColor = strokeColor self.strokeColor = strokeColor
self.fillColor = fillColor self.fillColor = fillColor
@ -31,19 +35,29 @@ final class CheckNode: ASDisplayNode {
super.didLoad() super.didLoad()
let style: TGCheckButtonStyle let style: TGCheckButtonStyle
let checkSize: CGSize
switch self.checkStyle { switch self.checkStyle {
case .plain: case .plain:
style = TGCheckButtonStyleDefault style = TGCheckButtonStyleDefault
checkSize = CGSize(width: 32.0, height: 32.0)
case .overlay: case .overlay:
style = TGCheckButtonStyleMedia 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))! 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.setSelected(true, animated: false)
checkView.layoutSubviews() checkView.layoutSubviews()
checkView.setSelected(self.isChecked, animated: false) 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.checkView = checkView
self.view.addSubview(checkView) self.view.addSubview(checkView)
checkView.frame = self.bounds
checkView.frame = CGRect(origin: CGPoint(), size: checkSize)
} }
func setIsChecked(_ isChecked: Bool, animated: Bool) { func setIsChecked(_ isChecked: Bool, animated: Bool) {
@ -52,4 +66,12 @@ final class CheckNode: ASDisplayNode {
self.checkView?.setSelected(isChecked, animated: animated) 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)
}
}
} }

View File

@ -791,7 +791,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
if let verificationIconNode = self.verificationIconNode { if let verificationIconNode = self.verificationIconNode {
var iconFrame = verificationIconNode.frame 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) transition.updateFrame(node: verificationIconNode, frame: iconFrame)
} }

View File

@ -0,0 +1,7 @@
import Foundation
import AsyncDisplayKit
import Display
class CountBadgeNode: ASDisplayNode {
}

View File

@ -258,6 +258,9 @@ public func createChannelController(account: Account) -> ViewController {
})) }))
} }
}, changeProfilePhoto: { }, changeProfilePhoto: {
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 presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
@ -273,10 +276,8 @@ public func createChannelController(account: Account) -> ViewController {
endEditingImpl?() endEditingImpl?()
presentControllerImpl?(legacyController, nil) presentControllerImpl?(legacyController, nil)
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)! let completedImpl: (UIImage) -> Void = { image in
let _ = currentAvatarMixin.swap(mixin) if let data = UIImageJPEGRepresentation(image, 0.6) {
mixin.didFinishWithImage = { image in
if let image = image, let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64()) let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data) account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource) let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
@ -288,6 +289,20 @@ public func createChannelController(account: Account) -> ViewController {
} }
} }
} }
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.didFinishWithImage = { image in
if let image = image {
completedImpl(image)
}
}
if stateValue.with({ $0.avatar }) != nil { if stateValue.with({ $0.avatar }) != nil {
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {
updateState { current in updateState { current in
@ -309,6 +324,7 @@ public func createChannelController(account: Account) -> ViewController {
} }
} }
}) })
})
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get()) let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get())
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<CreateChannelEntry>, CreateChannelEntry.ItemGenerationArguments)) in |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState<CreateChannelEntry>, CreateChannelEntry.ItemGenerationArguments)) in

View File

@ -277,6 +277,9 @@ public func createGroupController(account: Account, peerIds: [PeerId]) -> ViewCo
})) }))
} }
}, changeProfilePhoto: { }, changeProfilePhoto: {
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 presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
@ -292,10 +295,8 @@ public func createGroupController(account: Account, peerIds: [PeerId]) -> ViewCo
endEditingImpl?() endEditingImpl?()
presentControllerImpl?(legacyController, nil) presentControllerImpl?(legacyController, nil)
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)! let completedImpl: (UIImage) -> Void = { image in
let _ = currentAvatarMixin.swap(mixin) if let data = UIImageJPEGRepresentation(image, 0.6) {
mixin.didFinishWithImage = { image in
if let image = image, let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64()) let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data) account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource) let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
@ -307,6 +308,20 @@ public func createGroupController(account: Account, peerIds: [PeerId]) -> ViewCo
} }
} }
} }
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.didFinishWithImage = { image in
if let image = image {
completedImpl(image)
}
}
if stateValue.with({ $0.avatar }) != nil { if stateValue.with({ $0.avatar }) != nil {
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {
updateState { current in updateState { current in
@ -328,6 +343,7 @@ public func createGroupController(account: Account, peerIds: [PeerId]) -> ViewCo
} }
} }
}) })
})
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), account.postbox.multiplePeersView(peerIds)) let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), account.postbox.multiplePeersView(peerIds))
|> map { presentationData, state, view -> (ItemListControllerState, (ItemListNodeState<CreateGroupEntry>, CreateGroupEntry.ItemGenerationArguments)) in |> map { presentationData, state, view -> (ItemListControllerState, (ItemListNodeState<CreateGroupEntry>, CreateGroupEntry.ItemGenerationArguments)) in

View File

@ -171,15 +171,15 @@ private let bubble = PresentationThemeChatBubble(
incomingFileDurationColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5), incomingFileDurationColor: UIColor(rgb: 0xDBF5FF, alpha: 0.5),
outgoingFileDurationColor: 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)), shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5)),
shareButtonStrokeColor: UIColor(rgb: 0x213040), shareButtonStrokeColor: UIColor(rgb: 0x587fa3, alpha: 0.15),
shareButtonForegroundColor: UIColor(rgb: 0xb2b2b2), //!!! shareButtonForegroundColor: UIColor(rgb: 0xb2b2b2),
mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), //!!! mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), //!!!
mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), //!!! mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), //!!!
actionButtonsIncomingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5)), 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), actionButtonsIncomingTextColor: UIColor(rgb: 0xffffff),
actionButtonsOutgoingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x18222D, alpha: 0.5)), 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), actionButtonsOutgoingTextColor: UIColor(rgb: 0xffffff),
selectionControlBorderColor: .white, selectionControlBorderColor: .white,
selectionControlFillColor: accentColor, selectionControlFillColor: accentColor,

View File

@ -171,15 +171,15 @@ private let bubble = PresentationThemeChatBubble(
incomingFileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5), incomingFileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5),
outgoingFileDurationColor: 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)), 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), //!!! shareButtonForegroundColor: UIColor(rgb: 0xb2b2b2), //!!!
mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), //!!! mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), //!!!
mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), //!!! mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), //!!!
actionButtonsIncomingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), 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), actionButtonsIncomingTextColor: UIColor(rgb: 0xffffff),
actionButtonsOutgoingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)), 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), actionButtonsOutgoingTextColor: UIColor(rgb: 0xffffff),
selectionControlBorderColor: .white, selectionControlBorderColor: .white,
selectionControlFillColor: accentColor, selectionControlFillColor: accentColor,

View File

@ -420,9 +420,9 @@ func editSettingsController(account: Account, currentName: ItemListAvatarAndName
} }
} }
changeProfilePhotoImpl = { [weak controller] in changeProfilePhotoImpl = { [weak controller] in
let _ = (account.postbox.transaction { transaction -> Peer? in let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
return transaction.getPeer(account.peerId) return (transaction.getPeer(account.peerId), currentSearchBotsConfiguration(transaction: transaction))
} |> deliverOnMainQueue).start(next: { peer in } |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
controller?.view.endEditing(true) controller?.view.endEditing(true)
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
@ -444,10 +444,7 @@ func editSettingsController(account: Account, currentName: ItemListAvatarAndName
hasPhotos = true hasPhotos = true
} }
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, hasViewButton: hasPhotos, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)! let completedImpl: (UIImage) -> Void = { image in
let _ = currentAvatarMixin.swap(mixin)
mixin.didFinishWithImage = { image in
if let image = image {
if let data = UIImageJPEGRepresentation(image, 0.6) { if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64()) let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data) account.postbox.mediaBox.storeResourceData(resource.id, data: data)
@ -467,6 +464,19 @@ func editSettingsController(account: Account, currentName: ItemListAvatarAndName
})) }))
} }
} }
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.didFinishWithDelete = { mixin.didFinishWithDelete = {
let _ = currentAvatarMixin.swap(nil) let _ = currentAvatarMixin.swap(nil)

View 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)
}
}

View 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)
}
}

View File

@ -1203,9 +1203,9 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
})) }))
}) })
}, changeProfilePhoto: { }, changeProfilePhoto: {
let _ = (account.postbox.transaction { transaction -> Peer? in let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
return transaction.getPeer(peerId) return (transaction.getPeer(peerId), currentSearchBotsConfiguration(transaction: transaction))
} |> deliverOnMainQueue).start(next: { peer in } |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
@ -1225,10 +1225,7 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
hasPhotos = true hasPhotos = true
} }
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)! let completedImpl: (UIImage) -> Void = { image in
let _ = currentAvatarMixin.swap(mixin)
mixin.didFinishWithImage = { image in
if let image = image {
if let data = UIImageJPEGRepresentation(image, 0.6) { if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64()) let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data) account.postbox.mediaBox.storeResourceData(resource.id, data: data)
@ -1248,6 +1245,19 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
})) }))
} }
} }
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 {
completedImpl(image)
}
} }
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {
let _ = currentAvatarMixin.swap(nil) let _ = currentAvatarMixin.swap(nil)
@ -1361,9 +1371,25 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
var options: [ContactListAdditionalOption] = [] var options: [ContactListAdditionalOption] = []
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
var inviteByLinkImpl: (() -> Void)? var inviteByLinkImpl: (() -> Void)?
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: { options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
inviteByLinkImpl?() inviteByLinkImpl?()
})) }))
}
let contactsController: ViewController let contactsController: ViewController
if peerId.namespace == Namespaces.Peer.CloudGroup { if peerId.namespace == Namespaces.Peer.CloudGroup {

View File

@ -195,8 +195,6 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>? var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
//messageFileMediaResourceStatus(account: account, file: file, message: message, isRecentActions: isRecentActions)
var imageResource: TelegramMediaResource? var imageResource: TelegramMediaResource?
var stickerFile: TelegramMediaFile? var stickerFile: TelegramMediaFile?

View File

@ -71,11 +71,18 @@ NSData * _Nullable albumArtworkData(NSData * _Nonnull data) {
uint32_t pos = 0; uint32_t pos = 0;
while (pos < size) { while (pos < size) {
const uint8_t * const frameBytes = ptr + pos; const uint8_t * const frameBytes = ptr + pos;
if (ID3TagOffset + pos + 4 >= data.length) {
return nil;
}
uint32_t frameSize = frameSizeForBytes(frameBytes, version); uint32_t frameSize = frameSizeForBytes(frameBytes, version);
if (ID3TagOffset + pos + frameSize >= data.length) {
return nil;
}
if (isArtworkFrame(frameBytes, version)) { if (isArtworkFrame(frameBytes, version)) {
uint32_t frameOffset = frameOffsetForVersion(version); uint32_t frameOffset = frameOffsetForVersion(version);
const uint8_t *ptr = frameBytes + frameOffset; const uint8_t *ptr = frameBytes + frameOffset;
uint32_t start = ID3TagOffset + pos + frameOffset;
bool isJpg = false; bool isJpg = false;
uint32_t imageOffset = UINT32_MAX; uint32_t imageOffset = UINT32_MAX;
@ -91,7 +98,6 @@ NSData * _Nullable albumArtworkData(NSData * _Nonnull data) {
} }
if (imageOffset != UINT32_MAX) { if (imageOffset != UINT32_MAX) {
uint32_t start = ID3TagOffset + pos + frameOffset;
if (isJpg) { if (isJpg) {
NSMutableData *jpgData = [[NSMutableData alloc] initWithCapacity:frameSize + 1024]; NSMutableData *jpgData = [[NSMutableData alloc] initWithCapacity:frameSize + 1024];
uint8_t previousByte = 0xff; uint8_t previousByte = 0xff;
@ -103,11 +109,7 @@ NSData * _Nullable albumArtworkData(NSData * _Nonnull data) {
return nil; return nil;
} }
uint8_t byte = (uint8_t)ptr[offset]; 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) { if (byte == 0xd9 && previousByte == 0xff) {
break; break;
} }

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
import Display import Display
import AsyncDisplayKit import AsyncDisplayKit
import Postbox
import TelegramCore import TelegramCore
import SafariServices import SafariServices
@ -188,8 +189,13 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
if self.contentNode == nil || self.contentNode?.frame.width != width { if self.contentNode == nil || self.contentNode?.frame.width != width {
self.contentNode?.removeFromSupernode() 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 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 }) 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))) transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: width, height: contentSize.height)))
self.contentContainerNode.insertSubnode(contentNode, at: 0) self.contentContainerNode.insertSubnode(contentNode, at: 0)

View File

@ -508,7 +508,10 @@ func attributedStringForRichText(_ text: RichText, styleStack: InstantPageTextSt
}) })
let delegate = CTRunDelegateCreate(&callbacks, extentBuffer) let delegate = CTRunDelegateCreate(&callbacks, extentBuffer)
let attrDictionaryDelegate = [(kCTRunDelegateAttributeName as NSAttributedStringKey): (delegate as Any), NSAttributedStringKey(rawValue: InstantPageMediaIdAttribute): id.id, NSAttributedStringKey(rawValue: InstantPageMediaDimensionsAttribute): dimensions] 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): case let .anchor(text, name):
var empty = false var empty = false
var text = text var text = text
@ -569,7 +572,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
var workingLineOrigin = currentLineOrigin var workingLineOrigin = currentLineOrigin
let currentMaxWidth = boundingWidth - workingLineOrigin.x let currentMaxWidth = boundingWidth - workingLineOrigin.x
let lineCharacterCount: CFIndex var lineCharacterCount: CFIndex
var hadIndexOffset = false var hadIndexOffset = false
if minimizeWidth { if minimizeWidth {
var count = 0 var count = 0
@ -584,6 +587,9 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
let suggestedLineBreak = CTTypesetterSuggestLineBreak(typesetter, lastIndex, Double(currentMaxWidth)) let suggestedLineBreak = CTTypesetterSuggestLineBreak(typesetter, lastIndex, Double(currentMaxWidth))
if let offset = indexOffset { if let offset = indexOffset {
lineCharacterCount = suggestedLineBreak + offset lineCharacterCount = suggestedLineBreak + offset
if lineCharacterCount <= 0 {
lineCharacterCount = suggestedLineBreak
}
indexOffset = nil indexOffset = nil
hadIndexOffset = true hadIndexOffset = true
} else { } else {

View File

@ -16,7 +16,7 @@ func presentLegacyAvatarPicker(holder: Atomic<NSObject?>, signup: Bool, theme: P
present(legacyController, nil) 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) let _ = holder.swap(mixin)
mixin.didFinishWithImage = { image in mixin.didFinishWithImage = { image in
guard let image = image else { guard let image = image else {

View 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)
}

View File

@ -20,12 +20,22 @@ class LegacyWebSearchItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem
let dimensions: CGSize let dimensions: CGSize
let thumbnailImage: Signal<UIImage, NoError> let thumbnailImage: Signal<UIImage, NoError>
let originalImage: 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.result = result
self.dimensions = dimensions self.dimensions = dimensions
self.thumbnailImage = thumbnailImage self.thumbnailImage = thumbnailImage
self.originalImage = originalImage self.originalImage = originalImage
self.progress = progress
} }
var originalSize: CGSize { 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! { func screenImageSignal(_ position: TimeInterval) -> SSignal! {
return self.originalImageSignal(position) return self.originalImageSignal(position)
} }
@ -53,7 +87,9 @@ class LegacyWebSearchItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem
return SSignal(generator: { subscriber -> SDisposable? in return SSignal(generator: { subscriber -> SDisposable? in
let disposable = self.originalImage.start(next: { image in let disposable = self.originalImage.start(next: { image in
subscriber?.putNext(image) subscriber?.putNext(image)
if !image.degraded() {
subscriber?.putCompletion() subscriber?.putCompletion()
}
}) })
return SBlockDisposable(block: { return SBlockDisposable(block: {
@ -93,14 +129,26 @@ private class LegacyWebSearchGalleryItem: TGModernGalleryImageItem, TGModernGall
override func viewClass() -> AnyClass! { override func viewClass() -> AnyClass! {
return LegacyWebSearchGalleryItemView.self 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) { func setHiddenAsBeingEdited(_ hidden: Bool) {
self.imageView.isHidden = hidden self.imageView.isHidden = hidden
} }
override func readyForTransitionIn() -> SSignal! {
return self.readyForTransition.signal()!.take(1)
}
override func setItem(_ item: TGModernGalleryItem!, synchronously: Bool) { override func setItem(_ item: TGModernGalleryItem!, synchronously: Bool) {
if let item = item as? LegacyWebSearchGalleryItem { if let item = item as? LegacyWebSearchGalleryItem {
self._setItem(item) self._setItem(item)
@ -110,7 +158,7 @@ private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGMo
if let image = result as? UIImage { if let image = result as? UIImage {
return SSignal.single(image) return SSignal.single(image)
} else if result == nil, let mediaItem = item.editableMediaItem() as? LegacyWebSearchItem { } else if result == nil, let mediaItem = item.editableMediaItem() as? LegacyWebSearchItem {
return mediaItem.originalImageSignal(0.0) return mediaItem.screenImageAndProgressSignal()
} else { } else {
return SSignal.complete() return SSignal.complete()
} }
@ -120,6 +168,7 @@ private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGMo
if let strongSelf = self, let image = next as? UIImage { if let strongSelf = self, let image = next as? UIImage {
strongSelf.imageSize = image.size strongSelf.imageSize = image.size
strongSelf.reset() strongSelf.reset()
strongSelf.readyForTransition.set(SSignal.single(true))
} }
})) }))
@ -144,10 +193,7 @@ 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 focusItem: TGModernGalleryItem?
var galleryItems: [TGModernGalleryItem] = []
for result in results {
var thumbnailDimensions: CGSize? var thumbnailDimensions: CGSize?
var thumbnailResource: TelegramMediaResource? var thumbnailResource: TelegramMediaResource?
var imageResource: TelegramMediaResource? var imageResource: TelegramMediaResource?
@ -155,6 +201,7 @@ private func galleryItems(account: Account, results: [ChatContextResult], curren
let thumbnailSignal: Signal<UIImage, NoError> let thumbnailSignal: Signal<UIImage, NoError>
let originalSignal: Signal<UIImage, NoError> let originalSignal: Signal<UIImage, NoError>
switch result { switch result {
case let .externalReference(_, _, _, _, _, _, content, thumbnail, _): case let .externalReference(_, _, _, _, _, _, content, thumbnail, _):
if let content = content { if let content = content {
@ -167,9 +214,6 @@ private func galleryItems(account: Account, results: [ChatContextResult], curren
if let dimensions = content?.dimensions { if let dimensions = content?.dimensions {
imageDimensions = dimensions imageDimensions = dimensions
} }
// if let imageResource = imageResource {
// updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
// }
case let .internalReference(_, _, _, _, _, image, _, _): case let .internalReference(_, _, _, _, _, image, _, _):
if let image = image { if let image = image {
if let largestRepresentation = largestImageRepresentation(image.representations) { if let largestRepresentation = largestImageRepresentation(image.representations) {
@ -184,23 +228,55 @@ private func galleryItems(account: Account, results: [ChatContextResult], curren
} }
if let imageResource = imageResource { 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] = [] var representations: [TelegramMediaImageRepresentation] = []
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions { if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource)) representations.append(TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource))
} }
representations.append(TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource)) representations.append(TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource))
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, reference: nil, partialReference: nil) 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) thumbnailSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: false)
|> mapToSignal { (thumbnailData, fullSizeData, fullSizeComplete) -> Signal<UIImage, NoError> in |> mapToSignal { (thumbnailData, _, _) -> Signal<UIImage, NoError> in
if let data = fullSizeData, let image = UIImage(data: data) { 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) return .single(image)
} else { } else {
return .complete() return .complete()
} }
} }
originalSignal = thumbnailSignal
let item = LegacyWebSearchItem(result: result, dimensions: imageDimensions, thumbnailImage: thumbnailSignal, originalImage: originalSignal) 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 {
if let item = legacyWebSearchItem(account: account, result: result) {
let galleryItem = LegacyWebSearchGalleryItem(item: item) let galleryItem = LegacyWebSearchGalleryItem(item: item)
galleryItem.selectionContext = selectionContext galleryItem.selectionContext = selectionContext
galleryItem.editingContext = editingContext galleryItem.editingContext = editingContext
@ -213,11 +289,12 @@ private func galleryItems(account: Account, results: [ChatContextResult], curren
return (galleryItems, focusItem) 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) let legacyController = LegacyController(presentation: .custom, theme: theme, initialLayout: initialLayout)
legacyController.statusBar.statusBarStyle = .Ignore legacyController.statusBar.statusBarStyle = .Ignore
let controller = TGModernGalleryController(context: legacyController.context)! let controller = TGModernGalleryController(context: legacyController.context)!
controller.asyncTransitionIn = true
legacyController.bind(controller: controller) legacyController.bind(controller: controller)
let (items, focusItem) = galleryItems(account: account, results: results, current: current, selectionContext: selectionContext, editingContext: editingContext) 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 controller.model = model
model.controller = controller model.controller = controller
model.externalSelectionCount = {
return 0
}
model.useGalleryImageAsEditableItemImage = true model.useGalleryImageAsEditableItemImage = true
model.storeOriginalImageForItem = { item, image in model.storeOriginalImageForItem = { item, image in
editingContext.setOriginalImage(image, for: item, synchronous: false) 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.setAdjustments(adjustments, for: item)
} }
editingContext.setTemporaryRep(representation, for: item) editingContext.setTemporaryRep(representation, for: item)
//if let selectionContext = selectionContext, { if let selectionContext = selectionContext, adjustments != nil, let item = item as? TGMediaSelectableItem {
// selectionContex selectionContext.setItem(item, selected: true)
//} }
} }
model.didFinishEditingItem = { item, adjustments, result, thumbnail in model.didFinishEditingItem = { item, adjustments, result, thumbnail in
editingContext.setImage(result, thumbnailImage: thumbnail, for: item, synchronous: true) editingContext.setImage(result, thumbnailImage: thumbnail, for: item, synchronous: true)
} }
model.saveItemCaption = { item, caption, entities in model.saveItemCaption = { item, caption, entities in
editingContext.setCaption(caption, entities: entities, for: item) 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 model.interfaceView.donePressed = { item in
if let item = item as? LegacyWebSearchGalleryItem { if let item = item as? LegacyWebSearchGalleryItem {
controller.dismissWhenReady(animated: false) controller.dismissWhenReady(animated: false)
@ -284,46 +363,4 @@ func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: Present
legacyController?.dismiss() legacyController?.dismiss()
} }
present(legacyController, nil) 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];
// }
// };
//
} }

View File

@ -71,6 +71,14 @@ public enum MessageContentKind: Equatable {
public func messageContentKind(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> MessageContentKind { public func messageContentKind(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> MessageContentKind {
for media in message.media { for media in message.media {
if let kind = mediaContentKind(media, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId) {
return kind
}
}
return .text(message.text)
}
public func mediaContentKind(_ media: Media, message: Message? = nil, strings: PresentationStrings? = nil, nameDisplayOrder: PresentationPersonNameOrder? = nil, accountPeerId: PeerId? = nil) -> MessageContentKind? {
switch media { switch media {
case let expiredMedia as TelegramMediaExpiredContent: case let expiredMedia as TelegramMediaExpiredContent:
switch expiredMedia.data { switch expiredMedia.data {
@ -127,21 +135,20 @@ public func messageContentKind(_ message: Message, strings: PresentationStrings,
return .location return .location
} }
case _ as TelegramMediaAction: 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) ?? "") return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) ?? "")
} else {
return nil
}
case let poll as TelegramMediaPoll: case let poll as TelegramMediaPoll:
return .text(poll.text) return .text(poll.text)
default: default:
break return nil
} }
} }
return .text(message.text)
}
func descriptionStringForMessage(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) { func stringForMediaKind(_ kind: MessageContentKind, strings: PresentationStrings) -> (String, Bool) {
if !message.text.isEmpty { switch kind {
return (message.text, false)
}
switch messageContentKind(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId) {
case let .text(text): case let .text(text):
return (text, false) return (text, false)
case .image: case .image:
@ -180,3 +187,10 @@ func descriptionStringForMessage(_ message: Message, strings: PresentationString
return (strings.Message_VideoExpired, true) 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)
}

View File

@ -32,7 +32,6 @@ public class NotificationExceptionsController: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) 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.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)) 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() { private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) 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.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.controllerNode.updatePresentationData(self.presentationData) self.controllerNode.updatePresentationData(self.presentationData)

View File

@ -60,6 +60,20 @@ public final class PeerSelectionController: ViewController {
strongSelf.peerSelectionNode.scrollToTop() 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) { required public init(coder aDecoder: NSCoder) {
@ -68,6 +82,14 @@ public final class PeerSelectionController: ViewController {
deinit { deinit {
self.openMessageFromSearchDisposable.dispose() 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() { override public func loadDisplayNode() {

View File

@ -120,6 +120,10 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.searchDisplayController?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) 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.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) { 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)) 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]) var insets = layout.insets(options: [.input])
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top) insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
insets.bottom = max(insets.bottom, cleanInsets.bottom) insets.bottom = max(insets.bottom, cleanInsets.bottom)

File diff suppressed because it is too large Load Diff

View File

@ -443,7 +443,7 @@ class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
func transitionOut(to node: SearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { func transitionOut(to node: SearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
let targetTextBackgroundFrame = node.convert(node.backgroundNode.frame, to: self) 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 let timingFunction = kCAMediaTimingFunctionSpring
node.isHidden = true node.isHidden = true

View File

@ -616,9 +616,9 @@ public func settingsController(account: Account, accountManager: AccountManager)
}) })
changeProfilePhotoImpl = { changeProfilePhotoImpl = {
let _ = (account.postbox.transaction { transaction -> Peer? in let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
return transaction.getPeer(account.peerId) return (transaction.getPeer(account.peerId), currentSearchBotsConfiguration(transaction: transaction))
} |> deliverOnMainQueue).start(next: { peer in } |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
@ -638,10 +638,7 @@ public func settingsController(account: Account, accountManager: AccountManager)
hasPhotos = true hasPhotos = true
} }
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false)! let completedImpl: (UIImage) -> Void = { image in
let _ = currentAvatarMixin.swap(mixin)
mixin.didFinishWithImage = { image in
if let image = image {
if let data = UIImageJPEGRepresentation(image, 0.6) { if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64()) let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data) account.postbox.mediaBox.storeResourceData(resource.id, data: data)
@ -661,6 +658,19 @@ public func settingsController(account: Account, accountManager: AccountManager)
})) }))
} }
} }
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 {
completedImpl(image)
}
} }
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {
let _ = currentAvatarMixin.swap(nil) let _ = currentAvatarMixin.swap(nil)

View File

@ -93,7 +93,6 @@ class SetupTwoStepVerificationController: ViewController {
private func updateThemeAndStrings() { private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData)) 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.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.controllerNode.updatePresentationData(self.presentationData) self.controllerNode.updatePresentationData(self.presentationData)
} }

View File

@ -25,6 +25,7 @@ private final class UserInfoControllerArguments {
let displayCopyContextMenu: (UserInfoEntryTag, String) -> Void let displayCopyContextMenu: (UserInfoEntryTag, String) -> Void
let call: () -> Void let call: () -> Void
let openCallMenu: (String) -> Void let openCallMenu: (String) -> Void
let aboutLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void
let displayAboutContextMenu: (String) -> Void let displayAboutContextMenu: (String) -> Void
let openEncryptionKey: (SecretChatKeyFingerprint) -> Void let openEncryptionKey: (SecretChatKeyFingerprint) -> Void
let addBotToGroup: () -> Void let addBotToGroup: () -> Void
@ -34,7 +35,7 @@ private final class UserInfoControllerArguments {
let botPrivacy: () -> Void let botPrivacy: () -> Void
let report: () -> 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.account = account
self.avatarAndNameInfoContext = avatarAndNameInfoContext self.avatarAndNameInfoContext = avatarAndNameInfoContext
self.updateEditingName = updateEditingName self.updateEditingName = updateEditingName
@ -55,6 +56,7 @@ private final class UserInfoControllerArguments {
self.displayCopyContextMenu = displayCopyContextMenu self.displayCopyContextMenu = displayCopyContextMenu
self.call = call self.call = call
self.openCallMenu = openCallMenu self.openCallMenu = openCallMenu
self.aboutLinkAction = aboutLinkAction
self.displayAboutContextMenu = displayAboutContextMenu self.displayAboutContextMenu = displayAboutContextMenu
self.openEncryptionKey = openEncryptionKey self.openEncryptionKey = openEncryptionKey
self.addBotToGroup = addBotToGroup self.addBotToGroup = addBotToGroup
@ -93,7 +95,7 @@ private func areMessagesEqual(_ lhsMessage: Message, _ rhsMessage: Message) -> B
private enum UserInfoEntry: ItemListNodeEntry { private enum UserInfoEntry: ItemListNodeEntry {
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer?, presence: PeerPresence?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, displayCall: Bool) case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer?, presence: PeerPresence?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, displayCall: Bool)
case calls(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, messages: [Message]) 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 phoneNumber(PresentationTheme, Int, String, String, Bool)
case userName(PresentationTheme, String, String) case userName(PresentationTheme, String, String)
case sendMessage(PresentationTheme, String) case sendMessage(PresentationTheme, String)
@ -192,8 +194,8 @@ private enum UserInfoEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .about(lhsTheme, lhsText, lhsValue): case let .about(lhsTheme, lhsPeer, lhsText, lhsValue):
if case let .about(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .about(rhsTheme, rhsPeer, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsPeer.isEqual(rhsPeer), lhsText == rhsText, lhsValue == rhsValue {
return true return true
} else { } else {
return false return false
@ -380,9 +382,15 @@ private enum UserInfoEntry: ItemListNodeEntry {
} : nil) } : nil)
case let .calls(theme, strings, dateTimeFormat, messages): case let .calls(theme, strings, dateTimeFormat, messages):
return ItemListCallListItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, messages: messages, sectionId: self.section, style: .plain) return ItemListCallListItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, messages: messages, sectionId: self.section, style: .plain)
case let .about(theme, text, value): case let .about(theme, peer, text, value):
return ItemListTextWithLabelItem(theme: theme, label: text, text: value, enabledEntitiyTypes: [], multiline: true, sectionId: self.section, action: { 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) arguments.displayAboutContextMenu(value)
}, linkItemAction: { action, itemLink in
arguments.aboutLinkAction(action, itemLink)
}, tag: UserInfoEntryTag.about) }, tag: UserInfoEntryTag.about)
case let .phoneNumber(theme, _, label, value, isMain): 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: { 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 { } else {
title = presentationData.strings.Profile_About title = presentationData.strings.Profile_About
} }
entries.append(UserInfoEntry.about(presentationData.theme, title, about)) entries.append(UserInfoEntry.about(presentationData.theme, peer, title, about))
} }
if !isEditing { if !isEditing {
@ -764,10 +772,14 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC
let createSecretChatDisposable = MetaDisposable() let createSecretChatDisposable = MetaDisposable()
actionsDisposable.add(createSecretChatDisposable) actionsDisposable.add(createSecretChatDisposable)
let navigateDisposable = MetaDisposable()
actionsDisposable.add(navigateDisposable)
var avatarGalleryTransitionArguments: ((AvatarGalleryEntry) -> GalleryTransitionArguments?)? var avatarGalleryTransitionArguments: ((AvatarGalleryEntry) -> GalleryTransitionArguments?)?
let avatarAndNameInfoContext = ItemListAvatarAndNameInfoItemContext() let avatarAndNameInfoContext = ItemListAvatarAndNameInfoItemContext()
var updateHiddenAvatarImpl: (() -> Void)? var updateHiddenAvatarImpl: (() -> Void)?
var aboutLinkActionImpl: ((TextLinkItemActionType, TextLinkItem) -> Void)?
var displayAboutContextMenuImpl: ((String) -> Void)? var displayAboutContextMenuImpl: ((String) -> Void)?
var displayCopyContextMenuImpl: ((UserInfoEntryTag, 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 } 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()) updatePeerBlockedDisposable.set(requestUpdatePeerIsBlocked(account: account, peerId: peer.id, isBlocked: value).start())
if !value { if !value {
let _ = enqueueMessages(account: account, peerId: peer.id, messages: [.message(text: "/start", attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)]).start() 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: ""))") account.telegramApplicationContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(number).replacingOccurrences(of: " ", with: ""))")
} }
}) })
}, aboutLinkAction: { action, itemLink in
aboutLinkActionImpl?(action, itemLink)
}, displayAboutContextMenu: { text in }, displayAboutContextMenu: { text in
displayAboutContextMenuImpl?(text) displayAboutContextMenuImpl?(text)
}, openEncryptionKey: { fingerprint in }, 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 displayAboutContextMenuImpl = { [weak controller] text in
if let strongController = controller { if let strongController = controller {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }

View File

@ -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 { final class WebSearchControllerInteraction {
let openResult: (ChatContextResult) -> Void let openResult: (ChatContextResult) -> Void
let setSearchQuery: (String) -> Void let setSearchQuery: (String) -> Void
let deleteRecentQuery: (String) -> Void let deleteRecentQuery: (String) -> Void
let toggleSelection: ([String], Bool) -> Void let toggleSelection: (ChatContextResult, Bool) -> Void
let sendSelected: (ChatContextResultCollection, ChatContextResult?) -> Void let sendSelected: (ChatContextResult?) -> Void
var selectionState: WebSearchSelectionState? let avatarCompleted: (UIImage) -> Void
var selectionState: TGMediaSelectionContext?
let editingState: TGMediaEditingContext
var hiddenMediaId: String? 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.openResult = openResult
self.setSearchQuery = setSearchQuery self.setSearchQuery = setSearchQuery
self.deleteRecentQuery = deleteRecentQuery self.deleteRecentQuery = deleteRecentQuery
self.toggleSelection = toggleSelection self.toggleSelection = toggleSelection
self.sendSelected = sendSelected self.sendSelected = sendSelected
self.editingContext = editingContext 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 var validLayout: ContainerViewLayout?
private let account: Account private let account: Account
private let chatLocation: ChatLocation private let mode: WebSearchControllerMode
private let peer: Peer?
private let configuration: SearchBotsConfiguration private let configuration: SearchBotsConfiguration
private var controllerNode: WebSearchControllerNode { private var controllerNode: WebSearchControllerNode {
@ -70,12 +104,14 @@ final class WebSearchController: ViewController {
private var disposable: Disposable? private var disposable: Disposable?
private let resultsDisposable = MetaDisposable() private let resultsDisposable = MetaDisposable()
private var selectionDisposable: Disposable?
private var navigationContentNode: WebSearchNavigationContentNode? 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.account = account
self.chatLocation = chatLocation self.mode = mode
self.peer = peer
self.configuration = configuration self.configuration = configuration
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
@ -101,8 +137,8 @@ final class WebSearchController: ViewController {
} }
strongSelf.updateInterfaceState { current -> WebSearchInterfaceState in strongSelf.updateInterfaceState { current -> WebSearchInterfaceState in
var updated = current var updated = current
if current.state?.mode != settings.mode { if case .media = mode, current.state?.scope != settings.scope {
updated = updated.withUpdatedMode(settings.mode) updated = updated.withUpdatedScope(settings.scope)
} }
if current.presentationData !== presentationData { if current.presentationData !== presentationData {
updated = updated.withUpdatedPresentationData(presentationData) updated = updated.withUpdatedPresentationData(presentationData)
@ -121,12 +157,19 @@ final class WebSearchController: ViewController {
} }
self.navigationBar?.setContentNode(navigationContentNode, animated: false) 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 self.controllerInteraction = WebSearchControllerInteraction(openResult: { [weak self] result in
if let strongSelf = self { if let strongSelf = self {
strongSelf.controllerNode.openResult(currentResult: result, present: { [weak self] viewController, arguments in strongSelf.controllerNode.openResult(currentResult: result, present: { [weak self] viewController, arguments in
if let strongSelf = self { 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 { if let strongSelf = self {
_ = removeRecentWebSearchQuery(postbox: strongSelf.account.postbox, string: query).start() _ = removeRecentWebSearchQuery(postbox: strongSelf.account.postbox, string: query).start()
} }
}, toggleSelection: { [weak self] ids, value in }, toggleSelection: { [weak self] result, value in
if let strongSelf = self { 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 }, sendSelected: { current in
if let strongSelf = self, let state = strongSelf.interfaceState.state { if let selectionState = selectionState {
var selectedIds = state.selectionState.selectedIds
if let current = current { if let current = current {
selectedIds.insert(current.id) let currentItem = LegacyWebSearchItem(result: current)
selectionState.setItem(currentItem, selected: true)
} }
sendSelected(Array(selectedIds), collection, editingContext) if case let .media(sendSelected) = mode {
sendSelected(selectionState, editingState)
}
}
}, 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)
}
})
} }
}, editingContext: editingContext)
} }
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {
@ -161,6 +220,8 @@ final class WebSearchController: ViewController {
deinit { deinit {
self.disposable?.dispose() self.disposable?.dispose()
self.resultsDisposable.dispose()
self.selectionDisposable?.dispose()
} }
public override func viewDidAppear(_ animated: Bool) { public override func viewDidAppear(_ animated: Bool) {
@ -181,7 +242,7 @@ final class WebSearchController: ViewController {
} }
override public func loadDisplayNode() { 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 self.controllerNode.requestUpdateInterfaceState = { [weak self] animated, f in
if let strongSelf = self { if let strongSelf = self {
strongSelf.updateInterfaceState(f) strongSelf.updateInterfaceState(f)
@ -212,8 +273,6 @@ final class WebSearchController: ViewController {
self.interfaceState = updatedInterfaceState self.interfaceState = updatedInterfaceState
self.interfaceStatePromise.set(updatedInterfaceState) self.interfaceStatePromise.set(updatedInterfaceState)
self.controllerInteraction?.selectionState = updatedInterfaceState.state?.selectionState
if self.isNodeLoaded { if self.isNodeLoaded {
if previousTheme !== updatedInterfaceState.presentationData.theme || previousStrings !== updatedInterfaceState.presentationData.strings { if previousTheme !== updatedInterfaceState.presentationData.theme || previousStrings !== updatedInterfaceState.presentationData.strings {
self.controllerNode.updatePresentationData(theme: updatedInterfaceState.presentationData.theme, strings: 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 _ = addRecentWebSearchQuery(postbox: self.account.postbox, string: query).start()
} }
let mode = self.interfaceStatePromise.get() let scope: Signal<WebSearchScope?, NoError>
|> map { state -> WebSearchMode? in switch self.mode {
return state.state?.mode case .media:
scope = self.interfaceStatePromise.get()
|> map { state -> WebSearchScope? in
return state.state?.scope
} }
|> distinctUntilChanged |> distinctUntilChanged
case .avatar:
scope = .single(.images)
}
self.updateInterfaceState { $0.withUpdatedQuery(query) } self.updateInterfaceState { $0.withUpdatedQuery(query) }
var results = mode let scopes: [WebSearchScope: Promise<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?>] = [.images: Promise(initializeOnFirstAccess: self.signalForQuery(query, scope: .images)), .gifs: Promise(initializeOnFirstAccess: self.signalForQuery(query, scope: .gifs))]
|> mapToSignal { mode -> (Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>) in
if let mode = mode { var results = scope
return self.signalForQuery(query, mode: mode) |> mapToSignal { scope -> (Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>) in
|> deliverOnMainQueue if let scope = scope, let scopeResults = scopes[scope] {
|> beforeStarted { [weak self] in return scopeResults.get()
if let strongSelf = self {
strongSelf.navigationContentNode?.setActivity(true)
}
}
|> afterCompleted { [weak self] in
if let strongSelf = self {
strongSelf.navigationContentNode?.setActivity(false)
}
}
} else { } else {
return .complete() 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 delayRequest = true
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete() var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .single({ _ in return .contextRequestResult(nil, nil) })
// 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) })
// }
guard case let .peer(peerId) = self.chatLocation else { guard let peerId = self.peer?.id else {
return .single({ _ in return .contextRequestResult(nil, nil) }) return .single({ _ in return .contextRequestResult(nil, nil) })
} }
let botName: String? let botName: String?
switch mode { switch scope {
case .images: case .images:
botName = self.configuration.imageBotUsername botName = self.configuration.imageBotUsername
case .gifs: case .gifs:
@ -354,7 +397,18 @@ final class WebSearchController: ViewController {
return .single({ _ in return nil }) 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) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {

View File

@ -113,8 +113,10 @@ private func preparedWebSearchRecentTransition(from fromEntries: [WebSearchRecen
class WebSearchControllerNode: ASDisplayNode { class WebSearchControllerNode: ASDisplayNode {
private let account: Account private let account: Account
private let peer: Peer?
private var theme: PresentationTheme private var theme: PresentationTheme
private var strings: PresentationStrings private var strings: PresentationStrings
private let mode: WebSearchMode
private let controllerInteraction: WebSearchControllerInteraction private let controllerInteraction: WebSearchControllerInteraction
private var webSearchInterfaceState: WebSearchInterfaceState private var webSearchInterfaceState: WebSearchInterfaceState
@ -145,8 +147,6 @@ class WebSearchControllerNode: ASDisplayNode {
private var hasMore = false private var hasMore = false
private var isLoadingMore = false private var isLoadingMore = false
private let selectionContext = TGMediaSelectionContext()
private let hiddenMediaId = Promise<String?>(nil) private let hiddenMediaId = Promise<String?>(nil)
private var hiddenMediaDisposable: Disposable? private var hiddenMediaDisposable: Disposable?
@ -163,11 +163,13 @@ class WebSearchControllerNode: ASDisplayNode {
var cancel: (() -> Void)? var cancel: (() -> Void)?
var dismissInput: (() -> 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.account = account
self.theme = theme self.theme = theme
self.strings = strings self.strings = strings
self.controllerInteraction = controllerInteraction self.controllerInteraction = controllerInteraction
self.peer = peer
self.mode = mode
self.webSearchInterfaceState = WebSearchInterfaceState(presentationData: account.telegramApplicationContext.currentPresentationData.with { $0 }) self.webSearchInterfaceState = WebSearchInterfaceState(presentationData: account.telegramApplicationContext.currentPresentationData.with { $0 })
self.webSearchInterfaceStatePromise = ValuePromise(self.webSearchInterfaceState, ignoreRepeated: true) self.webSearchInterfaceStatePromise = ValuePromise(self.webSearchInterfaceState, ignoreRepeated: true)
@ -204,7 +206,9 @@ class WebSearchControllerNode: ASDisplayNode {
self.addSubnode(self.recentQueriesNode) self.addSubnode(self.recentQueriesNode)
self.addSubnode(self.segmentedBackgroundNode) self.addSubnode(self.segmentedBackgroundNode)
self.addSubnode(self.segmentedSeparatorNode) self.addSubnode(self.segmentedSeparatorNode)
if case .media = mode {
self.view.addSubview(self.segmentedControl) self.view.addSubview(self.segmentedControl)
}
self.addSubnode(self.toolbarBackgroundNode) self.addSubnode(self.toolbarBackgroundNode)
self.addSubnode(self.toolbarSeparatorNode) self.addSubnode(self.toolbarSeparatorNode)
self.addSubnode(self.cancelButton) self.addSubnode(self.cancelButton)
@ -247,7 +251,7 @@ class WebSearchControllerNode: ASDisplayNode {
self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in
if let strongSelf = self, let bottom = visibleItems.bottom, let entries = strongSelf.currentEntries { if let strongSelf = self, let bottom = visibleItems.bottom, let entries = strongSelf.currentEntries {
if bottom.0 <= entries.count { if bottom.0 <= entries.count {
strongSelf.loadMore() //strongSelf.loadMore()
} }
} }
} }
@ -291,7 +295,12 @@ class WebSearchControllerNode: ASDisplayNode {
func applyPresentationData(themeUpdated: Bool = true) { 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.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 { if themeUpdated {
self.backgroundColor = self.theme.chatList.backgroundColor self.backgroundColor = self.theme.chatList.backgroundColor
@ -322,7 +331,7 @@ class WebSearchControllerNode: ASDisplayNode {
var insets = layout.insets(options: [.input]) var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight 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 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))) 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 { 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.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 let toolbarPadding: CGFloat = 15.0
@ -352,6 +361,20 @@ class WebSearchControllerNode: ASDisplayNode {
let sendSize = self.sendButton.measure(CGSize(width: layout.size.width, height: toolbarHeight)) 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))) 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 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.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) 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) self.webSearchInterfaceStatePromise.set(self.webSearchInterfaceState)
if let state = interfaceState.state { 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 { if let validLayout = self.containerLayout {
@ -432,12 +467,8 @@ class WebSearchControllerNode: ASDisplayNode {
} }
strongSelf.isLoadingMore = false strongSelf.isLoadingMore = false
var results: [ChatContextResult] = [] var results: [ChatContextResult] = []
for result in currentProcessedResults.results { results.append(contentsOf: currentProcessedResults.results)
results.append(result) results.append(contentsOf: nextResults.results.suffix(from: 1))
}
for result in nextResults.results {
results.append(result)
}
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) 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.currentProcessedResults = mergedResults
strongSelf.results.set(mergedResults) strongSelf.results.set(mergedResults)
@ -527,11 +558,13 @@ class WebSearchControllerNode: ASDisplayNode {
} }
@objc private func indexChanged() { @objc private func indexChanged() {
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 self.requestUpdateInterfaceState(true) { current in
if let mode = WebSearchMode(rawValue: Int32(self.segmentedControl.selectedSegmentIndex)) { return current.withUpdatedScope(scope)
return current.withUpdatedMode(mode)
} }
return current
} }
} }
@ -540,24 +573,23 @@ class WebSearchControllerNode: ASDisplayNode {
} }
@objc private func sendPressed() { @objc private func sendPressed() {
if let results = self.currentProcessedResults { self.controllerInteraction.sendSelected(nil)
self.controllerInteraction.sendSelected(results, nil)
}
self.cancel?() self.cancel?()
} }
func openResult(currentResult: ChatContextResult, present: (ViewController, Any?) -> Void) { func openResult(currentResult: ChatContextResult, present: (ViewController, Any?) -> Void) {
if let state = self.webSearchInterfaceState.state, state.mode == .images { if self.controllerInteraction.selectionState != nil {
if let state = self.webSearchInterfaceState.state, state.scope == .images {
if let results = self.currentProcessedResults?.results { 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 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)) self?.hiddenMediaId.set(.single(id))
}, initialLayout: self.containerLayout?.0, transitionHostView: { [weak self] in }, initialLayout: self.containerLayout?.0, transitionHostView: { [weak self] in
return self?.gridNode.view return self?.gridNode.view
}, transitionView: { [weak self] result in }, transitionView: { [weak self] result in
return self?.transitionView(for: result) return self?.transitionView(for: result)
}, completed: { [weak self] result in }, completed: { [weak self] result in
if let strongSelf = self, let results = strongSelf.currentProcessedResults { if let strongSelf = self {
strongSelf.controllerInteraction.sendSelected(results, nil) strongSelf.controllerInteraction.sendSelected(result)
strongSelf.cancel?() strongSelf.cancel?()
} }
}, present: present) }, present: present)
@ -573,7 +605,7 @@ class WebSearchControllerNode: ASDisplayNode {
} }
} }
let controller = WebSearchGalleryController(account: self.account, entries: entries, centralIndex: centralIndex, replaceRootController: { (controller, _) in let controller = WebSearchGalleryController(account: self.account, peer: self.peer, entries: entries, centralIndex: centralIndex, replaceRootController: { (controller, _) in
}, baseNavigationController: nil) }, baseNavigationController: nil)
self.hiddenMediaId.set((controller.hiddenMedia |> deliverOnMainQueue) self.hiddenMediaId.set((controller.hiddenMedia |> deliverOnMainQueue)
@ -602,6 +634,20 @@ class WebSearchControllerNode: ASDisplayNode {
})) }))
} }
} }
} 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)
}
} }
private func transitionView(for result: ChatContextResult) -> UIView? { private func transitionView(for result: ChatContextResult) -> UIView? {

View File

@ -16,14 +16,14 @@ struct WebSearchGalleryEntry: Equatable {
func item(account: Account, presentationData: PresentationData) -> GalleryItem { func item(account: Account, presentationData: PresentationData) -> GalleryItem {
switch self.result { 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 { 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: [])])) 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 { 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() preconditionFailure()
@ -31,9 +31,11 @@ struct WebSearchGalleryEntry: Equatable {
} }
final class WebSearchGalleryControllerPresentationArguments { final class WebSearchGalleryControllerPresentationArguments {
let animated: Bool
let transitionArguments: (WebSearchGalleryEntry) -> GalleryTransitionArguments? let transitionArguments: (WebSearchGalleryEntry) -> GalleryTransitionArguments?
init(transitionArguments: @escaping (WebSearchGalleryEntry) -> GalleryTransitionArguments?) { init(animated: Bool = true, transitionArguments: @escaping (WebSearchGalleryEntry) -> GalleryTransitionArguments?) {
self.animated = animated
self.transitionArguments = transitionArguments self.transitionArguments = transitionArguments
} }
} }
@ -63,6 +65,8 @@ class WebSearchGalleryController: ViewController {
private let centralItemFooterContentNode = Promise<GalleryFooterContentNode?>() private let centralItemFooterContentNode = Promise<GalleryFooterContentNode?>()
private let centralItemAttributesDisposable = DisposableSet(); private let centralItemAttributesDisposable = DisposableSet();
private var checkNode: GalleryNavigationCheckNode?
private let _hiddenMedia = Promise<WebSearchGalleryEntry?>(nil) private let _hiddenMedia = Promise<WebSearchGalleryEntry?>(nil)
var hiddenMedia: Signal<WebSearchGalleryEntry?, NoError> { var hiddenMedia: Signal<WebSearchGalleryEntry?, NoError> {
return self._hiddenMedia.get() return self._hiddenMedia.get()
@ -71,7 +75,7 @@ class WebSearchGalleryController: ViewController {
private let replaceRootController: (ViewController, ValuePromise<Bool>?) -> Void private let replaceRootController: (ViewController, ValuePromise<Bool>?) -> Void
private let baseNavigationController: NavigationController? 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.account = account
self.replaceRootController = replaceRootController self.replaceRootController = replaceRootController
self.baseNavigationController = baseNavigationController self.baseNavigationController = baseNavigationController
@ -80,8 +84,16 @@ class WebSearchGalleryController: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))) 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)) if let title = peer?.displayTitle {
self.navigationItem.leftBarButtonItem = backItem 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 self.statusBar.statusBarStyle = .White
@ -236,7 +248,11 @@ class WebSearchGalleryController: ViewController {
if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) { if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) {
nodeAnimatesItself = true nodeAnimatesItself = true
centralItemNode.activateAsInitial()
if presentationArguments.animated {
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface) centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
}
self._hiddenMedia.set(.single(self.entries[centralItemNode.index])) self._hiddenMedia.set(.single(self.entries[centralItemNode.index]))
} }

View 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() {
}
}

View File

@ -1,26 +1,13 @@
import Foundation import Foundation
struct WebSearchSelectionState: Equatable { enum WebSearchScope: Int32 {
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 {
case images case images
case gifs case gifs
} }
struct WebSearchInterfaceInnerState: Equatable { struct WebSearchInterfaceInnerState: Equatable {
let mode: WebSearchMode let scope: WebSearchScope
let query: String let query: String
let selectionState: WebSearchSelectionState
} }
struct WebSearchInterfaceState: Equatable { struct WebSearchInterfaceState: Equatable {
@ -37,27 +24,12 @@ struct WebSearchInterfaceState: Equatable {
self.presentationData = presentationData self.presentationData = presentationData
} }
func withUpdatedMode(_ mode: WebSearchMode) -> WebSearchInterfaceState { func withUpdatedScope(_ scope: WebSearchScope) -> WebSearchInterfaceState {
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(mode: mode, query: self.state?.query ?? "", selectionState: self.state?.selectionState ?? WebSearchSelectionState(selectedIds: [])), presentationData: self.presentationData) return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(scope: scope, query: self.state?.query ?? ""), presentationData: self.presentationData)
} }
func withUpdatedQuery(_ query: String) -> WebSearchInterfaceState { 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) return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(scope: self.state?.scope ?? .images, query: query), 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)
} }
func withUpdatedPresentationData(_ presentationData: PresentationData) -> WebSearchInterfaceState { func withUpdatedPresentationData(_ presentationData: PresentationData) -> WebSearchInterfaceState {

View File

@ -40,7 +40,7 @@ final class WebSearchItem: GridItem {
final class WebSearchItemNode: GridItemNode { final class WebSearchItemNode: GridItemNode {
private let imageNodeBackground: ASDisplayNode private let imageNodeBackground: ASDisplayNode
private let imageNode: TransformImageNode private let imageNode: TransformImageNode
private var selectionNode: GridMessageSelectionNode? private var checkNode: CheckNode?
private var currentImageResource: TelegramMediaResource? private var currentImageResource: TelegramMediaResource?
private var currentVideoFile: TelegramMediaFile? private var currentVideoFile: TelegramMediaFile?
@ -52,8 +52,6 @@ final class WebSearchItemNode: GridItemNode {
private let fetchDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable()
private var resourceStatus: MediaResourceStatus? private var resourceStatus: MediaResourceStatus?
private let statusNode: RadialStatusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
override init() { override init() {
self.imageNodeBackground = ASDisplayNode() self.imageNodeBackground = ASDisplayNode()
self.imageNodeBackground.isLayerBacked = true self.imageNodeBackground.isLayerBacked = true
@ -86,13 +84,14 @@ final class WebSearchItemNode: GridItemNode {
func setup(item: WebSearchItem) { func setup(item: WebSearchItem) {
if self.item !== item { if self.item !== item {
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
var thumbnailDimensions: CGSize?
var thumbnailResource: TelegramMediaResource?
var imageResource: TelegramMediaResource? var imageResource: TelegramMediaResource?
var videoFile: TelegramMediaFile? var videoFile: TelegramMediaFile?
var imageDimensions: CGSize? var imageDimensions: CGSize?
switch item.result { switch item.result {
case let .externalReference(_, _, type, title, _, url, content, thumbnail, _): case let .externalReference(_, _, type, _, _, _, content, thumbnail, _):
if let content = content { if let content = content {
imageResource = content.resource imageResource = content.resource
} else if let thumbnail = thumbnail { } 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: [])]) 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 imageResource = nil
} }
case let .internalReference(_, _, _, _, _, image, file, _):
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, _):
if let image = image { if let image = image {
if let largestRepresentation = largestImageRepresentation(image.representations) { if let largestRepresentation = largestImageRepresentation(image.representations) {
imageDimensions = largestRepresentation.dimensions imageDimensions = largestRepresentation.dimensions
} }
imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 100.0))?.resource 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 { } else if let file = file {
if let dimensions = file.dimensions { if let dimensions = file.dimensions {
imageDimensions = dimensions imageDimensions = dimensions
@ -123,32 +120,24 @@ final class WebSearchItemNode: GridItemNode {
} }
imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource 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 { if let imageResource = imageResource, let imageDimensions = imageDimensions {
let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource) var representations: [TelegramMediaImageRepresentation] = []
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], reference: nil, partialReference: nil) 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)) updateImageSignal = mediaGridMessagePhoto(account: item.account, photoReference: .standalone(media: tmpImage))
} else { } else {
updateImageSignal = .complete() updateImageSignal = .complete()
} }
if let updateImageSignal = updateImageSignal { if let updateImageSignal = updateImageSignal {
let editingContext = item.controllerInteraction.editingContext let editingContext = item.controllerInteraction.editingState
let editedImageSignal = Signal<UIImage?, NoError> { subscriber in 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) { if let signal = editingContext.thumbnailImageSignal(for: editableItem) {
let disposable = signal.start(next: { next in let disposable = signal.start(next: { next in
if let image = next as? UIImage { if let image = next as? UIImage {
@ -205,41 +194,45 @@ final class WebSearchItemNode: GridItemNode {
if let _ = imageDimensions { if let _ = imageDimensions {
self.setNeedsLayout() self.setNeedsLayout()
} }
self.updateHiddenMedia()
} }
self.item = item self.item = item
self.updateSelectionState(animated: false) self.updateSelectionState(animated: false)
} }
func updateSelectionState(animated: Bool) { @objc func toggleSelection() {
if self.selectionNode == nil, let item = self.item { if let checkNode = self.checkNode, let item = self.item {
let selectionNode = GridMessageSelectionNode(theme: item.theme, toggle: { [weak self] value in checkNode.setIsChecked(!checkNode.isChecked, animated: true)
if let strongSelf = self, let item = strongSelf.item { item.controllerInteraction.toggleSelection(item.result, checkNode.isChecked)
item.controllerInteraction.toggleSelection([item.result.id], value)
strongSelf.updateSelectionState(animated: true)
} }
}) }
self.addSubnode(selectionNode)
self.selectionNode = selectionNode func updateSelectionState(animated: Bool) {
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() self.setNeedsLayout()
} }
if let item = self.item { if let item = self.item {
if let selectionState = item.controllerInteraction.selectionState { if let selectionState = item.controllerInteraction.selectionState {
let selected = selectionState.selectedIds.contains(item.result.id) let selected = selectionState.isIdentifierSelected(item.result.id)
self.selectionNode?.updateSelected(selected, animated: animated) self.checkNode?.setIsChecked(selected, animated: animated)
} }
} }
} }
func updateHiddenMedia() { func updateHiddenMedia() {
if let item = self.item { 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 { 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) view.frame = self.convert(self.bounds, to: nil)
return view return view
} }
@ -256,11 +249,7 @@ final class WebSearchItemNode: GridItemNode {
} }
let checkSize = CGSize(width: 32.0, height: 32.0) 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) self.checkNode?.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)
} }
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { @objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
@ -274,9 +263,6 @@ final class WebSearchItemNode: GridItemNode {
switch gesture { switch gesture {
case .tap: case .tap:
item.controllerInteraction.openResult(item.result) item.controllerInteraction.openResult(item.result)
case .longTap:
break
default: default:
break break
} }

View File

@ -75,6 +75,7 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
private var textNode: TextNode? private var textNode: TextNode?
private var item: WebSearchRecentQueryItem? private var item: WebSearchRecentQueryItem?
private var layoutParams: ListViewItemLayoutParams?
required init() { required init() {
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
@ -152,6 +153,7 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
return (nil, { _ in return (nil, { _ in
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
strongSelf.layoutParams = params
if let _ = updatedTheme { if let _ = updatedTheme {
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
@ -205,95 +207,16 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition) super.updateRevealOffset(offset: offset, transition: transition)
// if let _ = self.item, let params = self.layoutParams?.5 { if let params = self.layoutParams, let textNode = self.textNode {
// let editingOffset: CGFloat let leftInset: CGFloat = 15.0 + params.leftInset
// if let selectableControlNode = self.selectableControlNode {
// editingOffset = selectableControlNode.bounds.size.width var textFrame = textNode.frame
// var selectableControlFrame = selectableControlNode.frame textFrame.origin.x = leftInset + offset
// selectableControlFrame.origin.x = params.leftInset + offset transition.updateFrame(node: textNode, frame: textFrame)
// 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))
// }
} }
override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) { override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
var close = true
if let item = self.item { if let item = self.item {
switch option.key { switch option.key {
case RevealOptionKey.delete.rawValue: case RevealOptionKey.delete.rawValue:
@ -302,9 +225,7 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
break break
} }
} }
if close {
self.setRevealOptionsOpened(false, animated: true) self.setRevealOptionsOpened(false, animated: true)
self.revealOptionsInteractivelyClosed() self.revealOptionsInteractivelyClosed()
} }
} }
}

View File

@ -3,22 +3,22 @@ import Postbox
import SwiftSignalKit import SwiftSignalKit
struct WebSearchSettings: Equatable, PreferencesEntry { struct WebSearchSettings: Equatable, PreferencesEntry {
var mode: WebSearchMode var scope: WebSearchScope
static var defaultSettings: WebSearchSettings { static var defaultSettings: WebSearchSettings {
return WebSearchSettings(mode: .images) return WebSearchSettings(scope: .images)
} }
init(mode: WebSearchMode) { init(scope: WebSearchScope) {
self.mode = mode self.scope = scope
} }
init(decoder: PostboxDecoder) { 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) { func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.mode.rawValue, forKey: "mode") encoder.encodeInt32(self.scope.rawValue, forKey: "scope")
} }
func isEqual(to: PreferencesEntry) -> Bool { func isEqual(to: PreferencesEntry) -> Bool {

View 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)
}
}

View File

@ -72,11 +72,11 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
} }
if let text = self.titleString?.string { 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 { 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() self.updateWebpage()
@ -101,6 +101,16 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
authorName = self.strings.Channel_NotificationLoading authorName = self.strings.Channel_NotificationLoading
text = self.url text = self.url
case let .Loaded(content): 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 { if let title = content.title {
authorName = title authorName = title
} else if let websiteName = content.websiteName { } else if let websiteName = content.websiteName {
@ -108,7 +118,7 @@ final class WebpagePreviewAccessoryPanelNode: AccessoryPanelNode {
} else { } else {
authorName = content.displayUrl authorName = content.displayUrl
} }
text = content.text ?? ""
} }
self.titleString = NSAttributedString(string: authorName, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor) self.titleString = NSAttributedString(string: authorName, font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor)