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,28 +650,38 @@ 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
if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = currentAvatarMixin.swap(mixin) 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 mixin.didFinishWithImage = { image in
if let image = image { if let image = image {
if let data = UIImageJPEGRepresentation(image, 0.6) { completedImpl(image)
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
} }
} }
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {

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

@ -43,18 +43,18 @@ final class ContactsControllerNode: ASDisplayNode {
self.addSubnode(self.contactListNode) self.addSubnode(self.contactListNode)
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in |> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self { if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings() strongSelf.updateThemeAndStrings()
}
} }
}) }
})
inviteImpl = { [weak self] in inviteImpl = { [weak self] in
let _ = (DeviceAccess.contacts let _ = (DeviceAccess.contacts

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,56 +258,72 @@ public func createChannelController(account: Account) -> ViewController {
})) }))
} }
}, changeProfilePhoto: { }, changeProfilePhoto: {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
return (transaction.getPeer(account.peerId), currentSearchBotsConfiguration(transaction: transaction))
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) } |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
legacyController.statusBar.statusBarStyle = .Ignore let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let emptyController = LegacyEmptyController(context: legacyController.context)! let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
let navigationController = makeLegacyNavigationController(rootController: emptyController) legacyController.statusBar.statusBarStyle = .Ignore
navigationController.setNavigationBarHidden(true, animated: false)
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) let emptyController = LegacyEmptyController(context: legacyController.context)!
let navigationController = makeLegacyNavigationController(rootController: emptyController)
legacyController.bind(controller: navigationController) navigationController.setNavigationBarHidden(true, animated: false)
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
endEditingImpl?()
presentControllerImpl?(legacyController, nil) legacyController.bind(controller: navigationController)
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)! endEditingImpl?()
let _ = currentAvatarMixin.swap(mixin) presentControllerImpl?(legacyController, nil)
mixin.didFinishWithImage = { image in
if let image = image, let data = UIImageJPEGRepresentation(image, 0.6) { let completedImpl: (UIImage) -> Void = { image in
let resource = LocalFileMediaResource(fileId: arc4random64()) if let data = UIImageJPEGRepresentation(image, 0.6) {
account.postbox.mediaBox.storeResourceData(resource.id, data: data) let resource = LocalFileMediaResource(fileId: arc4random64())
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource) account.postbox.mediaBox.storeResourceData(resource.id, data: data)
uploadedAvatar.set(uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState { current in uploadedAvatar.set(uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource))
var current = current updateState { current in
current.avatar = .image(representation, false) var current = current
return current current.avatar = .image(representation, false)
return current
}
} }
} }
}
if stateValue.with({ $0.avatar }) != nil { 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)!
mixin.didFinishWithDelete = { let _ = currentAvatarMixin.swap(mixin)
updateState { current in mixin.requestSearchController = { _ in
var current = current let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
current.avatar = nil completedImpl(result)
return current }))
} presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
uploadedAvatar.set(.never())
} }
} mixin.didFinishWithImage = { image in
mixin.didDismiss = { [weak legacyController] in if let image = image {
let _ = currentAvatarMixin.swap(nil) completedImpl(image)
legacyController?.dismiss() }
} }
let menuController = mixin.present() if stateValue.with({ $0.avatar }) != nil {
if let menuController = menuController { mixin.didFinishWithDelete = {
menuController.customRemoveFromParentViewController = { [weak legacyController] in updateState { current in
var current = current
current.avatar = nil
return current
}
uploadedAvatar.set(.never())
}
}
mixin.didDismiss = { [weak legacyController] in
let _ = currentAvatarMixin.swap(nil)
legacyController?.dismiss() legacyController?.dismiss()
} }
} let menuController = mixin.present()
if let menuController = menuController {
menuController.customRemoveFromParentViewController = { [weak legacyController] in
legacyController?.dismiss()
}
}
})
}) })
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get()) let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get())

View File

@ -277,56 +277,72 @@ public func createGroupController(account: Account, peerIds: [PeerId]) -> ViewCo
})) }))
} }
}, changeProfilePhoto: { }, changeProfilePhoto: {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
return (transaction.getPeer(account.peerId), currentSearchBotsConfiguration(transaction: transaction))
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) } |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
legacyController.statusBar.statusBarStyle = .Ignore let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let emptyController = LegacyEmptyController(context: legacyController.context)! let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
let navigationController = makeLegacyNavigationController(rootController: emptyController) legacyController.statusBar.statusBarStyle = .Ignore
navigationController.setNavigationBarHidden(true, animated: false)
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) let emptyController = LegacyEmptyController(context: legacyController.context)!
let navigationController = makeLegacyNavigationController(rootController: emptyController)
legacyController.bind(controller: navigationController) navigationController.setNavigationBarHidden(true, animated: false)
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
endEditingImpl?()
presentControllerImpl?(legacyController, nil) legacyController.bind(controller: navigationController)
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: stateValue.with({ $0.avatar }) != nil, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)! endEditingImpl?()
let _ = currentAvatarMixin.swap(mixin) presentControllerImpl?(legacyController, nil)
mixin.didFinishWithImage = { image in
if let image = image, let data = UIImageJPEGRepresentation(image, 0.6) { let completedImpl: (UIImage) -> Void = { image in
let resource = LocalFileMediaResource(fileId: arc4random64()) if let data = UIImageJPEGRepresentation(image, 0.6) {
account.postbox.mediaBox.storeResourceData(resource.id, data: data) let resource = LocalFileMediaResource(fileId: arc4random64())
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource) account.postbox.mediaBox.storeResourceData(resource.id, data: data)
uploadedAvatar.set(uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState { current in uploadedAvatar.set(uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource))
var current = current updateState { current in
current.avatar = .image(representation, false) var current = current
return current current.avatar = .image(representation, false)
return current
}
} }
} }
}
if stateValue.with({ $0.avatar }) != nil { 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)!
mixin.didFinishWithDelete = { let _ = currentAvatarMixin.swap(mixin)
updateState { current in mixin.requestSearchController = { _ in
var current = current let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
current.avatar = nil completedImpl(result)
return current }))
} presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
uploadedAvatar.set(.never())
} }
} mixin.didFinishWithImage = { image in
mixin.didDismiss = { [weak legacyController] in if let image = image {
let _ = currentAvatarMixin.swap(nil) completedImpl(image)
legacyController?.dismiss() }
} }
let menuController = mixin.present() if stateValue.with({ $0.avatar }) != nil {
if let menuController = menuController { mixin.didFinishWithDelete = {
menuController.customRemoveFromParentViewController = { [weak legacyController] in updateState { current in
var current = current
current.avatar = nil
return current
}
uploadedAvatar.set(.never())
}
}
mixin.didDismiss = { [weak legacyController] in
let _ = currentAvatarMixin.swap(nil)
legacyController?.dismiss() legacyController?.dismiss()
} }
} let menuController = mixin.present()
if let menuController = menuController {
menuController.customRemoveFromParentViewController = { [weak legacyController] in
legacyController?.dismiss()
}
}
})
}) })
let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), account.postbox.multiplePeersView(peerIds)) let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), account.postbox.multiplePeersView(peerIds))

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,64 +420,39 @@ 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 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
legacyController.statusBar.statusBarStyle = .Ignore legacyController.statusBar.statusBarStyle = .Ignore
let emptyController = LegacyEmptyController(context: legacyController.context)! let emptyController = LegacyEmptyController(context: legacyController.context)!
let navigationController = makeLegacyNavigationController(rootController: emptyController) let navigationController = makeLegacyNavigationController(rootController: emptyController)
navigationController.setNavigationBarHidden(true, animated: false) navigationController.setNavigationBarHidden(true, animated: false)
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0) navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
legacyController.bind(controller: navigationController) legacyController.bind(controller: navigationController)
presentControllerImpl?(legacyController, nil) presentControllerImpl?(legacyController, nil)
var hasPhotos = false var hasPhotos = false
if let peer = peer, !peer.profileImageRepresentations.isEmpty { if let peer = peer, !peer.profileImageRepresentations.isEmpty {
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) if let data = UIImageJPEGRepresentation(image, 0.6) {
mixin.didFinishWithImage = { image in let resource = LocalFileMediaResource(fileId: arc4random64())
if let image = image { account.postbox.mediaBox.storeResourceData(resource.id, data: data)
if let data = UIImageJPEGRepresentation(image, 0.6) { let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: resource) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
}
}
mixin.didFinishWithDelete = {
let _ = currentAvatarMixin.swap(nil)
updateState { updateState {
if let profileImage = peer?.smallProfileImage { $0.withUpdatedUpdatingAvatar(.image(representation, true))
return $0.withUpdatedUpdatingAvatar(.image(profileImage, false))
} else {
return $0.withUpdatedUpdatingAvatar(.none)
}
} }
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: nil) |> deliverOnMainQueue).start(next: { result in updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: resource) |> deliverOnMainQueue).start(next: { result in
switch result { switch result {
case .complete: case .complete:
updateState { updateState {
@ -488,38 +463,73 @@ func editSettingsController(account: Account, currentName: ItemListAvatarAndName
} }
})) }))
} }
mixin.didFinishWithView = { }
let _ = currentAvatarMixin.swap(nil)
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: hasPhotos, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = (account.postbox.loadedPeerWithId(account.peerId) let _ = currentAvatarMixin.swap(mixin)
|> take(1) mixin.requestSearchController = { _ in
|> deliverOnMainQueue).start(next: { peer in let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
if peer.smallProfileImage != nil { completedImpl(result)
let galleryController = AvatarGalleryController(account: account, peer: peer, replaceRootController: { controller, ready in }))
}) presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
/*hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in }
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first mixin.didFinishWithImage = { image in
updateHiddenAvatarImpl?() if let image = image {
}))*/ completedImpl(image)
presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in
return nil
}))
} else {
changeProfilePhotoImpl?()
}
})
} }
mixin.didDismiss = { [weak legacyController] in }
let _ = currentAvatarMixin.swap(nil) mixin.didFinishWithDelete = {
legacyController?.dismiss() let _ = currentAvatarMixin.swap(nil)
} updateState {
let menuController = mixin.present() if let profileImage = peer?.smallProfileImage {
if let menuController = menuController { return $0.withUpdatedUpdatingAvatar(.image(profileImage, false))
menuController.customRemoveFromParentViewController = { [weak legacyController] in } else {
legacyController?.dismiss() return $0.withUpdatedUpdatingAvatar(.none)
} }
} }
}) updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: nil) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
mixin.didFinishWithView = {
let _ = currentAvatarMixin.swap(nil)
let _ = (account.postbox.loadedPeerWithId(account.peerId)
|> take(1)
|> deliverOnMainQueue).start(next: { peer in
if peer.smallProfileImage != nil {
let galleryController = AvatarGalleryController(account: account, peer: peer, replaceRootController: { controller, ready in
})
/*hiddenAvatarRepresentationDisposable.set((galleryController.hiddenMedia |> deliverOnMainQueue).start(next: { entry in
avatarAndNameInfoContext.hiddenAvatarRepresentation = entry?.representations.first
updateHiddenAvatarImpl?()
}))*/
presentControllerImpl?(galleryController, AvatarGalleryControllerPresentationArguments(transitionArguments: { entry in
return nil
}))
} else {
changeProfilePhotoImpl?()
}
})
}
mixin.didDismiss = { [weak legacyController] in
let _ = currentAvatarMixin.swap(nil)
legacyController?.dismiss()
}
let menuController = mixin.present()
if let menuController = menuController {
menuController.customRemoveFromParentViewController = { [weak legacyController] in
legacyController?.dismiss()
}
}
})
} }
return controller return controller

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,28 +1225,38 @@ 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
if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = currentAvatarMixin.swap(mixin) 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 mixin.didFinishWithImage = { image in
if let image = image { if let image = image {
if let data = UIImageJPEGRepresentation(image, 0.6) { completedImpl(image)
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
} }
} }
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {
@ -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)?
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
inviteByLinkImpl?() var canCreateInviteLink = false
})) if let group = groupPeer as? TelegramGroup {
if case .creator = group.role {
canCreateInviteLink = true
}
} else if let channel = groupPeer as? TelegramChannel {
if channel.hasAdminRights(.canInviteUsers) {
canCreateInviteLink = true
} else if case let .group(info) = channel.info, info.flags.contains(.everyMemberCanInviteMembers) {
canCreateInviteLink = true
}
}
if canCreateInviteLink {
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
inviteByLinkImpl?()
}))
}
let contactsController: ViewController 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) { [jpgData appendBytes:&byte length:1];
// skippedBytes++;
// } else {
[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

@ -44,18 +44,18 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr
} }
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in |> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self { if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings() strongSelf.updateThemeAndStrings()
}
} }
}) }
})
} }
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {

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)
subscriber?.putCompletion() if !image.degraded() {
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,63 +193,90 @@ private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGMo
} }
} }
private func galleryItems(account: Account, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext, editingContext: TGMediaEditingContext) -> ([TGModernGalleryItem], TGModernGalleryItem?) { func legacyWebSearchItem(account: Account, result: ChatContextResult) -> LegacyWebSearchItem? {
var thumbnailDimensions: CGSize?
var thumbnailResource: TelegramMediaResource?
var imageResource: TelegramMediaResource?
var imageDimensions = CGSize()
let thumbnailSignal: Signal<UIImage, NoError>
let originalSignal: Signal<UIImage, NoError>
switch result {
case let .externalReference(_, _, _, _, _, _, content, thumbnail, _):
if let content = content {
imageResource = content.resource
}
if let thumbnail = thumbnail {
thumbnailResource = thumbnail.resource
thumbnailDimensions = thumbnail.dimensions
}
if let dimensions = content?.dimensions {
imageDimensions = dimensions
}
case let .internalReference(_, _, _, _, _, image, _, _):
if let image = image {
if let largestRepresentation = largestImageRepresentation(image.representations) {
imageDimensions = largestRepresentation.dimensions
imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 1000.0, height: 800.0))?.resource
}
if let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 100.0)) {
thumbnailDimensions = thumbnailRepresentation.dimensions
thumbnailResource = thumbnailRepresentation.resource
}
}
}
if let imageResource = imageResource {
let progressSignal = account.postbox.mediaBox.resourceStatus(imageResource)
|> map { status -> Float in
switch status {
case .Local:
return 1.0
case .Remote:
return 0.0
case let .Fetching(_, progress):
return progress
}
}
var representations: [TelegramMediaImageRepresentation] = []
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource))
}
representations.append(TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource))
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, reference: nil, partialReference: nil)
thumbnailSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: false)
|> mapToSignal { (thumbnailData, _, _) -> Signal<UIImage, NoError> in
if let data = thumbnailData, let image = UIImage(data: data) {
return .single(image)
} else {
return .complete()
}
}
originalSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: true)
|> mapToSignal { (thumbnailData, fullSizeData, fullSizeComplete) -> Signal<UIImage, NoError> in
if fullSizeComplete, let data = fullSizeData, let image = UIImage(data: data) {
return .single(image)
} else if let data = thumbnailData, let image = UIImage(data: data) {
image.setDegraded(true)
return .single(image)
} else {
return .complete()
}
}
return LegacyWebSearchItem(result: result, dimensions: imageDimensions, thumbnailImage: thumbnailSignal, originalImage: originalSignal, progress: progressSignal)
} else {
return nil
}
}
private func galleryItems(account: Account, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext) -> ([TGModernGalleryItem], TGModernGalleryItem?) {
var focusItem: TGModernGalleryItem? var focusItem: TGModernGalleryItem?
var galleryItems: [TGModernGalleryItem] = [] var galleryItems: [TGModernGalleryItem] = []
for result in results { for result in results {
var thumbnailDimensions: CGSize? if let item = legacyWebSearchItem(account: account, result: result) {
var thumbnailResource: TelegramMediaResource?
var imageResource: TelegramMediaResource?
var imageDimensions = CGSize()
let thumbnailSignal: Signal<UIImage, NoError>
let originalSignal: Signal<UIImage, NoError>
switch result {
case let .externalReference(_, _, _, _, _, _, content, thumbnail, _):
if let content = content {
imageResource = content.resource
}
if let thumbnail = thumbnail {
thumbnailResource = thumbnail.resource
thumbnailDimensions = thumbnail.dimensions
}
if let dimensions = content?.dimensions {
imageDimensions = dimensions
}
// if let imageResource = imageResource {
// updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
// }
case let .internalReference(_, _, _, _, _, image, _, _):
if let image = image {
if let largestRepresentation = largestImageRepresentation(image.representations) {
imageDimensions = largestRepresentation.dimensions
imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 1000.0, height: 800.0))?.resource
}
if let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 100.0)) {
thumbnailDimensions = thumbnailRepresentation.dimensions
thumbnailResource = thumbnailRepresentation.resource
}
}
}
if let imageResource = imageResource {
var representations: [TelegramMediaImageRepresentation] = []
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource))
}
representations.append(TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource))
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, reference: nil, partialReference: nil)
thumbnailSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: true)
|> mapToSignal { (thumbnailData, fullSizeData, fullSizeComplete) -> Signal<UIImage, NoError> in
if let data = fullSizeData, let image = UIImage(data: data) {
return .single(image)
} else {
return .complete()
}
}
originalSignal = thumbnailSignal
let item = LegacyWebSearchItem(result: result, dimensions: imageDimensions, thumbnailImage: thumbnailSignal, originalImage: originalSignal)
let galleryItem = LegacyWebSearchGalleryItem(item: item) 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,77 +71,84 @@ 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 {
switch media { if let kind = mediaContentKind(media, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId) {
case let expiredMedia as TelegramMediaExpiredContent: return kind
switch expiredMedia.data {
case .image:
return .expiredImage
case .file:
return .expiredVideo
}
case _ as TelegramMediaImage:
return .image
case let file as TelegramMediaFile:
var fileName: String = ""
for attribute in file.attributes {
switch attribute {
case let .Sticker(text, _, _):
return .sticker(text)
case let .FileName(name):
fileName = name
case let .Audio(isVoice, _, title, performer, _):
if isVoice {
return .audioMessage
} else {
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
return .file(title + "" + performer)
} else if let title = title, !title.isEmpty {
return .file(title)
} else if let performer = performer, !performer.isEmpty {
return .file(performer)
}
}
case let .Video(_, _, flags):
if file.isAnimated {
return .animation
} else {
if flags.contains(.instantRoundVideo) {
return .videoMessage
} else {
return .video
}
}
default:
break
}
}
return .file(fileName)
case _ as TelegramMediaContact:
return .contact
case let game as TelegramMediaGame:
return .game(game.title)
case let location as TelegramMediaMap:
if location.liveBroadcastingTimeout != nil {
return .liveLocation
} else {
return .location
}
case _ as TelegramMediaAction:
return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) ?? "")
case let poll as TelegramMediaPoll:
return .text(poll.text)
default:
break
} }
} }
return .text(message.text) return .text(message.text)
} }
func descriptionStringForMessage(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) { public func mediaContentKind(_ media: Media, message: Message? = nil, strings: PresentationStrings? = nil, nameDisplayOrder: PresentationPersonNameOrder? = nil, accountPeerId: PeerId? = nil) -> MessageContentKind? {
if !message.text.isEmpty { switch media {
return (message.text, false) case let expiredMedia as TelegramMediaExpiredContent:
switch expiredMedia.data {
case .image:
return .expiredImage
case .file:
return .expiredVideo
}
case _ as TelegramMediaImage:
return .image
case let file as TelegramMediaFile:
var fileName: String = ""
for attribute in file.attributes {
switch attribute {
case let .Sticker(text, _, _):
return .sticker(text)
case let .FileName(name):
fileName = name
case let .Audio(isVoice, _, title, performer, _):
if isVoice {
return .audioMessage
} else {
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
return .file(title + "" + performer)
} else if let title = title, !title.isEmpty {
return .file(title)
} else if let performer = performer, !performer.isEmpty {
return .file(performer)
}
}
case let .Video(_, _, flags):
if file.isAnimated {
return .animation
} else {
if flags.contains(.instantRoundVideo) {
return .videoMessage
} else {
return .video
}
}
default:
break
}
}
return .file(fileName)
case _ as TelegramMediaContact:
return .contact
case let game as TelegramMediaGame:
return .game(game.title)
case let location as TelegramMediaMap:
if location.liveBroadcastingTimeout != nil {
return .liveLocation
} else {
return .location
}
case _ as TelegramMediaAction:
if let message = message, let strings = strings, let nameDisplayOrder = nameDisplayOrder, let accountPeerId = accountPeerId {
return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) ?? "")
} else {
return nil
}
case let poll as TelegramMediaPoll:
return .text(poll.text)
default:
return nil
} }
switch messageContentKind(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId) { }
func stringForMediaKind(_ kind: MessageContentKind, strings: PresentationStrings) -> (String, Bool) {
switch kind {
case let .text(text): 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,13 +443,13 @@ 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
self.clearButton.isHidden = true self.clearButton.isHidden = true
self.textField.text = "" self.textField.text = ""
var backgroundCompleted = false var backgroundCompleted = false
var separatorCompleted = false var separatorCompleted = false
var textBackgroundCompleted = false var textBackgroundCompleted = false

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,28 +638,38 @@ 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
if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: resource) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = currentAvatarMixin.swap(mixin) 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 mixin.didFinishWithImage = { image in
if let image = image { if let image = image {
if let data = UIImageJPEGRepresentation(image, 0.6) { completedImpl(image)
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: resource) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
} }
} }
mixin.didFinishWithDelete = { mixin.didFinishWithDelete = {

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,17 +104,19 @@ 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 }
self.interfaceState = WebSearchInterfaceState(presentationData: presentationData) self.interfaceState = WebSearchInterfaceState(presentationData: presentationData)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme).withUpdatedSeparatorColor(presentationData.theme.rootController.navigationBar.backgroundColor), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme).withUpdatedSeparatorColor(presentationData.theme.rootController.navigationBar.backgroundColor), strings: NavigationBarStrings(presentationStrings: presentationData.strings)))
self.statusBar.statusBarStyle = presentationData.theme.rootController.statusBar.style.style self.statusBar.statusBarStyle = presentationData.theme.rootController.statusBar.style.style
@ -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)
}
if case let .media(sendSelected) = mode {
sendSelected(selectionState, editingState)
} }
sendSelected(Array(selectedIds), collection, editingContext)
} }
}, editingContext: editingContext) }, avatarCompleted: { result in
if case let .avatar(avatarCompleted) = mode {
avatarCompleted(result)
}
}, selectionState: selectionState, editingState: editingState)
if let selectionState = selectionState {
self.selectionDisposable = (selectionChangedSignal(selectionState: selectionState)
|> deliverOnMainQueue).start(next: { [weak self] _ in
if let strongSelf = self {
strongSelf.controllerNode.updateSelectionState(animated: true)
}
})
}
} }
required public init(coder aDecoder: NSCoder) { 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
case .avatar:
scope = .single(.images)
} }
|> distinctUntilChanged
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)
self.view.addSubview(self.segmentedControl) if case .media = mode {
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() {
self.requestUpdateInterfaceState(true) { current in if let scope = WebSearchScope(rawValue: Int32(self.segmentedControl.selectedSegmentIndex)) {
if let mode = WebSearchMode(rawValue: Int32(self.segmentedControl.selectedSegmentIndex)) { let _ = updateWebSearchSettingsInteractively(postbox: self.account.postbox) { _ -> WebSearchSettings in
return current.withUpdatedMode(mode) return WebSearchSettings(scope: scope)
}.start()
self.requestUpdateInterfaceState(true) { current in
return current.withUpdatedScope(scope)
} }
return current
} }
} }
@ -540,67 +573,80 @@ class WebSearchControllerNode: ASDisplayNode {
} }
@objc private func sendPressed() { @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 results = self.currentProcessedResults?.results { if let state = self.webSearchInterfaceState.state, state.scope == .images {
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 if let results = self.currentProcessedResults?.results {
self?.hiddenMediaId.set(.single(id)) 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
}, initialLayout: self.containerLayout?.0, transitionHostView: { [weak self] in self?.hiddenMediaId.set(.single(id))
return self?.gridNode.view }, initialLayout: self.containerLayout?.0, transitionHostView: { [weak self] in
}, transitionView: { [weak self] result in return self?.gridNode.view
return self?.transitionView(for: result) }, transitionView: { [weak self] result in
}, completed: { [weak self] result in return self?.transitionView(for: result)
if let strongSelf = self, let results = strongSelf.currentProcessedResults { }, completed: { [weak self] result in
strongSelf.controllerInteraction.sendSelected(results, nil) if let strongSelf = self {
strongSelf.cancel?() strongSelf.controllerInteraction.sendSelected(result)
} strongSelf.cancel?()
}, present: present) }
} }, present: present)
} else {
if let results = self.currentProcessedResults?.results {
var entries: [WebSearchGalleryEntry] = []
var centralIndex: Int = 0
for i in 0 ..< results.count {
entries.append(WebSearchGalleryEntry(result: results[i]))
if results[i] == currentResult {
centralIndex = i
}
} }
} else {
let controller = WebSearchGalleryController(account: self.account, entries: entries, centralIndex: centralIndex, replaceRootController: { (controller, _) in if let results = self.currentProcessedResults?.results {
var entries: [WebSearchGalleryEntry] = []
var centralIndex: Int = 0
for i in 0 ..< results.count {
entries.append(WebSearchGalleryEntry(result: results[i]))
if results[i] == currentResult {
centralIndex = i
}
}
}, baseNavigationController: nil) let controller = WebSearchGalleryController(account: self.account, peer: self.peer, entries: entries, centralIndex: centralIndex, replaceRootController: { (controller, _) in
self.hiddenMediaId.set((controller.hiddenMedia |> deliverOnMainQueue)
|> map { entry in }, baseNavigationController: nil)
return entry?.result.id self.hiddenMediaId.set((controller.hiddenMedia |> deliverOnMainQueue)
}) |> map { entry in
present(controller, WebSearchGalleryControllerPresentationArguments(transitionArguments: { [weak self] entry -> GalleryTransitionArguments? in return entry?.result.id
if let strongSelf = self { })
var transitionNode: WebSearchItemNode? present(controller, WebSearchGalleryControllerPresentationArguments(transitionArguments: { [weak self] entry -> GalleryTransitionArguments? in
strongSelf.gridNode.forEachItemNode { itemNode in if let strongSelf = self {
if let itemNode = itemNode as? WebSearchItemNode, itemNode.item?.result.id == entry.result.id { var transitionNode: WebSearchItemNode?
transitionNode = itemNode strongSelf.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? WebSearchItemNode, itemNode.item?.result.id == entry.result.id {
transitionNode = itemNode
}
}
if let transitionNode = transitionNode {
return GalleryTransitionArguments(transitionNode: (transitionNode, { [weak transitionNode] in
return transitionNode?.transitionView().snapshotContentTree(unhide: true)
}), addToTransitionSurface: { view in
if let strongSelf = self {
strongSelf.gridNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.gridNode.view)
}
})
} }
} }
if let transitionNode = transitionNode { return nil
return GalleryTransitionArguments(transitionNode: (transitionNode, { [weak transitionNode] in }))
return transitionNode?.transitionView().snapshotContentTree(unhide: true) }
}), addToTransitionSurface: { view in
if let strongSelf = self {
strongSelf.gridNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.gridNode.view)
}
})
}
}
return nil
}))
} }
} 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)
} }
} }

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,8 +248,12 @@ 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.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface) centralItemNode.activateAsInitial()
if presentationArguments.animated {
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
}
self._hiddenMedia.set(.single(self.entries[centralItemNode.index])) 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)
} }
@objc func toggleSelection() {
if let checkNode = self.checkNode, let item = self.item {
checkNode.setIsChecked(!checkNode.isChecked, animated: true)
item.controllerInteraction.toggleSelection(item.result, checkNode.isChecked)
}
}
func updateSelectionState(animated: Bool) { func updateSelectionState(animated: Bool) {
if self.selectionNode == nil, let item = self.item { if self.checkNode == nil, let item = self.item, let _ = item.controllerInteraction.selectionState {
let selectionNode = GridMessageSelectionNode(theme: item.theme, toggle: { [weak self] value in let checkNode = CheckNode(strokeColor: item.theme.list.itemCheckColors.strokeColor, fillColor: item.theme.list.itemCheckColors.fillColor, foregroundColor: item.theme.list.itemCheckColors.foregroundColor, style: .overlay)
if let strongSelf = self, let item = strongSelf.item { checkNode.addTarget(target: self, action: #selector(self.toggleSelection))
item.controllerInteraction.toggleSelection([item.result.id], value) self.addSubnode(checkNode)
strongSelf.updateSelectionState(animated: true) self.checkNode = checkNode
}
})
self.addSubnode(selectionNode)
self.selectionNode = selectionNode
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)