Added caption scroll in media viewer

This commit is contained in:
Ilya Laktyushin 2019-03-26 22:47:33 +02:00
parent 6b34f748d7
commit 58eb595460
28 changed files with 353 additions and 162 deletions

View File

@ -2490,6 +2490,13 @@
path = TelegramUI/Resources/Animations;
sourceTree = "<group>";
};
093857B422464B2700EB6A54 /* Stats */ = {
isa = PBXGroup;
children = (
);
name = Stats;
sourceTree = "<group>";
};
0941A99E210B053300EBE194 /* Open In */ = {
isa = PBXGroup;
children = (
@ -4325,6 +4332,7 @@
D0EE97131D88BB1A006C18E1 /* Peer Info */ = {
isa = PBXGroup;
children = (
093857B422464B2700EB6A54 /* Stats */,
D0B843CC1DA903BB005F29E1 /* PeerInfoController.swift */,
D0486F091E523C8500091F0C /* GroupInfoController.swift */,
D03E5E0E1E55F8B90029569A /* ChannelVisibilityController.swift */,

View File

@ -99,7 +99,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
}
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let width = size.width
var panelHeight: CGFloat = 44.0 + bottomInset
panelHeight += contentInset

View File

@ -296,7 +296,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
return false
}
strongSelf.commitPurposefulAction()
strongSelf.videoUnmuteTooltipController?.dismiss()
strongSelf.dismissAllTooltips()
var openMessageByAction: Bool = false
@ -860,7 +860,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}
})
}
}, longTap: { [weak self] action, messageId in
}, longTap: { [weak self] action, message in
if let strongSelf = self {
switch action {
case let .url(url):
@ -1027,7 +1027,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
strongSelf.chatDisplayNode.dismissInput()
strongSelf.present(actionSheet, in: .window(.root))
case let .timecode(timecode, text):
guard let messageId = messageId else {
guard let message = message else {
return
}
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
@ -1036,7 +1036,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.controllerInteraction?.seekToTimecode(messageId, timecode)
strongSelf.controllerInteraction?.seekToTimecode(message, timecode, true)
}
}),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
@ -1216,21 +1216,22 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}))
}
}
}, seekToTimecode: { [weak self] messageId, timestamp in
}, seekToTimecode: { [weak self] message, timestamp, forceOpen in
if let strongSelf = self {
let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId)
var completed = false
strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in
if !completed, let itemNode = itemNode as? ChatMessageItemView, itemNode.item?.message.id == messageId, let (action, _, _, _, _) = itemNode.playMediaWithSound() {
if case let .visible(fraction) = itemNode.visibility, fraction > 0.7 {
action(Double(timestamp))
} else if let message = message {
let _ = strongSelf.controllerInteraction?.openMessage(message, .timecode(Double(timestamp)))
var found = false
if !forceOpen {
strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in
if !found, let itemNode = itemNode as? ChatMessageItemView, itemNode.item?.message.id == message.id, let (action, _, _, _, _) = itemNode.playMediaWithSound() {
if case let .visible(fraction) = itemNode.visibility, fraction > 0.7 {
action(Double(timestamp))
} else {
let _ = strongSelf.controllerInteraction?.openMessage(message, .timecode(Double(timestamp)))
}
found = true
}
completed = true
}
}
if !completed, let message = message {
if !found {
let _ = strongSelf.controllerInteraction?.openMessage(message, .timecode(Double(timestamp)))
}
}
@ -2762,15 +2763,15 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
guard let strongSelf = self, let layout = strongSelf.validLayout, strongSelf.traceVisibility() && isTopmostChatController(strongSelf) else {
return
}
let deviceMetrics = DeviceMetrics.forScreenSize(layout.size)
let icon: UIImage?
if deviceMetrics == .iPhoneX || deviceMetrics == .iPhoneXSMax {
icon = UIImage(bundleImageName: "Chat/Message/VolumeButtonIconX")
} else {
icon = UIImage(bundleImageName: "Chat/Message/VolumeButtonIcon")
switch DeviceMetrics.forScreenSize(layout.size) {
case .iPhoneX?, .iPhoneXSMax?:
icon = UIImage(bundleImageName: "Chat/Message/VolumeButtonIconX")
default:
icon = UIImage(bundleImageName: "Chat/Message/VolumeButtonIcon")
}
if let location = location, let icon = icon {
strongSelf.mediaRestrictedTooltipController?.dismiss()
strongSelf.videoUnmuteTooltipController?.dismiss()
let tooltipController = TooltipController(content: .iconAndText(icon, strongSelf.presentationInterfaceState.strings.Conversation_PressVolumeButtonForSound), timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
strongSelf.videoUnmuteTooltipController = tooltipController
tooltipController.dismissed = { [weak tooltipController] in
@ -2816,7 +2817,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
let _ = ApplicationSpecificNotice.incrementChatMediaMediaRecordingTips(accountManager: strongSelf.context.sharedContext.accountManager, count: 3).start()
}
strongSelf.displayMediaRecordingTip()
strongSelf.displayMediaRecordingTooltip()
}
}, setupMessageAutoremoveTimeout: { [weak self] in
if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
@ -3491,7 +3492,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}
if displayTip {
let _ = ApplicationSpecificNotice.incrementChatMediaMediaRecordingTips(accountManager: strongSelf.context.sharedContext.accountManager).start()
strongSelf.displayMediaRecordingTip()
strongSelf.displayMediaRecordingTooltip()
}
})
}
@ -3505,11 +3506,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
self.chatDisplayNode.historyNode.canReadHistory.set(.single(false))
self.saveInterfaceState()
self.messageTooltipController?.dismiss()
self.videoUnmuteTooltipController?.dismiss()
self.silentPostTooltipController?.dismiss()
self.mediaRecordingModeTooltipController?.dismiss()
self.mediaRestrictedTooltipController?.dismiss()
self.dismissAllTooltips()
self.window?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
@ -4143,24 +4140,36 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
legacyController.bind(controller: navigationController)
legacyController.enableSizeClassSignal = true
let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, editMediaOptions: editMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, openGallery: {
let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, editMediaOptions: editMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText.string, openGallery: {
self?.presentMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals in
if !inputText.string.isEmpty {
strongSelf.clearInputText()
}
if editMediaOptions != nil {
self?.editMessageMediaWithLegacySignals(signals)
} else {
self?.enqueueMediaMessages(signals: signals)
}
})
}, openCamera: { cameraView, menuController in
}, openCamera: { [weak self] cameraView, menuController in
if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
presentedLegacyCamera(context: strongSelf.context, peer: peer, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, sendMessagesWithSignals: { signals in
if editMediaOptions != nil {
self?.editMessageMediaWithLegacySignals(signals!)
} else {
self?.enqueueMediaMessages(signals: signals)
presentedLegacyCamera(context: strongSelf.context, peer: peer, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: settings.storeEditedPhotos, mediaGrouping: true, initialCaption: inputText.string, sendMessagesWithSignals: { [weak self] signals in
if let strongSelf = self {
if editMediaOptions != nil {
strongSelf.editMessageMediaWithLegacySignals(signals!)
} else {
strongSelf.enqueueMediaMessages(signals: signals)
}
if !inputText.string.isEmpty {
strongSelf.clearInputText()
}
}
}, recognizedQRCode: { [weak self] code in
if let strongSelf = self, let (host, port, username, password, secret) = parseProxyUrl(code) {
strongSelf.openResolved(ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret))
}
}, recognizedQRCode: { code in
self?.processQRCode(code)
})
}
}, openFileGallery: {
@ -4174,6 +4183,9 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}, openPoll: {
self?.presentPollCreation()
}, sendMessagesWithSignals: { [weak self] signals in
if !inputText.string.isEmpty {
strongSelf.clearInputText()
}
if editMediaOptions != nil {
self?.editMessageMediaWithLegacySignals(signals!)
} else {
@ -4290,6 +4302,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
return
}
let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
let _ = legacyAssetPicker(context: strongSelf.context, presentationData: strongSelf.presentationData, editingMedia: editingMedia, fileMode: fileMode, peer: peer, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true).start(next: { generator in
if let strongSelf = self {
let legacyController = LegacyController(presentation: .modal(animateIn: true), theme: strongSelf.presentationData.theme, initialLayout: strongSelf.validLayout)
@ -4301,7 +4314,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
legacyController.bind(controller: controller)
legacyController.deferScreenEdgeGestures = [.top]
configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, presentWebSearch: { [weak self, weak legacyController] in
configureLegacyAssetPicker(controller, context: strongSelf.context, peer: peer, initialCaption: inputText.string, presentWebSearch: { [weak self, weak legacyController] in
if let strongSelf = self {
let controller = WebSearchController(context: strongSelf.context, peer: peer, configuration: searchBotsConfiguration, mode: .media(completion: { results, selectionState, editingState in
if let legacyController = legacyController {
@ -4321,7 +4334,13 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}
})
controller.descriptionGenerator = legacyAssetPickerItemGenerator()
controller.completionBlock = { [weak legacyController] signals in
controller.completionBlock = { [weak legacyController, weak self] signals in
if let strongSelf = self {
if !inputText.string.isEmpty {
strongSelf.clearInputText()
}
}
if let legacyController = legacyController {
legacyController.dismiss()
completion(signals!)
@ -4811,7 +4830,6 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
} else if let videoRecorderValue = self.videoRecorderValue {
if case .send = action {
videoRecorderValue.completeVideo()
//self.tempVideoRecorderValue = videoRecorderValue
self.videoRecorder.set(.single(nil))
} else {
self.videoRecorder.set(.single(nil))
@ -6143,7 +6161,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
self.interfaceInteraction?.beginMessageSearch(.everything, query)
}
private func displayMediaRecordingTip() {
private func displayMediaRecordingTooltip() {
let rect: CGRect? = self.chatDisplayNode.frameForInputActionButton()
let updatedMode: ChatTextInputMediaRecordingButtonMode = self.presentationInterfaceState.interfaceState.mediaRecordingMode
@ -6174,8 +6192,19 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
}
}
private func dismissAllTooltips() {
self.messageTooltipController?.dismiss()
self.videoUnmuteTooltipController?.dismiss()
self.silentPostTooltipController?.dismiss()
self.mediaRecordingModeTooltipController?.dismiss()
self.mediaRestrictedTooltipController?.dismiss()
}
private func commitPurposefulAction() {
self.purposefulAction?()
if let purposefulAction = self.purposefulAction {
self.purposefulAction = nil
purposefulAction()
}
}
public var keyShortcuts: [KeyShortcut] {
@ -6287,12 +6316,6 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
return inputShortcuts + otherShortcuts
}
private func processQRCode(_ code: String) {
if let (host, port, username, password, secret) = parseProxyUrl(code) {
self.openResolved(ResolvedUrl.proxy(host: host, port: port, username: username, password: password, secret: secret))
}
}
func getTransitionInfo(messageId: MessageId, media: Media) -> ((UIView) -> Void, ASDisplayNode, () -> (UIView?, UIView?))? {
var selectedNode: (ASDisplayNode, () -> (UIView?, UIView?))?
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
@ -6313,4 +6336,17 @@ public final class ChatController: TelegramController, KeyShortcutResponder, Gal
return nil
}
}
private func clearInputText() {
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
if !state.interfaceState.effectiveInputState.inputText.string.isEmpty {
return state.updatedInterfaceState { interfaceState in
let effectiveInputState = ChatTextInputState(inputText: NSAttributedString(string: ""))
return interfaceState.withUpdatedEffectiveInputState(effectiveInputState)
}
} else {
return state
}
})
}
}

View File

@ -77,7 +77,7 @@ public final class ChatControllerInteraction {
let navigationController: () -> NavigationController?
let presentGlobalOverlayController: (ViewController, Any?) -> Void
let callPeer: (PeerId) -> Void
let longTap: (ChatControllerInteractionLongTapAction, MessageId?) -> Void
let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void
let openCheckoutOrReceipt: (MessageId) -> Void
let openSearch: () -> Void
let setupReply: (MessageId) -> Void
@ -89,7 +89,7 @@ public final class ChatControllerInteraction {
let requestSelectMessagePollOption: (MessageId, Data) -> Void
let openAppStorePage: () -> Void
let displayMessageTooltip: (MessageId, String, ASDisplayNode?, CGRect?) -> Void
let seekToTimecode: (MessageId, Double) -> Void
let seekToTimecode: (Message, Double, Bool) -> Void
let requestMessageUpdate: (MessageId) -> Void
let cancelInteractiveKeyboardGestures: () -> Void
@ -102,7 +102,7 @@ public final class ChatControllerInteraction {
var pollActionState: ChatInterfacePollActionState
var searchTextHighightState: String?
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, MessageId?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (MessageId, Double) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState) {
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState) {
self.openMessage = openMessage
self.openPeer = openPeer
self.openPeerMention = openPeerMention
@ -166,7 +166,7 @@ public final class ChatControllerInteraction {
}, requestSelectMessagePollOption: { _, _ in
}, openAppStorePage: {
}, displayMessageTooltip: { _, _, _, _ in
}, seekToTimecode: { _, _ in
}, seekToTimecode: { _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -57,6 +57,19 @@ private let playImage = generateImage(CGSize(width: 15.0, height: 18.0), rotated
private let cloudFetchIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: UIColor.white)
private let captionMaskImage = generateImage(CGSize(width: 1.0, height: 17.0), opaque: false, rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let gradientColors = [UIColor.white.withAlphaComponent(1.0).cgColor, UIColor.white.withAlphaComponent(0.0).cgColor] as CFArray
var locations: [CGFloat] = [0.0, 1.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 17.0), options: CGGradientDrawingOptions())
})
private let titleFont = Font.medium(15.0)
private let dateFont = Font.regular(14.0)
@ -102,7 +115,7 @@ enum ChatItemGalleryFooterContentTapAction {
case ignore
}
final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScrollViewDelegate {
private let context: AccountContext
private var theme: PresentationTheme
private var strings: PresentationStrings
@ -110,6 +123,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
private let deleteButton: UIButton
private let actionButton: UIButton
private let maskNode: ASDisplayNode
private let scrollWrapperNode: ASDisplayNode
private let scrollNode: ASScrollNode
private let textNode: ImmediateTextNode
private let authorNameNode: ASTextNode
private let dateNode: ASTextNode
@ -213,10 +230,18 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
self.deleteButton.setImage(deleteImage, for: [.normal])
self.actionButton.setImage(actionImage, for: [.normal])
self.scrollWrapperNode = ASDisplayNode()
self.scrollWrapperNode.clipsToBounds = true
self.scrollNode = ASScrollNode()
self.scrollNode.clipsToBounds = false
self.maskNode = ASDisplayNode()
self.textNode = ImmediateTextNode()
self.textNode.maximumNumberOfLines = 10
self.textNode.maximumNumberOfLines = 0
self.textNode.linkHighlightColor = UIColor(rgb: 0x5ac8fa, alpha: 0.2)
self.authorNameNode = ASTextNode()
self.authorNameNode.maximumNumberOfLines = 1
self.authorNameNode.isUserInteractionEnabled = false
@ -271,7 +296,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
self.view.addSubview(self.deleteButton)
self.view.addSubview(self.actionButton)
self.addSubnode(self.textNode)
self.addSubnode(self.scrollWrapperNode)
self.scrollWrapperNode.addSubnode(self.scrollNode)
self.scrollNode.addSubnode(self.textNode)
self.addSubnode(self.authorNameNode)
self.addSubnode(self.dateNode)
@ -307,6 +335,21 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
self.messageContextDisposable.dispose()
}
override func didLoad() {
super.didLoad()
self.scrollNode.view.delegate = self
if let maskImage = captionMaskImage {
let mask = CALayer()
mask.contents = maskImage.cgImage
mask.contentsScale = maskImage.scale
//mask.contentsCenter = CGRect(x: max(corners.topLeft.radius, corners.bottomLeft.radius) / maskImage.size.width, y: max(corners.topLeft.radius, corners.topRight.radius) / maskImage.size.height, width: (maskImage.size.width - max(corners.topLeft.radius, corners.bottomLeft.radius) - max(corners.topRight.radius, corners.bottomRight.radius)) / maskImage.size.width, height: (maskImage.size.height - max(corners.topLeft.radius, corners.topRight.radius) - max(corners.bottomLeft.radius, corners.bottomRight.radius)) / maskImage.size.height)
//self.scrollWrapperNode.layer.mask = mask
//self.scrollWrapperNode.layer.mask?.frame = self.scrollWrapperNode.bounds
}
}
private func actionForAttributes(_ attributes: [NSAttributedStringKey: Any]) -> GalleryControllerInteractionTapAction? {
if let url = attributes[NSAttributedStringKey(rawValue: TelegramTextAttributes.URL)] as? String {
return .url(url: url, concealed: false)
@ -441,7 +484,20 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
self.currentWebPageAndMedia = (webPage, media)
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.requestLayout?(.immediate)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if self.scrollWrapperNode.frame.contains(point) {
return self.scrollNode.view
} else {
return result
}
}
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let width = size.width
var bottomInset = bottomInset
if bottomInset < 30.0 {
@ -451,7 +507,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
panelHeight += contentInset
let isLandscape = size.width > size.height
let displayCaption = !self.textNode.isHidden && !isLandscape
let displayCaption: Bool
if case .compact = metrics.widthClass {
displayCaption = !self.textNode.isHidden && !isLandscape
} else {
displayCaption = !self.textNode.isHidden
}
var textFrame = CGRect()
if !self.textNode.isHidden {
@ -459,10 +520,55 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
let topInset: CGFloat = 8.0
let textBottomInset: CGFloat = 8.0
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude))
var textOffset: CGFloat = 0.0
if displayCaption {
panelHeight += textSize.height + topInset + textBottomInset
var visibleTextHeight = textSize.height
if visibleTextHeight > 100.0 {
visibleTextHeight = 80.0
self.scrollNode.view.isScrollEnabled = true
} else {
self.scrollNode.view.isScrollEnabled = false
}
let visibleTextPanelHeight = visibleTextHeight + topInset + textBottomInset
let scrollViewContentSize = CGSize(width: width, height: textSize.height + topInset + textBottomInset)
if self.scrollNode.view.contentSize != scrollViewContentSize {
self.scrollNode.view.contentSize = scrollViewContentSize
}
let scrollNodeFrame = CGRect(x: 0.0, y: 0.0, width: width, height: visibleTextPanelHeight)
if self.scrollNode.frame != scrollNodeFrame {
self.scrollNode.frame = scrollNodeFrame
}
textOffset = min(400.0, self.scrollNode.view.contentOffset.y)
panelHeight = max(0.0, panelHeight + visibleTextPanelHeight + textOffset)
if self.scrollNode.view.isScrollEnabled {
if self.scrollWrapperNode.layer.mask == nil {
let maskImage = captionMaskImage!
let maskLayer = CALayer()
maskLayer.contents = maskImage.cgImage
maskLayer.contentsScale = maskImage.scale
maskLayer.contentsCenter = CGRect(x: 0.0, y: 0.0, width: 1.0, height: (maskImage.size.height - 16.0) / maskImage.size.height)
self.scrollWrapperNode.layer.mask = maskLayer
}
} else {
self.scrollWrapperNode.layer.mask = nil
}
let scrollWrapperNodeFrame = CGRect(x: 0.0, y: 0.0, width: width, height: max(0.0, visibleTextPanelHeight + textOffset))
if self.scrollWrapperNode.frame != scrollWrapperNodeFrame {
self.scrollWrapperNode.frame = scrollWrapperNodeFrame
self.scrollWrapperNode.layer.mask?.frame = self.scrollWrapperNode.bounds //.offsetBy(dx: 0.0, dy: textOffset)
self.scrollWrapperNode.layer.mask?.removeAllAnimations()
}
}
textFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + textOffset), size: textSize)
if self.textNode.frame != textFrame {
self.textNode.frame = textFrame
}
textFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: textSize)
}
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
@ -484,8 +590,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
scrubberView.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset)
transition.updateFrame(layer: scrubberView.layer, frame: scrubberFrame)
}
self.textNode.frame = textFrame
transition.updateAlpha(node: self.textNode, alpha: displayCaption ? 1.0 : 0.0)
self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
@ -524,8 +628,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
scrubberView.alpha = 1.0
scrubberView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight))
self.textNode.alpha = 1.0
transition.animatePositionAdditive(node: self.scrollWrapperNode, offset: CGPoint(x: 0.0, y: self.bounds.height - fromHeight))
self.scrollWrapperNode.alpha = 1.0
self.dateNode.alpha = 1.0
self.authorNameNode.alpha = 1.0
self.deleteButton.alpha = 1.0
@ -534,7 +638,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
self.forwardButton.alpha = 1.0
self.statusNode.alpha = 1.0
self.playbackControlButton.alpha = 1.0
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
self.scrollWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
}
override func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
@ -546,8 +650,8 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
scrubberView.alpha = 0.0
scrubberView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15)
}
transition.updateFrame(node: self.textNode, frame: self.textNode.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight))
self.textNode.alpha = 0.0
transition.updateFrame(node: self.scrollWrapperNode, frame: self.scrollWrapperNode.frame.offsetBy(dx: 0.0, dy: self.bounds.height - toHeight))
self.scrollWrapperNode.alpha = 0.0
self.dateNode.alpha = 0.0
self.authorNameNode.alpha = 0.0
self.deleteButton.alpha = 0.0
@ -556,7 +660,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode {
self.forwardButton.alpha = 0.0
self.statusNode.alpha = 0.0
self.playbackControlButton.alpha = 0.0
self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { _ in
self.scrollWrapperNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { _ in
completion()
})
}

View File

@ -1612,6 +1612,35 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
switch recognizer.state {
case .ended:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
var mediaMessage: Message?
var forceOpen = false
if let item = self.item {
for media in item.message.media {
if let file = media as? TelegramMediaFile, file.duration != nil {
mediaMessage = item.message
}
}
var forceOpen = false
if mediaMessage == nil {
for attribute in item.message.attributes {
if let attribute = attribute as? ReplyMessageAttribute {
if let replyMessage = item.message.associatedMessages[attribute.messageId] {
for media in replyMessage.media {
if let file = media as? TelegramMediaFile, file.duration != nil {
mediaMessage = replyMessage
forceOpen = true
break
}
}
}
}
}
}
if mediaMessage == nil {
mediaMessage = item.message
}
}
switch gesture {
case .tap:
if let avatarNode = self.accessoryItemNode as? ChatMessageAvatarAccessoryItemNode, avatarNode.frame.contains(location) {
@ -1729,33 +1758,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
break loop
case let .timecode(timecode, _):
foundTapAction = true
if let item = self.item {
var messageId: MessageId?
for media in item.message.media {
if let file = media as? TelegramMediaFile, file.duration != nil {
messageId = item.message.id
}
}
if messageId == nil {
for attribute in item.message.attributes {
if let attribute = attribute as? ReplyMessageAttribute {
if let replyMessage = item.message.associatedMessages[attribute.messageId] {
for media in replyMessage.media {
if let file = media as? TelegramMediaFile, file.duration != nil {
messageId = replyMessage.id
break
}
}
}
}
}
}
if messageId == nil {
messageId = item.message.id
}
if let messageId = messageId {
item.controllerInteraction.seekToTimecode(messageId, timecode)
}
if let item = self.item, let mediaMessage = mediaMessage {
item.controllerInteraction.seekToTimecode(mediaMessage, timecode, forceOpen)
}
break loop
}
@ -1765,7 +1769,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
}
case .longTap, .doubleTap:
if let item = self.item, self.backgroundNode.frame.contains(location) {
let messageId = item.message.id
let message = item.message
var foundTapAction = false
var tapMessage: Message? = item.content.firstMessage
@ -1783,23 +1787,23 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
break
case let .url(url, _):
foundTapAction = true
item.controllerInteraction.longTap(.url(url), messageId)
item.controllerInteraction.longTap(.url(url), message)
break loop
case let .peerMention(peerId, mention):
foundTapAction = true
item.controllerInteraction.longTap(.peerMention(peerId, mention), messageId)
item.controllerInteraction.longTap(.peerMention(peerId, mention), message)
break loop
case let .textMention(name):
foundTapAction = true
item.controllerInteraction.longTap(.mention(name), messageId)
item.controllerInteraction.longTap(.mention(name), message)
break loop
case let .botCommand(command):
foundTapAction = true
item.controllerInteraction.longTap(.command(command), messageId)
item.controllerInteraction.longTap(.command(command), message)
break loop
case let .hashtag(_, hashtag):
foundTapAction = true
item.controllerInteraction.longTap(.hashtag(hashtag), messageId)
item.controllerInteraction.longTap(.hashtag(hashtag), message)
break loop
case .instantPage:
break
@ -1812,7 +1816,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
break
case let .timecode(timecode, text):
foundTapAction = true
item.controllerInteraction.longTap(.timecode(timecode, text), messageId)
if let mediaMessage = mediaMessage {
item.controllerInteraction.longTap(.timecode(timecode, text), mediaMessage)
}
break loop
}
}

View File

@ -757,7 +757,7 @@ public class ChatMessageItemView: ListViewItemNode {
if let item = self.item {
switch button.action {
case let .url(url):
item.controllerInteraction.longTap(.url(url), item.message.id)
item.controllerInteraction.longTap(.url(url), item.message)
default:
break
}

View File

@ -220,7 +220,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, presentController: { _, _ in
}, navigationController: { [weak self] in
return self?.getNavigationController()
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { [weak self] action, messageId in
}, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { [weak self] action, message in
if let strongSelf = self {
switch action {
case let .url(url):
@ -348,7 +348,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
])])
strongSelf.presentController(actionSheet, nil)
case let .timecode(timecode, text):
guard let messageId = messageId else {
guard let message = message else {
return
}
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
@ -357,7 +357,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogOpen, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.controllerInteraction?.seekToTimecode(messageId, timecode)
strongSelf.controllerInteraction?.seekToTimecode(message, timecode, true)
}
}),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in
@ -387,7 +387,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
strongSelf.context.sharedContext.applicationBindings.openAppStorePage()
}
}, displayMessageTooltip: { _, _, _, _ in
}, seekToTimecode: { _, _ in
}, seekToTimecode: { _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings,

View File

@ -337,6 +337,9 @@ public extension DeviceContactExtendedData {
value.postalCode = address.postcode
return CNLabeledValue<CNPostalAddress>(label: address.label, value: value)
})
if let birthdayDate = self.birthdayDate {
contact.birthday = Calendar(identifier: .gregorian).dateComponents([.day, .month, .year], from: birthdayDate)
}
return contact
}

View File

@ -614,7 +614,8 @@ private func deviceContactInfoEntries(account: Account, presentationData: Presen
if let birthday = contactData.birthdayDate {
let dateText: String
let calendar = Calendar(identifier: .gregorian)
let components = calendar.dateComponents(Set([.era, .year, .month, .day]), from: birthday)
var components = calendar.dateComponents(Set([.era, .year, .month, .day]), from: birthday)
components.hour = 12
if let year = components.year, year > 1 {
dateText = stringForDate(timestamp: Int32(birthday.timeIntervalSince1970), strings: presentationData.strings)
} else {

View File

@ -19,7 +19,7 @@ open class GalleryFooterContentNode: ASDisplayNode {
var requestLayout: ((ContainedViewLayoutTransition) -> Void)?
var controllerInteraction: GalleryControllerInteraction?
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
return 0.0
}

View File

@ -45,7 +45,7 @@ final class GalleryFooterNode: ASDisplayNode {
var backgroundHeight: CGFloat = 0.0
if let footerContentNode = self.currentFooterContentNode {
backgroundHeight = footerContentNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, contentInset: thumbnailPanelHeight, transition: transition)
backgroundHeight = footerContentNode.updateLayout(size: layout.size, metrics: layout.metrics, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, contentInset: thumbnailPanelHeight, transition: transition)
transition.updateFrame(node: footerContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight), size: CGSize(width: layout.size.width, height: backgroundHeight)))
if let removeCurrentFooterContentNode = removeCurrentFooterContentNode {
let contentTransition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)

View File

@ -111,8 +111,8 @@ private func commitEntity(_ utf16: String.UTF16View, _ type: CurrentEntityType,
entityType = .Custom(type: ApplicationSpecificEntityType.Timecode)
}
if case .timecode = type, let mediaDuration = mediaDuration, let timecode = parseTimecodeString(String(utf16[range])) {
if timecode <= mediaDuration {
if case .timecode = type {
if let mediaDuration = mediaDuration, let timecode = parseTimecodeString(String(utf16[range])), timecode <= mediaDuration {
entities.append(MessageTextEntity(range: indexRange, type: entityType))
}
} else {

View File

@ -99,7 +99,7 @@ final class InstantPageGalleryFooterContentNode: GalleryFooterContentNode {
self.actionButton.isHidden = shareMedia == nil
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let width = size.width
var panelHeight: CGFloat = 44.0 + bottomInset + contentInset
if !self.textNode.isHidden {

View File

@ -432,7 +432,7 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite
} else if case .generic = item.mode, !servicePeer {
let presence = (item.presence as? TelegramUserPresence) ?? TelegramUserPresence(status: .none, lastActivity: 0)
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
let (string, activity) = stringAndActivityForUserPresence(strings: item.strings, dateTimeFormat: item.dateTimeFormat, presence: presence, relativeTo: Int32(timestamp))
let (string, activity) = stringAndActivityForUserPresence(strings: item.strings, dateTimeFormat: item.dateTimeFormat, presence: presence, relativeTo: Int32(timestamp), expanded: true)
statusText = string
if activity {
statusColor = item.theme.list.itemAccentColor

View File

@ -6,7 +6,7 @@ import SwiftSignalKit
import Postbox
import TelegramCore
func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: MessageMediaEditingOptions?, saveEditedPhotos: Bool, allowGrouping: Bool, theme: PresentationTheme, strings: PresentationStrings, parentController: LegacyController, recentlyUsedInlineBots: [Peer], openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, sendMessagesWithSignals: @escaping ([Any]?) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void) -> TGMenuSheetController {
func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions: MessageMediaEditingOptions?, saveEditedPhotos: Bool, allowGrouping: Bool, theme: PresentationTheme, strings: PresentationStrings, parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: String, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, sendMessagesWithSignals: @escaping ([Any]?) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void) -> TGMenuSheetController {
let isSecretChat = peer.id.namespace == Namespaces.Peer.SecretChat
let controller = TGMenuSheetController(context: parentController.context, dark: false)!
@ -58,6 +58,7 @@ func legacyAttachmentMenu(context: AccountContext, peer: Peer, editMediaOptions:
}
};
carouselItem.allowCaptions = true
carouselItem.editingContext.setInitialCaption(initialCaption, entities: [])
itemViews.append(carouselItem)
let galleryItem = TGMenuSheetButtonItemView(title: editing ? strings.Conversation_EditingMessageMediaChange : strings.AttachmentMenu_PhotoOrVideo, type: TGMenuSheetButtonTypeDefault, action: { [weak controller] in

View File

@ -6,7 +6,7 @@ import TelegramCore
import Postbox
import SwiftSignalKit
func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, sendMessagesWithSignals: @escaping ([Any]?) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }) {
func presentedLegacyCamera(context: AccountContext, peer: Peer, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: String, sendMessagesWithSignals: @escaping ([Any]?) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)

View File

@ -13,7 +13,7 @@ func guessMimeTypeByFileExtension(_ ext: String) -> String {
return TGMimeTypeMap.mimeType(forExtension: ext) ?? "application/binary"
}
func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: Peer, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, presentWebSearch: (() -> Void)?) {
func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context: AccountContext, peer: Peer, captionsEnabled: Bool = true, storeCreatedAssets: Bool = true, showFileTooltip: Bool = false, initialCaption: String, presentWebSearch: (() -> Void)?) {
controller.captionsEnabled = captionsEnabled
controller.inhibitDocumentCaptions = false
controller.suggestionContext = legacySuggestionContext(account: context.account, peerId: peer.id)
@ -26,6 +26,8 @@ func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, context:
controller.shouldStoreAssets = storeCreatedAssets
controller.shouldShowFileTipIfNeeded = showFileTooltip
controller.requestSearchController = presentWebSearch
controller.editingContext.setInitialCaption(initialCaption, entities: [])
}
func legacyAssetPicker(context: AccountContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: Peer?, saveEditedPhotos: Bool, allowGrouping: Bool) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> {

View File

@ -543,7 +543,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
case .tap, .longTap:
if let item = self.item, let url = self.urlAtPoint(location) {
if case .longTap = gesture {
item.controllerInteraction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message.id)
item.controllerInteraction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message)
} else if url == self.currentPrimaryUrl {
if !item.controllerInteraction.openMessage(item.message, .default) {
item.controllerInteraction.openUrl(url, false, false)

View File

@ -73,7 +73,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec
}, requestSelectMessagePollOption: { _, _ in
}, openAppStorePage: {
}, displayMessageTooltip: { _, _, _, _ in
}, seekToTimecode: { _, _ in
}, seekToTimecode: { _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -254,7 +254,7 @@ public class PeerMediaCollectionController: TelegramController {
}, requestSelectMessagePollOption: { _, _ in
}, openAppStorePage: {
}, displayMessageTooltip: { _, _, _, _ in
}, seekToTimecode: { _, _ in
}, seekToTimecode: { _, _, _ in
}, requestMessageUpdate: { _ in
}, cancelInteractiveKeyboardGestures: {
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings,

View File

@ -276,7 +276,7 @@ func stringForRelativeLiveLocationUpdateTimestamp(strings: PresentationStrings,
}
}
func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, presence: TelegramUserPresence, relativeTo timestamp: Int32) -> (String, Bool) {
func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, presence: TelegramUserPresence, relativeTo timestamp: Int32, expanded: Bool = false) -> (String, Bool) {
switch presence.status {
case .none:
return (strings.LastSeen_Offline, false)
@ -287,7 +287,7 @@ func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeForm
let difference = timestamp - statusTimestamp
if difference < 60 {
return (strings.LastSeen_JustNow, false)
} else if difference < 60 * 60 {
} else if difference < 60 * 60 && !expanded {
let minutes = difference / 60
return (strings.LastSeen_MinutesAgo(minutes), false)
} else {
@ -307,8 +307,12 @@ func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeForm
if dayDifference == 0 || dayDifference == -1 {
let day: RelativeTimestampFormatDay
if dayDifference == 0 {
let minutes = difference / (60 * 60)
return (strings.LastSeen_HoursAgo(minutes), false)
if expanded {
day = .today
} else {
let minutes = difference / (60 * 60)
return (strings.LastSeen_HoursAgo(minutes), false)
}
} else {
day = .yesterday
}

View File

@ -31,7 +31,7 @@ final class SecretMediaPreviewFooterContentNode: GalleryFooterContentNode {
}
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let width = size.width
let panelHeight: CGFloat = 44.0 + bottomInset

View File

@ -86,7 +86,7 @@ final class SecureIdDocumentGalleryFooterContentNode: GalleryFooterContentNode {
}
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let width = size.width
var panelHeight: CGFloat = 44.0 + bottomInset
panelHeight += contentInset

View File

@ -160,7 +160,6 @@ final class SettingsSearchInteraction {
private enum SettingsSearchEntryStableId: Hashable {
case result(SettingsSearchableItemId)
case faq(String)
}
private enum SettingsSearchEntry: Comparable, Identifiable {
@ -302,7 +301,6 @@ private func preparedSettingsSearchContainerRecentTransition(from fromEntries: [
private final class SettingsSearchContainerNode: SearchDisplayControllerContentNode {
private let dimNode: ASDisplayNode
private let listNode: ListView
private let recentListNode: ListView
@ -323,9 +321,6 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationDataPromise = Promise(self.presentationData)
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5)
self.listNode = ListView()
self.listNode.backgroundColor = self.presentationData.theme.chatList.backgroundColor
self.listNode.isHidden = true
@ -338,7 +333,6 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN
self.backgroundColor = self.presentationData.theme.chatList.backgroundColor
//self.addSubnode(self.dimNode)
self.addSubnode(self.recentListNode)
self.addSubnode(self.listNode)
@ -407,7 +401,10 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN
var result: [SettingsSearchableItem] = []
for itemId in recentItems {
if let searchItem = searchableItemsMap[itemId] {
result.append(searchItem)
if case let .language(id) = searchItem.id, id > 0 {
} else {
result.append(searchItem)
}
}
}
return result
@ -489,12 +486,6 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN
self.presentationDataDisposable?.dispose()
}
override func didLoad() {
super.didLoad()
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
}
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
self.listNode.backgroundColor = theme.chatList.backgroundColor
self.recentListNode.backgroundColor = theme.chatList.backgroundColor
@ -529,7 +520,6 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN
let isSearching = transition.isSearching
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
self?.listNode.isHidden = !isSearching
self?.dimNode.isHidden = isSearching
})
}
}
@ -563,10 +553,7 @@ private final class SettingsSearchContainerNode: SearchDisplayControllerContentN
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
let topInset = navigationBarHeight
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: layout.size.width, height: layout.size.height - topInset)))
var duration: Double = 0.0
var curve: UInt = 0
switch transition {
@ -723,7 +710,6 @@ private final class SettingsSearchItemNode: ItemListControllerSearchNode {
}
override func queryUpdated(_ query: String) {
//self.containerNode.searchTextUpdated(text: query)
}
override func updateLayout(layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {

View File

@ -747,17 +747,22 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
}
let localizationPreferencesKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.localizationListState]))
let localizations = context.account.postbox.combinedView(keys: [localizationPreferencesKey])
|> map { view -> [LocalizationInfo] in
let localizations = combineLatest(context.account.postbox.combinedView(keys: [localizationPreferencesKey]), context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.localizationSettings]))
|> map { view, sharedData -> [LocalizationInfo] in
if let localizationListState = (view.views[localizationPreferencesKey] as? PreferencesView)?.values[PreferencesKeys.localizationListState] as? LocalizationListState, !localizationListState.availableOfficialLocalizations.isEmpty {
var existingIds = Set<String>()
let availableSavedLocalizations = localizationListState.availableSavedLocalizations.filter({ info in !localizationListState.availableOfficialLocalizations.contains(where: { $0.languageCode == info.languageCode }) })
var activeLanguageCode: String?
if let localizationSettings = sharedData.entries[SharedDataKeys.localizationSettings] as? LocalizationSettings {
activeLanguageCode = localizationSettings.primaryComponent.languageCode
}
var localizationItems: [LocalizationInfo] = []
if !availableSavedLocalizations.isEmpty {
for info in availableSavedLocalizations {
if existingIds.contains(info.languageCode) {
if existingIds.contains(info.languageCode) || info.languageCode == activeLanguageCode {
continue
}
existingIds.insert(info.languageCode)
@ -765,7 +770,7 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
}
}
for info in localizationListState.availableOfficialLocalizations {
if existingIds.contains(info.languageCode) {
if existingIds.contains(info.languageCode) || info.languageCode == activeLanguageCode {
continue
}
existingIds.insert(info.languageCode)

View File

@ -4,6 +4,8 @@ import Postbox
import TelegramCore
import SwiftSignalKit
import TelegramUIPrivateModule
public final class TelegramRootController: NavigationController {
private let context: AccountContext
@ -81,8 +83,52 @@ public final class TelegramRootController: NavigationController {
self.accountSettingsController = accountSettingsController
self.rootTabController = tabBarController
self.pushViewController(tabBarController, animated: false)
// guard let controller = self.viewControllers.last as? ViewController else {
// return
// }
//
// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
// let wrapperNode = ASDisplayNode()
// let bounds = controller.displayNode.bounds
// wrapperNode.frame = bounds
// wrapperNode.backgroundColor = .gray
// //controller.displayNode.addSubnode(wrapperNode)
//
// let label = TGMarqLabel(frame: CGRect())
// label.textColor = .white
// label.font = Font.regular(28.0)
// label.scrollDuration = 15.0
// label.fadeLength = 25.0
// label.trailingBuffer = 60.0
// label.animationDelay = 2.0
// label.text = "Lorem ipsum dolor sir amet, consecteur"
// label.sizeToFit()
// label.frame = CGRect(x: bounds.width / 2.0 - 100.0, y: 100.0, width: 200.0, height: label.frame.height)
// //wrapperNode.view.addSubview(label)
//
// let data = testLineChartData()
// let node = LineChartContainerNode(data: data)
// node.frame = CGRect(x: 0.0, y: 100.0, width: bounds.width, height: 280.0)
// node.updateLayout(size: node.frame.size)
// wrapperNode.addSubnode(node)
//
// self.wNode = wrapperNode
//
// let gesture = UITapGestureRecognizer(target: self, action: #selector(self.closeIt))
// wrapperNode.view.addGestureRecognizer(gesture)
// }
}
@objc func closeIt() {
self.wNode?.removeFromSupernode()
}
private var wNode: ASDisplayNode?
public func updateRootControllers(showCallsTab: Bool) {
guard let rootTabController = self.rootTabController else {
return

View File

@ -40,22 +40,11 @@ final class WebSearchGalleryFooterContentNode: GalleryFooterContentNode {
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let width = size.width
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))