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