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

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

View File

@ -628,9 +628,9 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr
}))
})
}, changeProfilePhoto: {
let _ = (account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
} |> deliverOnMainQueue).start(next: { peer in
let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
return (transaction.getPeer(peerId), currentSearchBotsConfiguration(transaction: transaction))
} |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
@ -650,28 +650,38 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr
hasPhotos = true
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)!
let completedImpl: (UIImage) -> Void = { image in
if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { _ in
let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
completedImpl(result)
}))
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
mixin.didFinishWithImage = { image in
if let image = image {
if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
completedImpl(image)
}
}
mixin.didFinishWithDelete = {

View File

@ -132,7 +132,6 @@ final class ChatBotInfoItemNode: ListViewItemNode {
var updatedBackgroundImage: UIImage?
if currentTheme != item.presentationData.theme {
//let principalGraphics = PresentationResourcesChat.principalGraphics(item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
updatedBackgroundImage = PresentationResourcesChat.chatInfoItemBackgroundImage(item.presentationData.theme.theme, wallpaper: !item.presentationData.theme.wallpaper.isEmpty)
}
@ -181,9 +180,9 @@ final class ChatBotInfoItemNode: ListViewItemNode {
override func updateTrailingItemSpace(_ height: CGFloat, transition: ContainedViewLayoutTransition) {
if height.isLessThanOrEqualTo(0.0) {
transition.updateBounds(node: self.offsetContainer, bounds: CGRect(origin: CGPoint(), size: self.offsetContainer.bounds.size))
transition.updateFrame(node: self.offsetContainer, frame: CGRect(origin: CGPoint(), size: self.offsetContainer.bounds.size))
} else {
transition.updateBounds(node: self.offsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: floor(height) / 2.0), size: self.offsetContainer.bounds.size))
transition.updateFrame(node: self.offsetContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: -floorToScreenPixels(height / 2.0)), size: self.offsetContainer.bounds.size))
}
}
@ -199,12 +198,18 @@ final class ChatBotInfoItemNode: ListViewItemNode {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false)
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
let result = super.point(inside: point, with: event)
let extra = self.offsetContainer.frame.contains(point)
return result || extra
}
func updateTouchesAtPoint(_ point: CGPoint?) {
if let item = self.item {
var rects: [CGRect]?
if let point = point {
let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - self.offsetContainer.frame.minX - textNodeFrame.minX, y: point.y - self.offsetContainer.frame.minY - textNodeFrame.minY)) {
let possibleNames: [String] = [
TelegramTextAttributes.URL,
TelegramTextAttributes.PeerMention,
@ -228,7 +233,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
} else {
linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.theme.chat.bubble.incomingLinkHighlightColor)
self.linkHighlightingNode = linkHighlightingNode
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
self.offsetContainer.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
}
linkHighlightingNode.frame = self.textNode.frame
linkHighlightingNode.updateRects(rects)
@ -243,7 +248,7 @@ final class ChatBotInfoItemNode: ListViewItemNode {
func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture) -> ChatMessageBubbleContentTapAction {
let textNodeFrame = self.textNode.frame
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - self.offsetContainer.frame.minX - textNodeFrame.minX, y: point.y - self.offsetContainer.frame.minY - textNodeFrame.minY)) {
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
var concealed = true
if let attributeText = self.textNode.attributeSubstring(name: TelegramTextAttributes.URL, index: index) {

View File

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

View File

@ -226,7 +226,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
mediaAndFlags = (file, [])
}
} else if let image = mainMedia as? TelegramMediaImage {
if let type = webpage.type, ["photo", "video", "embed", "gif", "telegram_album"].contains(type) {
if let type = webpage.type, ["photo", "video", "embed", "gif", "document", "telegram_album"].contains(type) {
var flags = ChatMessageAttachedContentNodeMediaFlags()
if webpage.instantPage != nil, let largest = largestImageRepresentation(image.representations) {
if largest.dimensions.width >= 256.0 {

View File

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

View File

@ -100,7 +100,21 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
var inputActivities: (PeerId, [(Peer, PeerInputActivity)])? {
didSet {
if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty {
var inputActivitiesAllowed = true
if let titleContent = self.titleContent {
switch titleContent {
case let .peer(peerView, _):
if let peer = peerViewMainPeer(peerView) {
if peer.id == self.account.peerId {
inputActivitiesAllowed = false
}
}
default:
break
}
}
if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty, inputActivitiesAllowed {
self.typingNode.isHidden = false
self.infoNode.isHidden = true
var stringValue = ""

View File

@ -6,6 +6,7 @@ import LegacyComponents
enum CheckNodeStyle {
case plain
case overlay
case navigation
}
final class CheckNode: ASDisplayNode {
@ -18,6 +19,9 @@ final class CheckNode: ASDisplayNode {
private(set) var isChecked: Bool = false
private weak var target: AnyObject?
private var action: Selector?
init(strokeColor: UIColor, fillColor: UIColor, foregroundColor: UIColor, style: CheckNodeStyle) {
self.strokeColor = strokeColor
self.fillColor = fillColor
@ -31,19 +35,29 @@ final class CheckNode: ASDisplayNode {
super.didLoad()
let style: TGCheckButtonStyle
let checkSize: CGSize
switch self.checkStyle {
case .plain:
style = TGCheckButtonStyleDefault
checkSize = CGSize(width: 32.0, height: 32.0)
case .overlay:
style = TGCheckButtonStyleMedia
checkSize = CGSize(width: 32.0, height: 32.0)
case .navigation:
style = TGCheckButtonStyleGallery
checkSize = CGSize(width: 39.0, height: 39.0)
}
let checkView = TGCheckButtonView(style: style, pallete: TGCheckButtonPallete(defaultBackgroundColor: self.fillColor, accentBackgroundColor: self.fillColor, defaultBorderColor: self.strokeColor, mediaBorderColor: self.strokeColor, chatBorderColor: self.strokeColor, check: self.foregroundColor, blueColor: self.fillColor, barBackgroundColor: self.fillColor))!
checkView.setSelected(true, animated: false)
checkView.layoutSubviews()
checkView.setSelected(self.isChecked, animated: false)
if let target = self.target, let action = self.action {
checkView.addTarget(target, action: action, for: .touchUpInside)
}
self.checkView = checkView
self.view.addSubview(checkView)
checkView.frame = self.bounds
checkView.frame = CGRect(origin: CGPoint(), size: checkSize)
}
func setIsChecked(_ isChecked: Bool, animated: Bool) {
@ -52,4 +66,12 @@ final class CheckNode: ASDisplayNode {
self.checkView?.setSelected(isChecked, animated: animated)
}
}
func addTarget(target: AnyObject?, action: Selector) {
self.target = target
self.action = action
if self.isNodeLoaded {
self.checkView?.addTarget(target, action: action, for: .touchUpInside)
}
}
}

View File

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

View File

@ -791,7 +791,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
if let verificationIconNode = self.verificationIconNode {
var iconFrame = verificationIconNode.frame
iconFrame.origin.x = offset + titleFrame.maxX + 3.0
iconFrame.origin.x = titleFrame.maxX + 3.0
transition.updateFrame(node: verificationIconNode, frame: iconFrame)
}

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

View File

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

View File

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

View File

@ -171,15 +171,15 @@ private let bubble = PresentationThemeChatBubble(
incomingFileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5),
outgoingFileDurationColor: UIColor(rgb: 0xffffff, alpha: 0.5),
shareButtonFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)),
shareButtonStrokeColor: UIColor(rgb: 0x1f1f1f),
shareButtonStrokeColor: UIColor(rgb: 0xb2b2b2, alpha: 0.18),
shareButtonForegroundColor: UIColor(rgb: 0xb2b2b2), //!!!
mediaOverlayControlBackgroundColor: UIColor(white: 0.0, alpha: 0.6), //!!!
mediaOverlayControlForegroundColor: UIColor(white: 1.0, alpha: 1.0), //!!!
actionButtonsIncomingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)),
actionButtonsIncomingStrokeColor: UIColor(rgb: 0x1f1f1f),
actionButtonsIncomingStrokeColor: UIColor(rgb: 0xb2b2b2, alpha: 0.18),
actionButtonsIncomingTextColor: UIColor(rgb: 0xffffff),
actionButtonsOutgoingFillColor: PresentationThemeVariableColor(withWallpaper: UIColor(rgb: 0x000000, alpha: 0.5), withoutWallpaper: UIColor(rgb: 0x000000, alpha: 0.5)),
actionButtonsOutgoingStrokeColor: UIColor(rgb: 0x1f1f1f),
actionButtonsOutgoingStrokeColor: UIColor(rgb: 0xb2b2b2, alpha: 0.18),
actionButtonsOutgoingTextColor: UIColor(rgb: 0xffffff),
selectionControlBorderColor: .white,
selectionControlFillColor: accentColor,

View File

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

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: {
let _ = (account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
} |> deliverOnMainQueue).start(next: { peer in
let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
return (transaction.getPeer(peerId), currentSearchBotsConfiguration(transaction: transaction))
} |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
@ -1225,28 +1225,38 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
hasPhotos = true
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false)!
let completedImpl: (UIImage) -> Void = { image in
if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { _ in
let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
completedImpl(result)
}))
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
mixin.didFinishWithImage = { image in
if let image = image {
if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: peerId, photo: uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: resource)) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
completedImpl(image)
}
}
mixin.didFinishWithDelete = {
@ -1361,9 +1371,25 @@ public func groupInfoController(account: Account, peerId: PeerId) -> ViewControl
var options: [ContactListAdditionalOption] = []
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
var inviteByLinkImpl: (() -> Void)?
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
inviteByLinkImpl?()
}))
var canCreateInviteLink = false
if let group = groupPeer as? TelegramGroup {
if case .creator = group.role {
canCreateInviteLink = true
}
} else if let channel = groupPeer as? TelegramChannel {
if channel.hasAdminRights(.canInviteUsers) {
canCreateInviteLink = true
} else if case let .group(info) = channel.info, info.flags.contains(.everyMemberCanInviteMembers) {
canCreateInviteLink = true
}
}
if canCreateInviteLink {
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: generateTintedImage(image: UIImage(bundleImageName: "Contact List/LinkActionIcon"), color: presentationData.theme.list.itemAccentColor), action: {
inviteByLinkImpl?()
}))
}
let contactsController: ViewController
if peerId.namespace == Namespaces.Peer.CloudGroup {

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import Foundation
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SafariServices
@ -188,8 +189,13 @@ class InstantPageReferenceControllerNode: ViewControllerTracingNode, UIScrollVie
if self.contentNode == nil || self.contentNode?.frame.width != width {
self.contentNode?.removeFromSupernode()
var media: [MediaId: Media] = [:]
if case let .Loaded(content) = self.webPage.content, let instantPage = content.instantPage {
media = instantPage.media
}
let sideInset: CGFloat = 16.0
let (_, items, contentSize) = layoutTextItemWithString(self.item.attributedString, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset))
let (_, items, contentSize) = layoutTextItemWithString(self.item.attributedString, boundingWidth: width - sideInset * 2.0, offset: CGPoint(x: sideInset, y: sideInset), media: media, webpage: self.webPage)
let contentNode = InstantPageContentNode(account: self.account, strings: self.presentationData.strings, theme: self.theme, items: items, contentSize: CGSize(width: width, height: contentSize.height), inOverlayPanel: true, openMedia: { _ in }, longPressMedia: { _ in }, openPeer: { _ in }, openUrl: { _ in })
transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleAreaHeight), size: CGSize(width: width, height: contentSize.height)))
self.contentContainerNode.insertSubnode(contentNode, at: 0)

View File

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

View File

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

View File

@ -16,7 +16,7 @@ func presentLegacyAvatarPicker(holder: Atomic<NSObject?>, signup: Bool, theme: P
present(legacyController, nil)
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: false, hasViewButton: openCurrent != nil, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: signup)!
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: false, hasDeleteButton: false, hasViewButton: openCurrent != nil, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: signup)!
let _ = holder.swap(mixin)
mixin.didFinishWithImage = { image in
guard let image = image else {

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 thumbnailImage: Signal<UIImage, NoError>
let originalImage: Signal<UIImage, NoError>
let progress: Signal<Float, NoError>
init(result: ChatContextResult, dimensions: CGSize, thumbnailImage: Signal<UIImage, NoError>, originalImage: Signal<UIImage, NoError>) {
init(result: ChatContextResult) {
self.result = result
self.dimensions = CGSize()
self.thumbnailImage = .complete()
self.originalImage = .complete()
self.progress = .complete()
}
init(result: ChatContextResult, dimensions: CGSize, thumbnailImage: Signal<UIImage, NoError>, originalImage: Signal<UIImage, NoError>, progress: Signal<Float, NoError>) {
self.result = result
self.dimensions = dimensions
self.thumbnailImage = thumbnailImage
self.originalImage = originalImage
self.progress = progress
}
var originalSize: CGSize {
@ -45,6 +55,30 @@ class LegacyWebSearchItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem
})
}
func screenImageAndProgressSignal() -> SSignal {
return SSignal { subscriber in
let imageDisposable = self.originalImage.start(next: { image in
if !image.degraded() {
subscriber?.putNext(1.0)
}
subscriber?.putNext(image)
if !image.degraded() {
subscriber?.putCompletion()
}
})
let progressDisposable = (self.progress
|> deliverOnMainQueue).start(next: { next in
subscriber?.putNext(next)
})
return SBlockDisposable {
imageDisposable.dispose()
progressDisposable.dispose()
}
}
}
func screenImageSignal(_ position: TimeInterval) -> SSignal! {
return self.originalImageSignal(position)
}
@ -53,7 +87,9 @@ class LegacyWebSearchItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem
return SSignal(generator: { subscriber -> SDisposable? in
let disposable = self.originalImage.start(next: { image in
subscriber?.putNext(image)
subscriber?.putCompletion()
if !image.degraded() {
subscriber?.putCompletion()
}
})
return SBlockDisposable(block: {
@ -93,14 +129,26 @@ private class LegacyWebSearchGalleryItem: TGModernGalleryImageItem, TGModernGall
override func viewClass() -> AnyClass! {
return LegacyWebSearchGalleryItemView.self
}
override func isEqual(_ object: Any?) -> Bool {
if let item = object as? LegacyWebSearchGalleryItem {
return item.item.result.id == self.item.result.id
}
return false
}
}
private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGModernGalleryEditableItemView
{
private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGModernGalleryEditableItemView {
private let readyForTransition = SVariable()!
func setHiddenAsBeingEdited(_ hidden: Bool) {
self.imageView.isHidden = hidden
}
override func readyForTransitionIn() -> SSignal! {
return self.readyForTransition.signal()!.take(1)
}
override func setItem(_ item: TGModernGalleryItem!, synchronously: Bool) {
if let item = item as? LegacyWebSearchGalleryItem {
self._setItem(item)
@ -110,7 +158,7 @@ private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGMo
if let image = result as? UIImage {
return SSignal.single(image)
} else if result == nil, let mediaItem = item.editableMediaItem() as? LegacyWebSearchItem {
return mediaItem.originalImageSignal(0.0)
return mediaItem.screenImageAndProgressSignal()
} else {
return SSignal.complete()
}
@ -120,6 +168,7 @@ private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGMo
if let strongSelf = self, let image = next as? UIImage {
strongSelf.imageSize = image.size
strongSelf.reset()
strongSelf.readyForTransition.set(SSignal.single(true))
}
}))
@ -144,63 +193,90 @@ private class LegacyWebSearchGalleryItemView: TGModernGalleryImageItemView, TGMo
}
}
private func galleryItems(account: Account, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext, editingContext: TGMediaEditingContext) -> ([TGModernGalleryItem], TGModernGalleryItem?) {
func legacyWebSearchItem(account: Account, result: ChatContextResult) -> LegacyWebSearchItem? {
var thumbnailDimensions: CGSize?
var thumbnailResource: TelegramMediaResource?
var imageResource: TelegramMediaResource?
var imageDimensions = CGSize()
let thumbnailSignal: Signal<UIImage, NoError>
let originalSignal: Signal<UIImage, NoError>
switch result {
case let .externalReference(_, _, _, _, _, _, content, thumbnail, _):
if let content = content {
imageResource = content.resource
}
if let thumbnail = thumbnail {
thumbnailResource = thumbnail.resource
thumbnailDimensions = thumbnail.dimensions
}
if let dimensions = content?.dimensions {
imageDimensions = dimensions
}
case let .internalReference(_, _, _, _, _, image, _, _):
if let image = image {
if let largestRepresentation = largestImageRepresentation(image.representations) {
imageDimensions = largestRepresentation.dimensions
imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 1000.0, height: 800.0))?.resource
}
if let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 100.0)) {
thumbnailDimensions = thumbnailRepresentation.dimensions
thumbnailResource = thumbnailRepresentation.resource
}
}
}
if let imageResource = imageResource {
let progressSignal = account.postbox.mediaBox.resourceStatus(imageResource)
|> map { status -> Float in
switch status {
case .Local:
return 1.0
case .Remote:
return 0.0
case let .Fetching(_, progress):
return progress
}
}
var representations: [TelegramMediaImageRepresentation] = []
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource))
}
representations.append(TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource))
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, reference: nil, partialReference: nil)
thumbnailSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: false)
|> mapToSignal { (thumbnailData, _, _) -> Signal<UIImage, NoError> in
if let data = thumbnailData, let image = UIImage(data: data) {
return .single(image)
} else {
return .complete()
}
}
originalSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: true)
|> mapToSignal { (thumbnailData, fullSizeData, fullSizeComplete) -> Signal<UIImage, NoError> in
if fullSizeComplete, let data = fullSizeData, let image = UIImage(data: data) {
return .single(image)
} else if let data = thumbnailData, let image = UIImage(data: data) {
image.setDegraded(true)
return .single(image)
} else {
return .complete()
}
}
return LegacyWebSearchItem(result: result, dimensions: imageDimensions, thumbnailImage: thumbnailSignal, originalImage: originalSignal, progress: progressSignal)
} else {
return nil
}
}
private func galleryItems(account: Account, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext) -> ([TGModernGalleryItem], TGModernGalleryItem?) {
var focusItem: TGModernGalleryItem?
var galleryItems: [TGModernGalleryItem] = []
for result in results {
var thumbnailDimensions: CGSize?
var thumbnailResource: TelegramMediaResource?
var imageResource: TelegramMediaResource?
var imageDimensions = CGSize()
let thumbnailSignal: Signal<UIImage, NoError>
let originalSignal: Signal<UIImage, NoError>
switch result {
case let .externalReference(_, _, _, _, _, _, content, thumbnail, _):
if let content = content {
imageResource = content.resource
}
if let thumbnail = thumbnail {
thumbnailResource = thumbnail.resource
thumbnailDimensions = thumbnail.dimensions
}
if let dimensions = content?.dimensions {
imageDimensions = dimensions
}
// if let imageResource = imageResource {
// updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
// }
case let .internalReference(_, _, _, _, _, image, _, _):
if let image = image {
if let largestRepresentation = largestImageRepresentation(image.representations) {
imageDimensions = largestRepresentation.dimensions
imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 1000.0, height: 800.0))?.resource
}
if let thumbnailRepresentation = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 100.0)) {
thumbnailDimensions = thumbnailRepresentation.dimensions
thumbnailResource = thumbnailRepresentation.resource
}
}
}
if let imageResource = imageResource {
var representations: [TelegramMediaImageRepresentation] = []
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource))
}
representations.append(TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource))
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, reference: nil, partialReference: nil)
thumbnailSignal = chatMessagePhotoDatas(postbox: account.postbox, photoReference: .standalone(media: tmpImage), autoFetchFullSize: true)
|> mapToSignal { (thumbnailData, fullSizeData, fullSizeComplete) -> Signal<UIImage, NoError> in
if let data = fullSizeData, let image = UIImage(data: data) {
return .single(image)
} else {
return .complete()
}
}
originalSignal = thumbnailSignal
let item = LegacyWebSearchItem(result: result, dimensions: imageDimensions, thumbnailImage: thumbnailSignal, originalImage: originalSignal)
if let item = legacyWebSearchItem(account: account, result: result) {
let galleryItem = LegacyWebSearchGalleryItem(item: item)
galleryItem.selectionContext = selectionContext
galleryItem.editingContext = editingContext
@ -213,11 +289,12 @@ private func galleryItems(account: Account, results: [ChatContextResult], curren
return (galleryItems, focusItem)
}
func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: PresentationTheme, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext, editingContext: TGMediaEditingContext, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (ChatContextResult) -> Void, present: (ViewController, Any?) -> Void) {
func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: PresentationTheme, results: [ChatContextResult], current: ChatContextResult, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, updateHiddenMedia: @escaping (String?) -> Void, initialLayout: ContainerViewLayout?, transitionHostView: @escaping () -> UIView?, transitionView: @escaping (ChatContextResult) -> UIView?, completed: @escaping (ChatContextResult) -> Void, present: (ViewController, Any?) -> Void) {
let legacyController = LegacyController(presentation: .custom, theme: theme, initialLayout: initialLayout)
legacyController.statusBar.statusBarStyle = .Ignore
let controller = TGModernGalleryController(context: legacyController.context)!
controller.asyncTransitionIn = true
legacyController.bind(controller: controller)
let (items, focusItem) = galleryItems(account: account, results: results, current: current, selectionContext: selectionContext, editingContext: editingContext)
@ -228,9 +305,6 @@ func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: Present
}
controller.model = model
model.controller = controller
model.externalSelectionCount = {
return 0
}
model.useGalleryImageAsEditableItemImage = true
model.storeOriginalImageForItem = { item, image in
editingContext.setOriginalImage(image, for: item, synchronous: false)
@ -240,17 +314,22 @@ func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: Present
editingContext.setAdjustments(adjustments, for: item)
}
editingContext.setTemporaryRep(representation, for: item)
//if let selectionContext = selectionContext, {
// selectionContex
//}
if let selectionContext = selectionContext, adjustments != nil, let item = item as? TGMediaSelectableItem {
selectionContext.setItem(item, selected: true)
}
}
model.didFinishEditingItem = { item, adjustments, result, thumbnail in
editingContext.setImage(result, thumbnailImage: thumbnail, for: item, synchronous: true)
}
model.saveItemCaption = { item, caption, entities in
editingContext.setCaption(caption, entities: entities, for: item)
if let selectionContext = selectionContext, let caption = caption, caption.count > 0, let item = item as? TGMediaSelectableItem {
selectionContext.setItem(item, selected: true)
}
}
if let selectionContext = selectionContext {
model.interfaceView.updateSelectionInterface(selectionContext.count(), counterVisible: selectionContext.count() > 0, animated: false)
}
//[model.interfaceView updateSelectionInterface:[self totalSelectionCount] counterVisible:([self totalSelectionCount] > 0) animated:false];
model.interfaceView.donePressed = { item in
if let item = item as? LegacyWebSearchGalleryItem {
controller.dismissWhenReady(animated: false)
@ -284,46 +363,4 @@ func presentLegacyWebSearchGallery(account: Account, peer: Peer?, theme: Present
legacyController?.dismiss()
}
present(legacyController, nil)
// if (item.selectionContext != nil && adjustments != nil && [editableItem conformsToProtocol:@protocol(TGMediaSelectableItem)])
// [item.selectionContext setItem:(id<TGMediaSelectableItem>)editableItem selected:true];
// };
// model.interfaceView.donePressed = ^(id<TGWebSearchResultsGalleryItem> item)
// {
// __strong TGWebSearchController *strongSelf = weakSelf;
// if (strongSelf == nil)
// return;
//
// NSMutableArray *selectedItems = [strongSelf selectedItems];
//
// if (selectedItems.count == 0)
// [selectedItems addObject:[item webSearchResult]];
//
// strongSelf->_selectedItems = selectedItems;
// [strongSelf complete];
// };
// _galleryModel = model;
// modernGallery.model = model;
//
// __weak TGModernGalleryController *weakGallery = modernGallery;
// modernGallery.itemFocused = ^(id<TGWebSearchResultsGalleryItem> item)
// {
// __strong TGWebSearchController *strongSelf = weakSelf;
// __strong TGModernGalleryController *strongGallery = weakGallery;
// if (strongSelf != nil)
// {
// if (strongGallery.previewMode)
// return;
//
// id<TGWebSearchListItem> listItem = [strongSelf listItemForSearchResult:[item webSearchResult]];
// strongSelf->_hiddenItem = listItem;
// [strongSelf updateHiddenItemAnimated:false];
// }
// };
//
}

View File

@ -71,77 +71,84 @@ public enum MessageContentKind: Equatable {
public func messageContentKind(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> MessageContentKind {
for media in message.media {
switch media {
case let expiredMedia as TelegramMediaExpiredContent:
switch expiredMedia.data {
case .image:
return .expiredImage
case .file:
return .expiredVideo
}
case _ as TelegramMediaImage:
return .image
case let file as TelegramMediaFile:
var fileName: String = ""
for attribute in file.attributes {
switch attribute {
case let .Sticker(text, _, _):
return .sticker(text)
case let .FileName(name):
fileName = name
case let .Audio(isVoice, _, title, performer, _):
if isVoice {
return .audioMessage
} else {
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
return .file(title + "" + performer)
} else if let title = title, !title.isEmpty {
return .file(title)
} else if let performer = performer, !performer.isEmpty {
return .file(performer)
}
}
case let .Video(_, _, flags):
if file.isAnimated {
return .animation
} else {
if flags.contains(.instantRoundVideo) {
return .videoMessage
} else {
return .video
}
}
default:
break
}
}
return .file(fileName)
case _ as TelegramMediaContact:
return .contact
case let game as TelegramMediaGame:
return .game(game.title)
case let location as TelegramMediaMap:
if location.liveBroadcastingTimeout != nil {
return .liveLocation
} else {
return .location
}
case _ as TelegramMediaAction:
return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) ?? "")
case let poll as TelegramMediaPoll:
return .text(poll.text)
default:
break
if let kind = mediaContentKind(media, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId) {
return kind
}
}
return .text(message.text)
}
func descriptionStringForMessage(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) {
if !message.text.isEmpty {
return (message.text, false)
public func mediaContentKind(_ media: Media, message: Message? = nil, strings: PresentationStrings? = nil, nameDisplayOrder: PresentationPersonNameOrder? = nil, accountPeerId: PeerId? = nil) -> MessageContentKind? {
switch media {
case let expiredMedia as TelegramMediaExpiredContent:
switch expiredMedia.data {
case .image:
return .expiredImage
case .file:
return .expiredVideo
}
case _ as TelegramMediaImage:
return .image
case let file as TelegramMediaFile:
var fileName: String = ""
for attribute in file.attributes {
switch attribute {
case let .Sticker(text, _, _):
return .sticker(text)
case let .FileName(name):
fileName = name
case let .Audio(isVoice, _, title, performer, _):
if isVoice {
return .audioMessage
} else {
if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty {
return .file(title + "" + performer)
} else if let title = title, !title.isEmpty {
return .file(title)
} else if let performer = performer, !performer.isEmpty {
return .file(performer)
}
}
case let .Video(_, _, flags):
if file.isAnimated {
return .animation
} else {
if flags.contains(.instantRoundVideo) {
return .videoMessage
} else {
return .video
}
}
default:
break
}
}
return .file(fileName)
case _ as TelegramMediaContact:
return .contact
case let game as TelegramMediaGame:
return .game(game.title)
case let location as TelegramMediaMap:
if location.liveBroadcastingTimeout != nil {
return .liveLocation
} else {
return .location
}
case _ as TelegramMediaAction:
if let message = message, let strings = strings, let nameDisplayOrder = nameDisplayOrder, let accountPeerId = accountPeerId {
return .text(plainServiceMessageString(strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) ?? "")
} else {
return nil
}
case let poll as TelegramMediaPoll:
return .text(poll.text)
default:
return nil
}
switch messageContentKind(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId) {
}
func stringForMediaKind(_ kind: MessageContentKind, strings: PresentationStrings) -> (String, Bool) {
switch kind {
case let .text(text):
return (text, false)
case .image:
@ -180,3 +187,10 @@ func descriptionStringForMessage(_ message: Message, strings: PresentationString
return (strings.Message_VideoExpired, true)
}
}
func descriptionStringForMessage(_ message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) {
if !message.text.isEmpty {
return (message.text, false)
}
return stringForMediaKind(messageContentKind(message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId), strings: strings)
}

View File

@ -32,7 +32,6 @@ public class NotificationExceptionsController: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
self.editItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.editPressed))
self.doneItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed))
@ -72,7 +71,7 @@ public class NotificationExceptionsController: ViewController {
private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.title = self.presentationData.strings.Settings_AppLanguage
self.title = self.presentationData.strings.Notifications_ExceptionsTitle
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
self.controllerNode.updatePresentationData(self.presentationData)

View File

@ -60,6 +60,20 @@ public final class PeerSelectionController: ViewController {
strongSelf.peerSelectionNode.scrollToTop()
}
}
self.presentationDataDisposable = (account.telegramApplicationContext.presentationData
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
if let strongSelf = self {
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
strongSelf.presentationData = presentationData
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
strongSelf.updateThemeAndStrings()
}
}
})
}
required public init(coder aDecoder: NSCoder) {
@ -68,6 +82,14 @@ public final class PeerSelectionController: ViewController {
deinit {
self.openMessageFromSearchDisposable.dispose()
self.presentationDataDisposable?.dispose()
}
private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBar.style.style
self.navigationBar?.updatePresentationData(NavigationBarPresentationData(presentationData: self.presentationData))
self.title = self.presentationData.strings.Conversation_ForwardTitle
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
}
override public func loadDisplayNode() {

View File

@ -120,6 +120,10 @@ final class PeerSelectionControllerNode: ASDisplayNode {
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.searchDisplayController?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings)
self.chatListNode.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)
self.toolbarBackgroundNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor
self.toolbarSeparatorNode?.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
self.segmentedControl?.tintColor = self.presentationData.theme.rootController.navigationBar.accentTextColor
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
@ -140,10 +144,6 @@ final class PeerSelectionControllerNode: ASDisplayNode {
transition.updateFrame(view: segmentedControl, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - controlSize.width) / 2.0), y: layout.size.height - toolbarHeight + floor((44.0 - controlSize.height) / 2.0)), size: controlSize))
}
var insets = layout.insets(options: [.input])
insets.top += max(navigationBarHeight, layout.insets(options: [.statusBar]).top)
insets.bottom = max(insets.bottom, cleanInsets.bottom)

File diff suppressed because it is too large Load Diff

View File

@ -443,13 +443,13 @@ class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
func transitionOut(to node: SearchBarPlaceholderNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
let targetTextBackgroundFrame = node.convert(node.backgroundNode.frame, to: self)
let duration: Double = 0.5
let duration: Double = transition.isAnimated ? 0.5 : 0.0
let timingFunction = kCAMediaTimingFunctionSpring
node.isHidden = true
self.clearButton.isHidden = true
self.textField.text = ""
var backgroundCompleted = false
var separatorCompleted = false
var textBackgroundCompleted = false

View File

@ -616,9 +616,9 @@ public func settingsController(account: Account, accountManager: AccountManager)
})
changeProfilePhotoImpl = {
let _ = (account.postbox.transaction { transaction -> Peer? in
return transaction.getPeer(account.peerId)
} |> deliverOnMainQueue).start(next: { peer in
let _ = (account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
return (transaction.getPeer(account.peerId), currentSearchBotsConfiguration(transaction: transaction))
} |> deliverOnMainQueue).start(next: { peer, searchBotsConfiguration in
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
@ -638,28 +638,38 @@ public func settingsController(account: Account, accountManager: AccountManager)
hasPhotos = true
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasDeleteButton: hasPhotos, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false)!
let completedImpl: (UIImage) -> Void = { image in
if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: resource) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { _ in
let controller = WebSearchController(account: account, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(completion: { result in
completedImpl(result)
}))
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
mixin.didFinishWithImage = { image in
if let image = image {
if let data = UIImageJPEGRepresentation(image, 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: CGSize(width: 640.0, height: 640.0), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((updateAccountPhoto(account: account, resource: resource) |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
completedImpl(image)
}
}
mixin.didFinishWithDelete = {

View File

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

View File

@ -25,6 +25,7 @@ private final class UserInfoControllerArguments {
let displayCopyContextMenu: (UserInfoEntryTag, String) -> Void
let call: () -> Void
let openCallMenu: (String) -> Void
let aboutLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void
let displayAboutContextMenu: (String) -> Void
let openEncryptionKey: (SecretChatKeyFingerprint) -> Void
let addBotToGroup: () -> Void
@ -34,7 +35,7 @@ private final class UserInfoControllerArguments {
let botPrivacy: () -> Void
let report: () -> Void
init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, tapAvatarAction: @escaping () -> Void, openChat: @escaping () -> Void, addContact: @escaping () -> Void, shareContact: @escaping () -> Void, shareMyContact: @escaping () -> Void, startSecretChat: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, changeNotificationSoundSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openGroupsInCommon: @escaping () -> Void, updatePeerBlocked: @escaping (Bool) -> Void, deleteContact: @escaping () -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayCopyContextMenu: @escaping (UserInfoEntryTag, String) -> Void, call: @escaping () -> Void, openCallMenu: @escaping (String) -> Void, displayAboutContextMenu: @escaping (String) -> Void, openEncryptionKey: @escaping (SecretChatKeyFingerprint) -> Void, addBotToGroup: @escaping () -> Void, shareBot: @escaping () -> Void, botSettings: @escaping () -> Void, botHelp: @escaping () -> Void, botPrivacy: @escaping () -> Void, report: @escaping () -> Void) {
init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, tapAvatarAction: @escaping () -> Void, openChat: @escaping () -> Void, addContact: @escaping () -> Void, shareContact: @escaping () -> Void, shareMyContact: @escaping () -> Void, startSecretChat: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, changeNotificationSoundSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openGroupsInCommon: @escaping () -> Void, updatePeerBlocked: @escaping (Bool) -> Void, deleteContact: @escaping () -> Void, displayUsernameContextMenu: @escaping (String) -> Void, displayCopyContextMenu: @escaping (UserInfoEntryTag, String) -> Void, call: @escaping () -> Void, openCallMenu: @escaping (String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, displayAboutContextMenu: @escaping (String) -> Void, openEncryptionKey: @escaping (SecretChatKeyFingerprint) -> Void, addBotToGroup: @escaping () -> Void, shareBot: @escaping () -> Void, botSettings: @escaping () -> Void, botHelp: @escaping () -> Void, botPrivacy: @escaping () -> Void, report: @escaping () -> Void) {
self.account = account
self.avatarAndNameInfoContext = avatarAndNameInfoContext
self.updateEditingName = updateEditingName
@ -55,6 +56,7 @@ private final class UserInfoControllerArguments {
self.displayCopyContextMenu = displayCopyContextMenu
self.call = call
self.openCallMenu = openCallMenu
self.aboutLinkAction = aboutLinkAction
self.displayAboutContextMenu = displayAboutContextMenu
self.openEncryptionKey = openEncryptionKey
self.addBotToGroup = addBotToGroup
@ -93,7 +95,7 @@ private func areMessagesEqual(_ lhsMessage: Message, _ rhsMessage: Message) -> B
private enum UserInfoEntry: ItemListNodeEntry {
case info(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, peer: Peer?, presence: PeerPresence?, cachedData: CachedPeerData?, state: ItemListAvatarAndNameInfoItemState, displayCall: Bool)
case calls(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, messages: [Message])
case about(PresentationTheme, String, String)
case about(PresentationTheme, Peer, String, String)
case phoneNumber(PresentationTheme, Int, String, String, Bool)
case userName(PresentationTheme, String, String)
case sendMessage(PresentationTheme, String)
@ -192,8 +194,8 @@ private enum UserInfoEntry: ItemListNodeEntry {
} else {
return false
}
case let .about(lhsTheme, lhsText, lhsValue):
if case let .about(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
case let .about(lhsTheme, lhsPeer, lhsText, lhsValue):
if case let .about(rhsTheme, rhsPeer, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsPeer.isEqual(rhsPeer), lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
@ -380,9 +382,15 @@ private enum UserInfoEntry: ItemListNodeEntry {
} : nil)
case let .calls(theme, strings, dateTimeFormat, messages):
return ItemListCallListItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, messages: messages, sectionId: self.section, style: .plain)
case let .about(theme, text, value):
return ItemListTextWithLabelItem(theme: theme, label: text, text: value, enabledEntitiyTypes: [], multiline: true, sectionId: self.section, action: {
case let .about(theme, peer, text, value):
var enabledEntitiyTypes: EnabledEntityTypes = []
if let peer = peer as? TelegramUser, let _ = peer.botInfo {
enabledEntitiyTypes = [.url, .mention, .hashtag]
}
return ItemListTextWithLabelItem(theme: theme, label: text, text: value, enabledEntitiyTypes: enabledEntitiyTypes, multiline: true, sectionId: self.section, action: nil, longTapAction: {
arguments.displayAboutContextMenu(value)
}, linkItemAction: { action, itemLink in
arguments.aboutLinkAction(action, itemLink)
}, tag: UserInfoEntryTag.about)
case let .phoneNumber(theme, _, label, value, isMain):
return ItemListTextWithLabelItem(theme: theme, label: label, text: value, textColor: isMain ? .highlighted : .accent, enabledEntitiyTypes: [], multiline: false, sectionId: self.section, action: {
@ -618,7 +626,7 @@ private func userInfoEntries(account: Account, presentationData: PresentationDat
} else {
title = presentationData.strings.Profile_About
}
entries.append(UserInfoEntry.about(presentationData.theme, title, about))
entries.append(UserInfoEntry.about(presentationData.theme, peer, title, about))
}
if !isEditing {
@ -764,10 +772,14 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC
let createSecretChatDisposable = MetaDisposable()
actionsDisposable.add(createSecretChatDisposable)
let navigateDisposable = MetaDisposable()
actionsDisposable.add(navigateDisposable)
var avatarGalleryTransitionArguments: ((AvatarGalleryEntry) -> GalleryTransitionArguments?)?
let avatarAndNameInfoContext = ItemListAvatarAndNameInfoItemContext()
var updateHiddenAvatarImpl: (() -> Void)?
var aboutLinkActionImpl: ((TextLinkItemActionType, TextLinkItem) -> Void)?
var displayAboutContextMenuImpl: ((String) -> Void)?
var displayCopyContextMenuImpl: ((UserInfoEntryTag, String) -> Void)?
@ -915,7 +927,7 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC
}
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
if let user = peer as? TelegramUser, user.botInfo != nil {
if let peer = peer as? TelegramUser, let _ = peer.botInfo {
updatePeerBlockedDisposable.set(requestUpdatePeerIsBlocked(account: account, peerId: peer.id, isBlocked: value).start())
if !value {
let _ = enqueueMessages(account: account, peerId: peer.id, messages: [.message(text: "/start", attributes: [], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil)]).start()
@ -989,6 +1001,8 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC
account.telegramApplicationContext.applicationBindings.openUrl("tel:\(formatPhoneNumber(number).replacingOccurrences(of: " ", with: ""))")
}
})
}, aboutLinkAction: { action, itemLink in
aboutLinkActionImpl?(action, itemLink)
}, displayAboutContextMenu: { text in
displayAboutContextMenuImpl?(text)
}, openEncryptionKey: { fingerprint in
@ -1311,6 +1325,11 @@ public func userInfoController(account: Account, peerId: PeerId, mode: UserInfoC
}
}
}
aboutLinkActionImpl = { [weak controller] action, itemLink in
if let controller = controller {
handlePeerInfoAboutTextAction(account: account, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
}
}
displayAboutContextMenuImpl = { [weak controller] text in
if let strongController = controller {
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }

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 {
let openResult: (ChatContextResult) -> Void
let setSearchQuery: (String) -> Void
let deleteRecentQuery: (String) -> Void
let toggleSelection: ([String], Bool) -> Void
let sendSelected: (ChatContextResultCollection, ChatContextResult?) -> Void
var selectionState: WebSearchSelectionState?
let toggleSelection: (ChatContextResult, Bool) -> Void
let sendSelected: (ChatContextResult?) -> Void
let avatarCompleted: (UIImage) -> Void
var selectionState: TGMediaSelectionContext?
let editingState: TGMediaEditingContext
var hiddenMediaId: String?
let editingContext: TGMediaEditingContext
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping ([String], Bool) -> Void, sendSelected: @escaping (ChatContextResultCollection, ChatContextResult?) -> Void, editingContext: TGMediaEditingContext) {
init(openResult: @escaping (ChatContextResult) -> Void, setSearchQuery: @escaping (String) -> Void, deleteRecentQuery: @escaping (String) -> Void, toggleSelection: @escaping (ChatContextResult, Bool) -> Void, sendSelected: @escaping (ChatContextResult?) -> Void, avatarCompleted: @escaping (UIImage) -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
self.openResult = openResult
self.setSearchQuery = setSearchQuery
self.deleteRecentQuery = deleteRecentQuery
self.toggleSelection = toggleSelection
self.sendSelected = sendSelected
self.editingContext = editingContext
self.avatarCompleted = avatarCompleted
self.selectionState = selectionState
self.editingState = editingState
}
}
private func selectionChangedSignal(selectionState: TGMediaSelectionContext) -> Signal<Void, NoError> {
return Signal { subscriber in
let disposable = selectionState.selectionChangedSignal()?.start(next: { next in
subscriber.putNext(Void())
}, completed: {})
return ActionDisposable {
disposable?.dispose()
}
}
}
@ -50,7 +83,8 @@ final class WebSearchController: ViewController {
private var validLayout: ContainerViewLayout?
private let account: Account
private let chatLocation: ChatLocation
private let mode: WebSearchControllerMode
private let peer: Peer?
private let configuration: SearchBotsConfiguration
private var controllerNode: WebSearchControllerNode {
@ -70,17 +104,19 @@ final class WebSearchController: ViewController {
private var disposable: Disposable?
private let resultsDisposable = MetaDisposable()
private var selectionDisposable: Disposable?
private var navigationContentNode: WebSearchNavigationContentNode?
init(account: Account, chatLocation: ChatLocation, configuration: SearchBotsConfiguration, sendSelected: @escaping ([String], ChatContextResultCollection, TGMediaEditingContext) -> Void) {
init(account: Account, peer: Peer?, configuration: SearchBotsConfiguration, mode: WebSearchControllerMode) {
self.account = account
self.chatLocation = chatLocation
self.mode = mode
self.peer = peer
self.configuration = configuration
let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 }
self.interfaceState = WebSearchInterfaceState(presentationData: presentationData)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: NavigationBarTheme(rootControllerTheme: presentationData.theme).withUpdatedSeparatorColor(presentationData.theme.rootController.navigationBar.backgroundColor), strings: NavigationBarStrings(presentationStrings: presentationData.strings)))
self.statusBar.statusBarStyle = presentationData.theme.rootController.statusBar.style.style
@ -101,8 +137,8 @@ final class WebSearchController: ViewController {
}
strongSelf.updateInterfaceState { current -> WebSearchInterfaceState in
var updated = current
if current.state?.mode != settings.mode {
updated = updated.withUpdatedMode(settings.mode)
if case .media = mode, current.state?.scope != settings.scope {
updated = updated.withUpdatedScope(settings.scope)
}
if current.presentationData !== presentationData {
updated = updated.withUpdatedPresentationData(presentationData)
@ -121,12 +157,19 @@ final class WebSearchController: ViewController {
}
self.navigationBar?.setContentNode(navigationContentNode, animated: false)
let editingContext = TGMediaEditingContext()
let selectionState: TGMediaSelectionContext?
switch self.mode {
case .media:
selectionState = TGMediaSelectionContext()
case .avatar:
selectionState = nil
}
let editingState = TGMediaEditingContext()
self.controllerInteraction = WebSearchControllerInteraction(openResult: { [weak self] result in
if let strongSelf = self {
strongSelf.controllerNode.openResult(currentResult: result, present: { [weak self] viewController, arguments in
if let strongSelf = self {
strongSelf.present(viewController, in: .window(.root), with: arguments)
strongSelf.present(viewController, in: .window(.root), with: arguments, blockInteraction: true)
}
})
}
@ -140,19 +183,35 @@ final class WebSearchController: ViewController {
if let strongSelf = self {
_ = removeRecentWebSearchQuery(postbox: strongSelf.account.postbox, string: query).start()
}
}, toggleSelection: { [weak self] ids, value in
}, toggleSelection: { [weak self] result, value in
if let strongSelf = self {
strongSelf.updateInterfaceState { $0.withToggledSelectedMessages(ids, value: value) }
let item = LegacyWebSearchItem(result: result)
strongSelf.controllerInteraction?.selectionState?.setItem(item, selected: value)
}
}, sendSelected: { [weak self] collection, current in
if let strongSelf = self, let state = strongSelf.interfaceState.state {
var selectedIds = state.selectionState.selectedIds
}, sendSelected: { current in
if let selectionState = selectionState {
if let current = current {
selectedIds.insert(current.id)
let currentItem = LegacyWebSearchItem(result: current)
selectionState.setItem(currentItem, selected: true)
}
if case let .media(sendSelected) = mode {
sendSelected(selectionState, editingState)
}
sendSelected(Array(selectedIds), collection, editingContext)
}
}, editingContext: editingContext)
}, avatarCompleted: { result in
if case let .avatar(avatarCompleted) = mode {
avatarCompleted(result)
}
}, selectionState: selectionState, editingState: editingState)
if let selectionState = selectionState {
self.selectionDisposable = (selectionChangedSignal(selectionState: selectionState)
|> deliverOnMainQueue).start(next: { [weak self] _ in
if let strongSelf = self {
strongSelf.controllerNode.updateSelectionState(animated: true)
}
})
}
}
required public init(coder aDecoder: NSCoder) {
@ -161,6 +220,8 @@ final class WebSearchController: ViewController {
deinit {
self.disposable?.dispose()
self.resultsDisposable.dispose()
self.selectionDisposable?.dispose()
}
public override func viewDidAppear(_ animated: Bool) {
@ -181,7 +242,7 @@ final class WebSearchController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = WebSearchControllerNode(account: self.account, theme: self.interfaceState.presentationData.theme, strings: interfaceState.presentationData.strings, controllerInteraction: self.controllerInteraction!)
self.displayNode = WebSearchControllerNode(account: self.account, theme: self.interfaceState.presentationData.theme, strings: interfaceState.presentationData.strings, controllerInteraction: self.controllerInteraction!, peer: self.peer, mode: self.mode.mode)
self.controllerNode.requestUpdateInterfaceState = { [weak self] animated, f in
if let strongSelf = self {
strongSelf.updateInterfaceState(f)
@ -212,8 +273,6 @@ final class WebSearchController: ViewController {
self.interfaceState = updatedInterfaceState
self.interfaceStatePromise.set(updatedInterfaceState)
self.controllerInteraction?.selectionState = updatedInterfaceState.state?.selectionState
if self.isNodeLoaded {
if previousTheme !== updatedInterfaceState.presentationData.theme || previousStrings !== updatedInterfaceState.presentationData.strings {
self.controllerNode.updatePresentationData(theme: updatedInterfaceState.presentationData.theme, strings: updatedInterfaceState.presentationData.strings)
@ -229,29 +288,26 @@ final class WebSearchController: ViewController {
let _ = addRecentWebSearchQuery(postbox: self.account.postbox, string: query).start()
}
let mode = self.interfaceStatePromise.get()
|> map { state -> WebSearchMode? in
return state.state?.mode
let scope: Signal<WebSearchScope?, NoError>
switch self.mode {
case .media:
scope = self.interfaceStatePromise.get()
|> map { state -> WebSearchScope? in
return state.state?.scope
}
|> distinctUntilChanged
case .avatar:
scope = .single(.images)
}
|> distinctUntilChanged
self.updateInterfaceState { $0.withUpdatedQuery(query) }
var results = mode
|> mapToSignal { mode -> (Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>) in
if let mode = mode {
return self.signalForQuery(query, mode: mode)
|> deliverOnMainQueue
|> beforeStarted { [weak self] in
if let strongSelf = self {
strongSelf.navigationContentNode?.setActivity(true)
}
}
|> afterCompleted { [weak self] in
if let strongSelf = self {
strongSelf.navigationContentNode?.setActivity(false)
}
}
let scopes: [WebSearchScope: Promise<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?>] = [.images: Promise(initializeOnFirstAccess: self.signalForQuery(query, scope: .images)), .gifs: Promise(initializeOnFirstAccess: self.signalForQuery(query, scope: .gifs))]
var results = scope
|> mapToSignal { scope -> (Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>) in
if let scope = scope, let scopeResults = scopes[scope] {
return scopeResults.get()
} else {
return .complete()
}
@ -276,29 +332,16 @@ final class WebSearchController: ViewController {
}))
}
private func signalForQuery(_ query: String, mode: WebSearchMode) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> {
private func signalForQuery(_ query: String, scope: WebSearchScope) -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> {
var delayRequest = true
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .complete()
// if let previousQuery = previousQuery {
// switch previousQuery {
// case let .contextRequest(currentAddressName, currentContextQuery) where currentAddressName == addressName:
// if query.isEmpty && !currentContextQuery.isEmpty {
// delayRequest = false
// }
// default:
// delayRequest = false
// signal = .single({ _ in return .contextRequestResult(nil, nil) })
// }
// } else {
signal = .single({ _ in return .contextRequestResult(nil, nil) })
// }
var signal: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> = .single({ _ in return .contextRequestResult(nil, nil) })
guard case let .peer(peerId) = self.chatLocation else {
guard let peerId = self.peer?.id else {
return .single({ _ in return .contextRequestResult(nil, nil) })
}
let botName: String?
switch mode {
switch scope {
case .images:
botName = self.configuration.imageBotUsername
case .gifs:
@ -354,7 +397,18 @@ final class WebSearchController: ViewController {
return .single({ _ in return nil })
}
}
return signal |> then(contextBot)
return (signal |> then(contextBot))
|> deliverOnMainQueue
|> beforeStarted { [weak self] in
if let strongSelf = self {
strongSelf.navigationContentNode?.setActivity(true)
}
}
|> afterCompleted { [weak self] in
if let strongSelf = self {
strongSelf.navigationContentNode?.setActivity(false)
}
}
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {

View File

@ -113,8 +113,10 @@ private func preparedWebSearchRecentTransition(from fromEntries: [WebSearchRecen
class WebSearchControllerNode: ASDisplayNode {
private let account: Account
private let peer: Peer?
private var theme: PresentationTheme
private var strings: PresentationStrings
private let mode: WebSearchMode
private let controllerInteraction: WebSearchControllerInteraction
private var webSearchInterfaceState: WebSearchInterfaceState
@ -145,8 +147,6 @@ class WebSearchControllerNode: ASDisplayNode {
private var hasMore = false
private var isLoadingMore = false
private let selectionContext = TGMediaSelectionContext()
private let hiddenMediaId = Promise<String?>(nil)
private var hiddenMediaDisposable: Disposable?
@ -163,11 +163,13 @@ class WebSearchControllerNode: ASDisplayNode {
var cancel: (() -> Void)?
var dismissInput: (() -> Void)?
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: WebSearchControllerInteraction) {
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: WebSearchControllerInteraction, peer: Peer?, mode: WebSearchMode) {
self.account = account
self.theme = theme
self.strings = strings
self.controllerInteraction = controllerInteraction
self.peer = peer
self.mode = mode
self.webSearchInterfaceState = WebSearchInterfaceState(presentationData: account.telegramApplicationContext.currentPresentationData.with { $0 })
self.webSearchInterfaceStatePromise = ValuePromise(self.webSearchInterfaceState, ignoreRepeated: true)
@ -204,7 +206,9 @@ class WebSearchControllerNode: ASDisplayNode {
self.addSubnode(self.recentQueriesNode)
self.addSubnode(self.segmentedBackgroundNode)
self.addSubnode(self.segmentedSeparatorNode)
self.view.addSubview(self.segmentedControl)
if case .media = mode {
self.view.addSubview(self.segmentedControl)
}
self.addSubnode(self.toolbarBackgroundNode)
self.addSubnode(self.toolbarSeparatorNode)
self.addSubnode(self.cancelButton)
@ -247,7 +251,7 @@ class WebSearchControllerNode: ASDisplayNode {
self.gridNode.visibleItemsUpdated = { [weak self] visibleItems in
if let strongSelf = self, let bottom = visibleItems.bottom, let entries = strongSelf.currentEntries {
if bottom.0 <= entries.count {
strongSelf.loadMore()
//strongSelf.loadMore()
}
}
}
@ -291,7 +295,12 @@ class WebSearchControllerNode: ASDisplayNode {
func applyPresentationData(themeUpdated: Bool = true) {
self.cancelButton.setTitle(self.strings.Common_Cancel, with: Font.regular(17.0), with: self.theme.rootController.navigationBar.accentTextColor, for: .normal)
self.sendButton.setTitle(self.strings.MediaPicker_Send, with: Font.medium(17.0), with: self.theme.rootController.navigationBar.accentTextColor, for: .normal)
if let selectionState = self.controllerInteraction.selectionState {
let sendEnabled = selectionState.count() > 0
let color = sendEnabled ? self.theme.rootController.navigationBar.accentTextColor : self.theme.rootController.navigationBar.disabledButtonColor
self.sendButton.setTitle(self.strings.MediaPicker_Send, with: Font.medium(17.0), with: color, for: .normal)
}
if themeUpdated {
self.backgroundColor = self.theme.chatList.backgroundColor
@ -322,7 +331,7 @@ class WebSearchControllerNode: ASDisplayNode {
var insets = layout.insets(options: [.input])
insets.top += navigationBarHeight
let segmentedHeight: CGFloat = 40.0
let segmentedHeight: CGFloat = self.segmentedControl.superview != nil ? 40.0 : 5.0
let panelY: CGFloat = insets.top - UIScreenPixel - 4.0
transition.updateFrame(node: self.segmentedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelY), size: CGSize(width: layout.size.width, height: segmentedHeight)))
@ -342,7 +351,7 @@ class WebSearchControllerNode: ASDisplayNode {
if let image = self.attributionNode.image {
transition.updateFrame(node: self.attributionNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - image.size.width) / 2.0), y: toolbarY + floor((toolbarHeight - image.size.height) / 2.0)), size: image.size))
transition.updateAlpha(node: self.attributionNode, alpha: self.webSearchInterfaceState.state?.mode == .gifs ? 1.0 : 0.0)
transition.updateAlpha(node: self.attributionNode, alpha: self.webSearchInterfaceState.state?.scope == .gifs ? 1.0 : 0.0)
}
let toolbarPadding: CGFloat = 15.0
@ -352,6 +361,20 @@ class WebSearchControllerNode: ASDisplayNode {
let sendSize = self.sendButton.measure(CGSize(width: layout.size.width, height: toolbarHeight))
transition.updateFrame(node: self.sendButton, frame: CGRect(origin: CGPoint(x: layout.size.width - toolbarPadding - layout.safeInsets.right - sendSize.width, y: toolbarY), size: CGSize(width: sendSize.width, height: toolbarHeight)))
if let selectionState = self.controllerInteraction.selectionState {
self.sendButton.isHidden = false
let previousSendEnabled = self.sendButton.isEnabled
let sendEnabled = selectionState.count() > 0
self.sendButton.isEnabled = sendEnabled
if sendEnabled != previousSendEnabled {
let color = sendEnabled ? self.theme.rootController.navigationBar.accentTextColor : self.theme.rootController.navigationBar.disabledButtonColor
self.sendButton.setTitle(self.strings.MediaPicker_Send, with: Font.medium(17.0), with: color, for: .normal)
}
} else {
self.sendButton.isHidden = true
}
let previousBounds = self.gridNode.bounds
self.gridNode.bounds = CGRect(x: previousBounds.origin.x, y: previousBounds.origin.y, width: layout.size.width, height: layout.size.height)
self.gridNode.position = CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0)
@ -396,7 +419,19 @@ class WebSearchControllerNode: ASDisplayNode {
self.webSearchInterfaceStatePromise.set(self.webSearchInterfaceState)
if let state = interfaceState.state {
self.segmentedControl.selectedSegmentIndex = Int(state.mode.rawValue)
self.segmentedControl.selectedSegmentIndex = Int(state.scope.rawValue)
}
if let validLayout = self.containerLayout {
self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate)
}
}
func updateSelectionState(animated: Bool) {
self.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? WebSearchItemNode {
itemNode.updateSelectionState(animated: animated)
}
}
if let validLayout = self.containerLayout {
@ -432,12 +467,8 @@ class WebSearchControllerNode: ASDisplayNode {
}
strongSelf.isLoadingMore = false
var results: [ChatContextResult] = []
for result in currentProcessedResults.results {
results.append(result)
}
for result in nextResults.results {
results.append(result)
}
results.append(contentsOf: currentProcessedResults.results)
results.append(contentsOf: nextResults.results.suffix(from: 1))
let mergedResults = ChatContextResultCollection(botId: currentProcessedResults.botId, peerId: currentProcessedResults.peerId, query: currentProcessedResults.query, geoPoint: currentProcessedResults.geoPoint, queryId: nextResults.queryId, nextOffset: nextResults.nextOffset, presentation: currentProcessedResults.presentation, switchPeer: currentProcessedResults.switchPeer, results: results, cacheTimeout: currentProcessedResults.cacheTimeout)
strongSelf.currentProcessedResults = mergedResults
strongSelf.results.set(mergedResults)
@ -527,11 +558,13 @@ class WebSearchControllerNode: ASDisplayNode {
}
@objc private func indexChanged() {
self.requestUpdateInterfaceState(true) { current in
if let mode = WebSearchMode(rawValue: Int32(self.segmentedControl.selectedSegmentIndex)) {
return current.withUpdatedMode(mode)
if let scope = WebSearchScope(rawValue: Int32(self.segmentedControl.selectedSegmentIndex)) {
let _ = updateWebSearchSettingsInteractively(postbox: self.account.postbox) { _ -> WebSearchSettings in
return WebSearchSettings(scope: scope)
}.start()
self.requestUpdateInterfaceState(true) { current in
return current.withUpdatedScope(scope)
}
return current
}
}
@ -540,67 +573,80 @@ class WebSearchControllerNode: ASDisplayNode {
}
@objc private func sendPressed() {
if let results = self.currentProcessedResults {
self.controllerInteraction.sendSelected(results, nil)
}
self.controllerInteraction.sendSelected(nil)
self.cancel?()
}
func openResult(currentResult: ChatContextResult, present: (ViewController, Any?) -> Void) {
if let state = self.webSearchInterfaceState.state, state.mode == .images {
if let results = self.currentProcessedResults?.results {
presentLegacyWebSearchGallery(account: self.account, peer: nil, theme: self.theme, results: results, current: currentResult, selectionContext: self.selectionContext, editingContext: self.controllerInteraction.editingContext, updateHiddenMedia: { [weak self] id in
self?.hiddenMediaId.set(.single(id))
}, initialLayout: self.containerLayout?.0, transitionHostView: { [weak self] in
return self?.gridNode.view
}, transitionView: { [weak self] result in
return self?.transitionView(for: result)
}, completed: { [weak self] result in
if let strongSelf = self, let results = strongSelf.currentProcessedResults {
strongSelf.controllerInteraction.sendSelected(results, nil)
strongSelf.cancel?()
}
}, present: present)
}
} else {
if let results = self.currentProcessedResults?.results {
var entries: [WebSearchGalleryEntry] = []
var centralIndex: Int = 0
for i in 0 ..< results.count {
entries.append(WebSearchGalleryEntry(result: results[i]))
if results[i] == currentResult {
centralIndex = i
}
if self.controllerInteraction.selectionState != nil {
if let state = self.webSearchInterfaceState.state, state.scope == .images {
if let results = self.currentProcessedResults?.results {
presentLegacyWebSearchGallery(account: self.account, peer: self.peer, theme: self.theme, results: results, current: currentResult, selectionContext: self.controllerInteraction.selectionState, editingContext: self.controllerInteraction.editingState, updateHiddenMedia: { [weak self] id in
self?.hiddenMediaId.set(.single(id))
}, initialLayout: self.containerLayout?.0, transitionHostView: { [weak self] in
return self?.gridNode.view
}, transitionView: { [weak self] result in
return self?.transitionView(for: result)
}, completed: { [weak self] result in
if let strongSelf = self {
strongSelf.controllerInteraction.sendSelected(result)
strongSelf.cancel?()
}
}, present: present)
}
let controller = WebSearchGalleryController(account: self.account, entries: entries, centralIndex: centralIndex, replaceRootController: { (controller, _) in
} else {
if let results = self.currentProcessedResults?.results {
var entries: [WebSearchGalleryEntry] = []
var centralIndex: Int = 0
for i in 0 ..< results.count {
entries.append(WebSearchGalleryEntry(result: results[i]))
if results[i] == currentResult {
centralIndex = i
}
}
}, baseNavigationController: nil)
self.hiddenMediaId.set((controller.hiddenMedia |> deliverOnMainQueue)
|> map { entry in
return entry?.result.id
})
present(controller, WebSearchGalleryControllerPresentationArguments(transitionArguments: { [weak self] entry -> GalleryTransitionArguments? in
if let strongSelf = self {
var transitionNode: WebSearchItemNode?
strongSelf.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? WebSearchItemNode, itemNode.item?.result.id == entry.result.id {
transitionNode = itemNode
let controller = WebSearchGalleryController(account: self.account, peer: self.peer, entries: entries, centralIndex: centralIndex, replaceRootController: { (controller, _) in
}, baseNavigationController: nil)
self.hiddenMediaId.set((controller.hiddenMedia |> deliverOnMainQueue)
|> map { entry in
return entry?.result.id
})
present(controller, WebSearchGalleryControllerPresentationArguments(transitionArguments: { [weak self] entry -> GalleryTransitionArguments? in
if let strongSelf = self {
var transitionNode: WebSearchItemNode?
strongSelf.gridNode.forEachItemNode { itemNode in
if let itemNode = itemNode as? WebSearchItemNode, itemNode.item?.result.id == entry.result.id {
transitionNode = itemNode
}
}
if let transitionNode = transitionNode {
return GalleryTransitionArguments(transitionNode: (transitionNode, { [weak transitionNode] in
return transitionNode?.transitionView().snapshotContentTree(unhide: true)
}), addToTransitionSurface: { view in
if let strongSelf = self {
strongSelf.gridNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.gridNode.view)
}
})
}
}
if let transitionNode = transitionNode {
return GalleryTransitionArguments(transitionNode: (transitionNode, { [weak transitionNode] in
return transitionNode?.transitionView().snapshotContentTree(unhide: true)
}), addToTransitionSurface: { view in
if let strongSelf = self {
strongSelf.gridNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.gridNode.view)
}
})
}
}
return nil
}))
return nil
}))
}
}
} else {
presentLegacyWebSearchEditor(account: self.account, theme: self.theme, result: currentResult, initialLayout: self.containerLayout?.0, updateHiddenMedia: { [weak self] id in
self?.hiddenMediaId.set(.single(id))
}, transitionHostView: { [weak self] in
return self?.gridNode.view
}, transitionView: { [weak self] result in
return self?.transitionView(for: result)
}, completed: { [weak self] result in
if let strongSelf = self {
strongSelf.controllerInteraction.avatarCompleted(result)
strongSelf.cancel?()
}
}, present: present)
}
}

View File

@ -16,14 +16,14 @@ struct WebSearchGalleryEntry: Equatable {
func item(account: Account, presentationData: PresentationData) -> GalleryItem {
switch self.result {
case let .externalReference(queryId, id, type, _, _, url, content, thumbnail, _):
case let .externalReference(_, _, type, _, _, _, content, thumbnail, _):
if let content = content, type == "gif", let thumbnailResource = thumbnail?.resource, let dimensions = content.dimensions {
let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])]))
return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: fileReference, streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true), originData: nil, indexData: nil, contentInfo: nil, caption: NSAttributedString(), credit: nil, performAction: { _ in }, openActionOptions: { _ in })
return WebSearchVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: fileReference, streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true))
}
case let .internalReference(queryId, id, _, _, _, _, file, _):
case let .internalReference(_, _, _, _, _, _, file, _):
if let file = file {
return UniversalVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: .standalone(media: file), streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true), originData: nil, indexData: nil, contentInfo: nil, caption: NSAttributedString(), credit: nil, performAction: { _ in }, openActionOptions: { _ in })
return WebSearchVideoGalleryItem(account: account, presentationData: presentationData, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), fileReference: .standalone(media: file), streamVideo: false, loopVideo: true, enableSound: false, fetchAutomatically: true))
}
}
preconditionFailure()
@ -31,9 +31,11 @@ struct WebSearchGalleryEntry: Equatable {
}
final class WebSearchGalleryControllerPresentationArguments {
let animated: Bool
let transitionArguments: (WebSearchGalleryEntry) -> GalleryTransitionArguments?
init(transitionArguments: @escaping (WebSearchGalleryEntry) -> GalleryTransitionArguments?) {
init(animated: Bool = true, transitionArguments: @escaping (WebSearchGalleryEntry) -> GalleryTransitionArguments?) {
self.animated = animated
self.transitionArguments = transitionArguments
}
}
@ -63,6 +65,8 @@ class WebSearchGalleryController: ViewController {
private let centralItemFooterContentNode = Promise<GalleryFooterContentNode?>()
private let centralItemAttributesDisposable = DisposableSet();
private var checkNode: GalleryNavigationCheckNode?
private let _hiddenMedia = Promise<WebSearchGalleryEntry?>(nil)
var hiddenMedia: Signal<WebSearchGalleryEntry?, NoError> {
return self._hiddenMedia.get()
@ -71,7 +75,7 @@ class WebSearchGalleryController: ViewController {
private let replaceRootController: (ViewController, ValuePromise<Bool>?) -> Void
private let baseNavigationController: NavigationController?
init(account: Account, entries: [WebSearchGalleryEntry], centralIndex: Int, replaceRootController: @escaping (ViewController, ValuePromise<Bool>?) -> Void, baseNavigationController: NavigationController?) {
init(account: Account, peer: Peer?, entries: [WebSearchGalleryEntry], centralIndex: Int, replaceRootController: @escaping (ViewController, ValuePromise<Bool>?) -> Void, baseNavigationController: NavigationController?) {
self.account = account
self.replaceRootController = replaceRootController
self.baseNavigationController = baseNavigationController
@ -80,8 +84,16 @@ class WebSearchGalleryController: ViewController {
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
let backItem = UIBarButtonItem(backButtonAppearanceWithTitle: presentationData.strings.Common_Back, target: self, action: #selector(self.donePressed))
self.navigationItem.leftBarButtonItem = backItem
if let title = peer?.displayTitle {
let recipientNode = GalleryNavigationRecipientNode(color: .white, title: title)
let leftItem = UIBarButtonItem(customDisplayNode: recipientNode)
self.navigationItem.leftBarButtonItem = leftItem
}
let checkNode = GalleryNavigationCheckNode(theme: self.presentationData.theme)
let rightItem = UIBarButtonItem(customDisplayNode: checkNode)
self.navigationItem.rightBarButtonItem = rightItem
self.checkNode = checkNode
self.statusBar.statusBarStyle = .White
@ -236,8 +248,12 @@ class WebSearchGalleryController: ViewController {
if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) {
nodeAnimatesItself = true
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
centralItemNode.activateAsInitial()
if presentationArguments.animated {
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
}
self._hiddenMedia.set(.single(self.entries[centralItemNode.index]))
}
}

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
struct WebSearchSelectionState: Equatable {
let selectedIds: Set<String>
static func ==(lhs: WebSearchSelectionState, rhs: WebSearchSelectionState) -> Bool {
return lhs.selectedIds == rhs.selectedIds
}
init(selectedIds: Set<String>) {
self.selectedIds = selectedIds
}
}
enum WebSearchMode: Int32 {
enum WebSearchScope: Int32 {
case images
case gifs
}
struct WebSearchInterfaceInnerState: Equatable {
let mode: WebSearchMode
let scope: WebSearchScope
let query: String
let selectionState: WebSearchSelectionState
}
struct WebSearchInterfaceState: Equatable {
@ -37,27 +24,12 @@ struct WebSearchInterfaceState: Equatable {
self.presentationData = presentationData
}
func withUpdatedMode(_ mode: WebSearchMode) -> WebSearchInterfaceState {
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(mode: mode, query: self.state?.query ?? "", selectionState: self.state?.selectionState ?? WebSearchSelectionState(selectedIds: [])), presentationData: self.presentationData)
func withUpdatedScope(_ scope: WebSearchScope) -> WebSearchInterfaceState {
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(scope: scope, query: self.state?.query ?? ""), presentationData: self.presentationData)
}
func withUpdatedQuery(_ query: String) -> WebSearchInterfaceState {
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(mode: self.state?.mode ?? .images, query: query, selectionState: self.state?.selectionState ?? WebSearchSelectionState(selectedIds: [])), presentationData: self.presentationData)
}
func withToggledSelectedMessages(_ ids: [String], value: Bool) -> WebSearchInterfaceState {
var selectedIds = Set<String>()
if let selectionState = self.state?.selectionState {
selectedIds.formUnion(selectionState.selectedIds)
}
for id in ids {
if value {
selectedIds.insert(id)
} else {
selectedIds.remove(id)
}
}
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(mode: self.state?.mode ?? .images, query: self.state?.query ?? "", selectionState: WebSearchSelectionState(selectedIds: selectedIds)), presentationData: self.presentationData)
return WebSearchInterfaceState(state: WebSearchInterfaceInnerState(scope: self.state?.scope ?? .images, query: query), presentationData: self.presentationData)
}
func withUpdatedPresentationData(_ presentationData: PresentationData) -> WebSearchInterfaceState {

View File

@ -40,7 +40,7 @@ final class WebSearchItem: GridItem {
final class WebSearchItemNode: GridItemNode {
private let imageNodeBackground: ASDisplayNode
private let imageNode: TransformImageNode
private var selectionNode: GridMessageSelectionNode?
private var checkNode: CheckNode?
private var currentImageResource: TelegramMediaResource?
private var currentVideoFile: TelegramMediaFile?
@ -52,8 +52,6 @@ final class WebSearchItemNode: GridItemNode {
private let fetchDisposable = MetaDisposable()
private var resourceStatus: MediaResourceStatus?
private let statusNode: RadialStatusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
override init() {
self.imageNodeBackground = ASDisplayNode()
self.imageNodeBackground.isLayerBacked = true
@ -86,13 +84,14 @@ final class WebSearchItemNode: GridItemNode {
func setup(item: WebSearchItem) {
if self.item !== item {
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedStatusSignal: Signal<MediaResourceStatus, NoError>?
var thumbnailDimensions: CGSize?
var thumbnailResource: TelegramMediaResource?
var imageResource: TelegramMediaResource?
var videoFile: TelegramMediaFile?
var imageDimensions: CGSize?
switch item.result {
case let .externalReference(_, _, type, title, _, url, content, thumbnail, _):
case let .externalReference(_, _, type, _, _, _, content, thumbnail, _):
if let content = content {
imageResource = content.resource
} else if let thumbnail = thumbnail {
@ -103,18 +102,16 @@ final class WebSearchItemNode: GridItemNode {
videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource)], mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [])])
imageResource = nil
}
if let file = videoFile {
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource)
} else if let imageResource = imageResource {
updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
}
case let .internalReference(_, _, _, title, _, image, file, _):
case let .internalReference(_, _, _, _, _, image, file, _):
if let image = image {
if let largestRepresentation = largestImageRepresentation(image.representations) {
imageDimensions = largestRepresentation.dimensions
}
imageResource = imageRepresentationLargerThan(image.representations, size: CGSize(width: 200.0, height: 100.0))?.resource
if let thumbnailRepresentation = smallestImageRepresentation(image.representations) {
thumbnailDimensions = thumbnailRepresentation.dimensions
thumbnailResource = thumbnailRepresentation.resource
}
} else if let file = file {
if let dimensions = file.dimensions {
imageDimensions = dimensions
@ -123,32 +120,24 @@ final class WebSearchItemNode: GridItemNode {
}
imageResource = smallestImageRepresentation(file.previewRepresentations)?.resource
}
// if let file = file {
// if file.isVideo && file.isAnimated {
// videoFile = file
// imageResource = nil
// updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(file.resource)
// } else if let imageResource = imageResource {
// updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
// }
// } else if let imageResource = imageResource {
// updatedStatusSignal = item.account.postbox.mediaBox.resourceStatus(imageResource)
// }
}
if let imageResource = imageResource, let imageDimensions = imageDimensions {
let tmpRepresentation = TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource)
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [tmpRepresentation], reference: nil, partialReference: nil)
var representations: [TelegramMediaImageRepresentation] = []
if let thumbnailResource = thumbnailResource, let thumbnailDimensions = thumbnailDimensions {
representations.append(TelegramMediaImageRepresentation(dimensions: thumbnailDimensions, resource: thumbnailResource))
}
representations.append(TelegramMediaImageRepresentation(dimensions: imageDimensions, resource: imageResource))
let tmpImage = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations, reference: nil, partialReference: nil)
updateImageSignal = mediaGridMessagePhoto(account: item.account, photoReference: .standalone(media: tmpImage))
} else {
updateImageSignal = .complete()
}
if let updateImageSignal = updateImageSignal {
let editingContext = item.controllerInteraction.editingContext
let editingContext = item.controllerInteraction.editingState
let editedImageSignal = Signal<UIImage?, NoError> { subscriber in
let editableItem = LegacyWebSearchItem(result: item.result, dimensions: CGSize(), thumbnailImage: .complete(), originalImage: .complete())
let editableItem = LegacyWebSearchItem(result: item.result)
if let signal = editingContext.thumbnailImageSignal(for: editableItem) {
let disposable = signal.start(next: { next in
if let image = next as? UIImage {
@ -205,41 +194,45 @@ final class WebSearchItemNode: GridItemNode {
if let _ = imageDimensions {
self.setNeedsLayout()
}
self.updateHiddenMedia()
}
self.item = item
self.updateSelectionState(animated: false)
}
@objc func toggleSelection() {
if let checkNode = self.checkNode, let item = self.item {
checkNode.setIsChecked(!checkNode.isChecked, animated: true)
item.controllerInteraction.toggleSelection(item.result, checkNode.isChecked)
}
}
func updateSelectionState(animated: Bool) {
if self.selectionNode == nil, let item = self.item {
let selectionNode = GridMessageSelectionNode(theme: item.theme, toggle: { [weak self] value in
if let strongSelf = self, let item = strongSelf.item {
item.controllerInteraction.toggleSelection([item.result.id], value)
strongSelf.updateSelectionState(animated: true)
}
})
self.addSubnode(selectionNode)
self.selectionNode = selectionNode
if self.checkNode == nil, let item = self.item, let _ = item.controllerInteraction.selectionState {
let checkNode = CheckNode(strokeColor: item.theme.list.itemCheckColors.strokeColor, fillColor: item.theme.list.itemCheckColors.fillColor, foregroundColor: item.theme.list.itemCheckColors.foregroundColor, style: .overlay)
checkNode.addTarget(target: self, action: #selector(self.toggleSelection))
self.addSubnode(checkNode)
self.checkNode = checkNode
self.setNeedsLayout()
}
if let item = self.item {
if let selectionState = item.controllerInteraction.selectionState {
let selected = selectionState.selectedIds.contains(item.result.id)
self.selectionNode?.updateSelected(selected, animated: animated)
let selected = selectionState.isIdentifierSelected(item.result.id)
self.checkNode?.setIsChecked(selected, animated: animated)
}
}
}
func updateHiddenMedia() {
if let item = self.item {
self.imageNode.isHidden = item.controllerInteraction.hiddenMediaId == item.result.id
self.isHidden = item.controllerInteraction.hiddenMediaId == item.result.id
}
}
func transitionView() -> UIView {
let view = self.imageNode.view.snapshotContentTree(unhide: true)!
let view = self.view.snapshotContentTree(unhide: true, keepTransform: true)!
view.frame = self.convert(self.bounds, to: nil)
return view
}
@ -256,11 +249,7 @@ final class WebSearchItemNode: GridItemNode {
}
let checkSize = CGSize(width: 32.0, height: 32.0)
self.selectionNode?.frame = CGRect(origin: CGPoint(x: imageFrame.width - checkSize.width, y: 0.0), size: checkSize)
let progressDiameter: CGFloat = 40.0
self.statusNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - progressDiameter) / 2.0), y: floor((imageFrame.size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter))
//self.videoAccessoryNode.frame = CGRect(origin: CGPoint(x: imageFrame.maxX - self.videoAccessoryNode.contentSize.width - 5, y: imageFrame.maxY - self.videoAccessoryNode.contentSize.height - 5), size: self.videoAccessoryNode.contentSize)
self.checkNode?.frame = CGRect(origin: CGPoint(x: imageFrame.width - checkSize.width, y: 0.0), size: checkSize)
}
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
@ -274,9 +263,6 @@ final class WebSearchItemNode: GridItemNode {
switch gesture {
case .tap:
item.controllerInteraction.openResult(item.result)
case .longTap:
break
default:
break
}

View File

@ -75,6 +75,7 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
private var textNode: TextNode?
private var item: WebSearchRecentQueryItem?
private var layoutParams: ListViewItemLayoutParams?
required init() {
self.backgroundNode = ASDisplayNode()
@ -152,6 +153,7 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
return (nil, { _ in
if let strongSelf = self {
strongSelf.item = item
strongSelf.layoutParams = params
if let _ = updatedTheme {
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
@ -205,95 +207,16 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
override func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
super.updateRevealOffset(offset: offset, transition: transition)
// if let _ = self.item, let params = self.layoutParams?.5 {
// let editingOffset: CGFloat
// if let selectableControlNode = self.selectableControlNode {
// editingOffset = selectableControlNode.bounds.size.width
// var selectableControlFrame = selectableControlNode.frame
// selectableControlFrame.origin.x = params.leftInset + offset
// transition.updateFrame(node: selectableControlNode, frame: selectableControlFrame)
// } else {
// editingOffset = 0.0
// }
//
// if let reorderControlNode = self.reorderControlNode {
// var reorderControlFrame = reorderControlNode.frame
// reorderControlFrame.origin.x = params.width - params.rightInset - reorderControlFrame.size.width + offset
// transition.updateFrame(node: reorderControlNode, frame: reorderControlFrame)
// }
//
// let leftInset: CGFloat = params.leftInset + 78.0
//
// let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: 8.0), size: CGSize(width: params.width - leftInset - params.rightInset - 10.0 - 1.0 - editingOffset, height: itemHeight - 12.0 - 9.0))
//
// let contentRect = rawContentRect.offsetBy(dx: editingOffset + leftInset + offset, dy: 0.0)
//
// var avatarFrame = self.avatarNode.frame
// avatarFrame.origin.x = leftInset - 78.0 + editingOffset + 10.0 + offset
// transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
// if let multipleAvatarsNode = self.multipleAvatarsNode {
// transition.updateFrame(node: multipleAvatarsNode, frame: avatarFrame)
// }
//
// var titleOffset: CGFloat = 0.0
// if let secretIconNode = self.secretIconNode, let image = secretIconNode.image {
// transition.updateFrame(node: secretIconNode, frame: CGRect(origin: CGPoint(x: contentRect.minX, y: secretIconNode.frame.minY), size: image.size))
// titleOffset += image.size.width + 3.0
// }
//
// let titleFrame = self.titleNode.frame
// transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + titleOffset, y: titleFrame.origin.y), size: titleFrame.size))
//
// let authorFrame = self.authorNode.frame
// transition.updateFrame(node: self.authorNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: authorFrame.origin.y), size: authorFrame.size))
//
// transition.updateFrame(node: self.inputActivitiesNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: self.inputActivitiesNode.frame.minY), size: self.inputActivitiesNode.bounds.size))
//
// let textFrame = self.textNode.frame
// transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: textFrame.origin.y), size: textFrame.size))
//
// let dateFrame = self.dateNode.frame
// transition.updateFrame(node: self.dateNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateFrame.size.width, y: dateFrame.minY), size: dateFrame.size))
//
// let statusFrame = self.statusNode.frame
// transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateFrame.size.width - 2.0 - statusFrame.size.width, y: statusFrame.minY), size: statusFrame.size))
//
// var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleFrame.size.width + 3.0 + titleOffset
//
// if let verificationIconNode = self.verificationIconNode {
// transition.updateFrame(node: verificationIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: verificationIconNode.frame.origin.y), size: verificationIconNode.bounds.size))
// nextTitleIconOrigin += verificationIconNode.bounds.size.width + 5.0
// }
//
// let mutedIconFrame = self.mutedIconNode.frame
// transition.updateFrame(node: self.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: contentRect.origin.y + 6.0), size: mutedIconFrame.size))
// nextTitleIconOrigin += mutedIconFrame.size.width + 3.0
//
// let badgeBackgroundFrame = self.badgeBackgroundNode.frame
// let updatedBadgeBackgroundFrame = CGRect(origin: CGPoint(x: contentRect.maxX - badgeBackgroundFrame.size.width, y: contentRect.maxY - badgeBackgroundFrame.size.height - 2.0), size: badgeBackgroundFrame.size)
// transition.updateFrame(node: self.badgeBackgroundNode, frame: updatedBadgeBackgroundFrame)
//
// if self.mentionBadgeNode.supernode != nil {
// let mentionBadgeSize = self.mentionBadgeNode.bounds.size
// let mentionBadgeOffset: CGFloat
// if updatedBadgeBackgroundFrame.size.width.isZero || self.badgeBackgroundNode.image == nil {
// mentionBadgeOffset = contentRect.maxX - mentionBadgeSize.width
// } else {
// mentionBadgeOffset = contentRect.maxX - updatedBadgeBackgroundFrame.size.width - 6.0 - mentionBadgeSize.width
// }
//
// let badgeBackgroundWidth = mentionBadgeSize.width
// let badgeBackgroundFrame = CGRect(x: mentionBadgeOffset, y: self.mentionBadgeNode.frame.origin.y, width: badgeBackgroundWidth, height: mentionBadgeSize.height)
// transition.updateFrame(node: self.mentionBadgeNode, frame: badgeBackgroundFrame)
// }
//
// let badgeTextFrame = self.badgeTextNode.frame
// transition.updateFrame(node: self.badgeTextNode, frame: CGRect(origin: CGPoint(x: updatedBadgeBackgroundFrame.midX - badgeTextFrame.size.width / 2.0, y: badgeTextFrame.minY), size: badgeTextFrame.size))
// }
if let params = self.layoutParams, let textNode = self.textNode {
let leftInset: CGFloat = 15.0 + params.leftInset
var textFrame = textNode.frame
textFrame.origin.x = leftInset + offset
transition.updateFrame(node: textNode, frame: textFrame)
}
}
override func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
var close = true
if let item = self.item {
switch option.key {
case RevealOptionKey.delete.rawValue:
@ -302,9 +225,7 @@ class WebSearchRecentQueryItemNode: ItemListRevealOptionsItemNode {
break
}
}
if close {
self.setRevealOptionsOpened(false, animated: true)
self.revealOptionsInteractivelyClosed()
}
self.setRevealOptionsOpened(false, animated: true)
self.revealOptionsInteractivelyClosed()
}
}

View File

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

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