From dfa4e6692a5a683cd9b861828ad51ce67f9aed0b Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 19 Oct 2018 20:32:02 +0300 Subject: [PATCH] Improved support for autosaving incoming photos Improved perceived sticker loading time --- TelegramUI.xcodeproj/project.pbxproj | 4 + .../AutomaticMediaDownloadSettings.swift | 17 +-- TelegramUI/ChatController.swift | 56 ++++++-- TelegramUI/ChatControllerInteraction.swift | 4 +- TelegramUI/ChatListController.swift | 2 +- TelegramUI/ChatListItem.swift | 9 +- TelegramUI/ChatMediaInputNode.swift | 8 +- .../ChatMediaInputStickerGridItem.swift | 6 +- .../ChatMediaInputStickerPackItem.swift | 2 +- TelegramUI/ChatMediaInputTrendingPane.swift | 2 +- TelegramUI/ChatMessageActionItemNode.swift | 2 +- .../ChatMessageAttachedContentNode.swift | 8 +- .../ChatMessageInteractiveMediaNode.swift | 20 +-- .../ChatMessageMediaBubbleContentNode.swift | 2 +- TelegramUI/ChatMessageStickerItemNode.swift | 4 +- .../ChatRecentActionsControllerNode.swift | 2 +- .../DataAndStorageSettingsController.swift | 28 ++-- TelegramUI/FastBlur.h | 1 + TelegramUI/FastBlur.m | 23 ++++ TelegramUI/FetchManager.swift | 16 +-- TelegramUI/FetchMediaUtils.swift | 8 +- TelegramUI/HorizontalStickerGridItem.swift | 2 +- ...rizontalStickersChatContextPanelNode.swift | 4 +- TelegramUI/InstantPageImageNode.swift | 2 +- TelegramUI/MediaInputPaneTrendingItem.swift | 2 +- TelegramUI/OverlayPlayerControllerNode.swift | 2 +- .../PeerMediaCollectionController.swift | 2 +- TelegramUI/PhotoResources.swift | 6 +- TelegramUI/SaveIncomingMediaController.swift | 128 ++++++++++++++++++ TelegramUI/StickerPackPreviewGridItem.swift | 3 +- .../StickerPaneSearchContainerNode.swift | 2 +- TelegramUI/StickerPaneSearchGlobaltem.swift | 2 +- TelegramUI/StickerPaneSearchStickerItem.swift | 2 +- TelegramUI/StickerResources.swift | 75 ++++++++-- TelegramUI/StoreDownloadedMedia.swift | 63 ++++++--- TelegramUI/ThemeSettingsChatPreviewItem.swift | 2 +- 36 files changed, 396 insertions(+), 125 deletions(-) create mode 100644 TelegramUI/SaveIncomingMediaController.swift diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index bcd37b117b..534b79fc40 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -293,6 +293,7 @@ D09F9DCF20768DAF00DB4DE1 /* SecureIdLocalResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09F9DCE20768DAF00DB4DE1 /* SecureIdLocalResource.swift */; }; D0A24D281F92C27100584D24 /* DefaultDarkAccentPresentationTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A24D271F92C27100584D24 /* DefaultDarkAccentPresentationTheme.swift */; }; D0A723541FC3B40E0094D167 /* RadialCheckContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A723531FC3B40E0094D167 /* RadialCheckContentNode.swift */; }; + D0A8998D217A294100759EE6 /* SaveIncomingMediaController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A8998C217A294100759EE6 /* SaveIncomingMediaController.swift */; }; D0A8BBA11F61EE83000F03FD /* UniversalVideoCalleryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A8BBA01F61EE83000F03FD /* UniversalVideoCalleryItem.swift */; }; D0AA29AE1F72770D00C050AC /* ChatListItemStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA29AD1F72770D00C050AC /* ChatListItemStrings.swift */; }; D0AA840C1FEB2BA3005C6E91 /* OverlayPlayerControlsNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA840B1FEB2BA3005C6E91 /* OverlayPlayerControlsNode.swift */; }; @@ -1617,6 +1618,7 @@ D0A24D271F92C27100584D24 /* DefaultDarkAccentPresentationTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultDarkAccentPresentationTheme.swift; sourceTree = ""; }; D0A723531FC3B40E0094D167 /* RadialCheckContentNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadialCheckContentNode.swift; sourceTree = ""; }; D0A749961E3AA25200AD786E /* NotificationSoundSelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationSoundSelection.swift; sourceTree = ""; }; + D0A8998C217A294100759EE6 /* SaveIncomingMediaController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveIncomingMediaController.swift; sourceTree = ""; }; D0A8BBA01F61EE83000F03FD /* UniversalVideoCalleryItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniversalVideoCalleryItem.swift; sourceTree = ""; }; D0AA29AD1F72770D00C050AC /* ChatListItemStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListItemStrings.swift; sourceTree = ""; }; D0AA840B1FEB2BA3005C6E91 /* OverlayPlayerControlsNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayPlayerControlsNode.swift; sourceTree = ""; }; @@ -3333,6 +3335,7 @@ D0185E89208A01AF005E1A6C /* ProxySettingsActionItem.swift */, D0185E8B208A025A005E1A6C /* ProxySettingsServerItem.swift */, D07E413C208A494D00FCA8F0 /* ProxyServerActionSheetController.swift */, + D0A8998C217A294100759EE6 /* SaveIncomingMediaController.swift */, ); name = "Data and Storage"; sourceTree = ""; @@ -4860,6 +4863,7 @@ D0E9BA371F05585000F079A4 /* STPPhoneNumberValidator.m in Sources */, D069F5D0212700B90000565A /* StickerPanePeerSpecificSetupGridItem.swift in Sources */, D0EC6D041EB9F58800EBF1C3 /* opusenc.m in Sources */, + D0A8998D217A294100759EE6 /* SaveIncomingMediaController.swift in Sources */, D0185E8A208A01AF005E1A6C /* ProxySettingsActionItem.swift in Sources */, D0EC6D051EB9F58800EBF1C3 /* picture.c in Sources */, D0EC6D061EB9F58800EBF1C3 /* wav_io.c in Sources */, diff --git a/TelegramUI/AutomaticMediaDownloadSettings.swift b/TelegramUI/AutomaticMediaDownloadSettings.swift index 206262e736..6bf4e20150 100644 --- a/TelegramUI/AutomaticMediaDownloadSettings.swift +++ b/TelegramUI/AutomaticMediaDownloadSettings.swift @@ -33,13 +33,15 @@ public struct AutomaticMediaDownloadCategories: Equatable, PostboxCoding { public var file: AutomaticMediaDownloadCategory public var voiceMessage: AutomaticMediaDownloadCategory public var videoMessage: AutomaticMediaDownloadCategory + public var saveDownloadedPhotos: Bool - public init(photo: AutomaticMediaDownloadCategory, video: AutomaticMediaDownloadCategory, file: AutomaticMediaDownloadCategory, voiceMessage: AutomaticMediaDownloadCategory, videoMessage: AutomaticMediaDownloadCategory) { + public init(photo: AutomaticMediaDownloadCategory, video: AutomaticMediaDownloadCategory, file: AutomaticMediaDownloadCategory, voiceMessage: AutomaticMediaDownloadCategory, videoMessage: AutomaticMediaDownloadCategory, saveDownloadedPhotos: Bool) { self.photo = photo self.video = video self.file = file self.voiceMessage = voiceMessage self.videoMessage = videoMessage + self.saveDownloadedPhotos = saveDownloadedPhotos } public init(decoder: PostboxDecoder) { @@ -48,6 +50,7 @@ public struct AutomaticMediaDownloadCategories: Equatable, PostboxCoding { self.file = decoder.decodeObjectForKey("file", decoder: AutomaticMediaDownloadCategory.init(decoder:)) as! AutomaticMediaDownloadCategory self.voiceMessage = decoder.decodeObjectForKey("voiceMessage", decoder: AutomaticMediaDownloadCategory.init(decoder:)) as! AutomaticMediaDownloadCategory self.videoMessage = decoder.decodeObjectForKey("videoMessage", decoder: AutomaticMediaDownloadCategory.init(decoder:)) as! AutomaticMediaDownloadCategory + self.saveDownloadedPhotos = decoder.decodeInt32ForKey("saveDownloadedPhotos", orElse: 0) != 0 } public func encode(_ encoder: PostboxEncoder) { @@ -56,6 +59,7 @@ public struct AutomaticMediaDownloadCategories: Equatable, PostboxCoding { encoder.encodeObject(self.file, forKey: "file") encoder.encodeObject(self.voiceMessage, forKey: "voiceMessage") encoder.encodeObject(self.videoMessage, forKey: "videoMessage") + encoder.encodeInt32(self.saveDownloadedPhotos ? 1 : 0, forKey: "saveDownloadedPhotos") } } @@ -91,7 +95,6 @@ public struct AutomaticMediaDownloadSettings: PreferencesEntry, Equatable { public var masterEnabled: Bool public var peers: AutomaticMediaDownloadPeers public var autoplayGifs: Bool - public var saveIncomingPhotos: Bool public static var defaultSettings: AutomaticMediaDownloadSettings { let defaultCategory = AutomaticMediaDownloadCategories( @@ -99,35 +102,33 @@ public struct AutomaticMediaDownloadSettings: PreferencesEntry, Equatable { video: AutomaticMediaDownloadCategory(cellular: false, wifi: false, sizeLimit: 1 * 1024 * 1024), file: AutomaticMediaDownloadCategory(cellular: false, wifi: false, sizeLimit: 1 * 1024 * 1024), voiceMessage: AutomaticMediaDownloadCategory(cellular: true, wifi: true, sizeLimit: 1 * 1024 * 1024), - videoMessage: AutomaticMediaDownloadCategory(cellular: true, wifi: true, sizeLimit: 1 * 1024 * 1024) + videoMessage: AutomaticMediaDownloadCategory(cellular: true, wifi: true, sizeLimit: 1 * 1024 * 1024), + saveDownloadedPhotos: false ) return AutomaticMediaDownloadSettings(masterEnabled: true, peers: AutomaticMediaDownloadPeers( contacts: defaultCategory, otherPrivate: defaultCategory, groups: defaultCategory, channels: defaultCategory - ), autoplayGifs: true, saveIncomingPhotos: false) + ), autoplayGifs: true) } - init(masterEnabled: Bool, peers: AutomaticMediaDownloadPeers, autoplayGifs: Bool, saveIncomingPhotos: Bool) { + init(masterEnabled: Bool, peers: AutomaticMediaDownloadPeers, autoplayGifs: Bool) { self.masterEnabled = masterEnabled self.peers = peers self.autoplayGifs = autoplayGifs - self.saveIncomingPhotos = saveIncomingPhotos } public init(decoder: PostboxDecoder) { self.masterEnabled = decoder.decodeInt32ForKey("masterEnabled", orElse: 1) != 0 self.peers = (decoder.decodeObjectForKey("peers", decoder: AutomaticMediaDownloadPeers.init(decoder:)) as? AutomaticMediaDownloadPeers) ?? AutomaticMediaDownloadSettings.defaultSettings.peers self.autoplayGifs = decoder.decodeInt32ForKey("autoplayGifs", orElse: 1) != 0 - self.saveIncomingPhotos = decoder.decodeInt32ForKey("siph", orElse: 0) != 0 } public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.masterEnabled ? 1 : 0, forKey: "masterEnabled") encoder.encodeObject(self.peers, forKey: "peers") encoder.encodeInt32(self.autoplayGifs ? 1 : 0, forKey: "autoplayGifs") - encoder.encodeInt32(self.saveIncomingPhotos ? 1 : 0, forKey: "siph") } public func isEqual(to: PreferencesEntry) -> Bool { diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 3cef8f3763..d7f60e491a 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -68,7 +68,27 @@ private func isTopmostChatController(_ controller: ChatController) -> Bool { let ChatControllerCount = Atomic(value: 0) -public final class ChatController: TelegramController, KeyShortcutResponder, UIViewControllerPreviewingDelegate, UIDropInteractionDelegate { +@available(iOSApplicationExtension 9.0, *) +private final class ChatControllerPreviewingDelegate: NSObject, UIViewControllerPreviewingDelegate { + private weak var target: (NSObject & UIViewControllerPreviewingDelegate)? + + init(target: NSObject & UIViewControllerPreviewingDelegate) { + self.target = target + + super.init() + } + + public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { + return self.target?.previewingContext(previewingContext, viewControllerForLocation: location) + } + + public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { + self.target?.previewingContext(previewingContext, commit: viewControllerToCommit) + } +} + +public final class ChatController: TelegramController, KeyShortcutResponder, UIDropInteractionDelegate, UIViewControllerPreviewingDelegate { + private var previewingDelegate: AnyObject? private var validLayout: ContainerViewLayout? public var peekActions: ChatControllerPeekActions = .standard @@ -316,7 +336,7 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UIV }, enqueueMessage: { message in self?.sendMessages([message]) }, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference in - self?.controllerInteraction?.sendSticker(fileReference) + self?.controllerInteraction?.sendSticker(fileReference, false) } : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in if let strongSelf = self { strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in @@ -451,19 +471,27 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UIV attributes.append(TextEntitiesMessageAttribute(entities: entities)) } strongSelf.sendMessages([.message(text: text, attributes: attributes, mediaReference: nil, replyToMessageId: strongSelf.presentationInterfaceState.interfaceState.replyMessageId, localGroupingKey: nil)]) - }, sendSticker: { [weak self] fileReference in + }, sendSticker: { [weak self] fileReference, clearInput in if let strongSelf = self { strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({ if let strongSelf = self { - strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { - $0.withUpdatedReplyMessageId(nil) + strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in + var current = current + current = current.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + interfaceState = interfaceState.withUpdatedReplyMessageId(nil) + if clearInput { + interfaceState = interfaceState.withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString())) + } + return interfaceState }.updatedInputMode { current in if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil { return .media(mode: mode, expanded: nil) } return current } + + return current }) } }) @@ -2811,11 +2839,19 @@ public final class ChatController: TelegramController, KeyShortcutResponder, UIV if !self.didSetup3dTouch { self.didSetup3dTouch = true if #available(iOSApplicationExtension 9.0, *) { - self.registerForPreviewing(with: self, sourceView: self.chatDisplayNode.historyNodeContainer.view, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) - if case .peer = self.chatLocation, let buttonView = (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.view { - self.registerForPreviewing(with: self, sourceView: buttonView, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) + let previewingDelegate: ChatControllerPreviewingDelegate + if let current = self.previewingDelegate as? ChatControllerPreviewingDelegate { + previewingDelegate = current + } else { + previewingDelegate = ChatControllerPreviewingDelegate(target: self) + self.previewingDelegate = previewingDelegate } - self.registerForPreviewing(with: self, sourceView: self.chatDisplayNode.historyNodeContainer.view, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) + + //self.registerForPreviewing(with: previewingDelegate, sourceView: self.chatDisplayNode.historyNodeContainer.view, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) + if case .peer = self.chatLocation, let buttonView = (self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.avatarNode.view { + //self.registerForPreviewing(with: previewingDelegate, sourceView: buttonView, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) + } + //self.registerForPreviewing(with: previewingDelegate, sourceView: self.chatDisplayNode.historyNodeContainer.view, theme: PeekControllerTheme(presentationTheme: self.presentationData.theme), onlyNative: true) } if #available(iOSApplicationExtension 11.0, *) { diff --git a/TelegramUI/ChatControllerInteraction.swift b/TelegramUI/ChatControllerInteraction.swift index 3f10e350aa..5299618d5e 100644 --- a/TelegramUI/ChatControllerInteraction.swift +++ b/TelegramUI/ChatControllerInteraction.swift @@ -46,7 +46,7 @@ public final class ChatControllerInteraction { let clickThroughMessage: () -> Void let toggleMessagesSelection: ([MessageId], Bool) -> Void let sendMessage: (String) -> Void - let sendSticker: (FileMediaReference) -> Void + let sendSticker: (FileMediaReference, Bool) -> Void let sendGif: (FileMediaReference) -> Void let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool) -> Void let activateSwitchInline: (PeerId?, String) -> Void @@ -79,7 +79,7 @@ public final class ChatControllerInteraction { var contextHighlightedState: ChatInterfaceHighlightedState? var automaticMediaDownloadSettings: AutomaticMediaDownloadSettings - init(openMessage: @escaping (Message) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference) -> 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) -> 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) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32)->Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) { + init(openMessage: @escaping (Message) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, 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) -> 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) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32)->Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index 652c60de27..0e8a2fc305 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -187,7 +187,7 @@ public class ChatListController: TelegramController, KeyShortcutResponder, UIVie if count.0 == 0 { strongSelf.tabBarItem.badgeValue = "" } else { - if count.0 > 1000 && false { + if count.0 > 1000 { strongSelf.tabBarItem.badgeValue = "\(count.0 / 1000)K" } else { strongSelf.tabBarItem.badgeValue = "\(count.0)" diff --git a/TelegramUI/ChatListItem.swift b/TelegramUI/ChatListItem.swift index 50f49cb907..e7f4f803f5 100644 --- a/TelegramUI/ChatListItem.swift +++ b/TelegramUI/ChatListItem.swift @@ -596,7 +596,14 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { currentBadgeBackgroundImage = PresentationResourcesChatList.badgeBackgroundActive(item.presentationData.theme) badgeTextColor = theme.unreadBadgeActiveTextColor } - badgeAttributedString = NSAttributedString(string: unreadCount.count > 0 ? "\(unreadCount.count)" : " ", font: badgeFont, textColor: badgeTextColor) + let unreadCountText: String + if unreadCount.count > 1000 { + unreadCountText = "\(unreadCount.count / 1000)K" + } else { + unreadCountText = "\(unreadCount.count)" + } + + badgeAttributedString = NSAttributedString(string: unreadCount.count > 0 ? unreadCountText : " ", font: badgeFont, textColor: badgeTextColor) } } diff --git a/TelegramUI/ChatMediaInputNode.swift b/TelegramUI/ChatMediaInputNode.swift index ca7660ac36..bd591bdcc3 100644 --- a/TelegramUI/ChatMediaInputNode.swift +++ b/TelegramUI/ChatMediaInputNode.swift @@ -741,7 +741,7 @@ final class ChatMediaInputNode: ChatInputNode { menuItems = [ PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { if let strongSelf = self { - strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file)) + strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false) } }), PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { @@ -762,7 +762,7 @@ final class ChatMediaInputNode: ChatInputNode { let controller = StickerPackPreviewController(account: strongSelf.account, stickerPack: packReference, parentNavigationController: strongSelf.controllerInteraction.navigationController()) controller.sendSticker = { file in if let strongSelf = self { - strongSelf.controllerInteraction.sendSticker(file) + strongSelf.controllerInteraction.sendSticker(file, false) } } @@ -823,7 +823,7 @@ final class ChatMediaInputNode: ChatInputNode { menuItems = [ PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { if let strongSelf = self { - strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file)) + strongSelf.controllerInteraction.sendSticker(.standalone(media: item.file), false) } }), PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { @@ -844,7 +844,7 @@ final class ChatMediaInputNode: ChatInputNode { let controller = StickerPackPreviewController(account: strongSelf.account, stickerPack: packReference, parentNavigationController: strongSelf.controllerInteraction.navigationController()) controller.sendSticker = { file in if let strongSelf = self { - strongSelf.controllerInteraction.sendSticker(file) + strongSelf.controllerInteraction.sendSticker(file, false) } } diff --git a/TelegramUI/ChatMediaInputStickerGridItem.swift b/TelegramUI/ChatMediaInputStickerGridItem.swift index 4e4b5f4289..71a65fb5d6 100644 --- a/TelegramUI/ChatMediaInputStickerGridItem.swift +++ b/TelegramUI/ChatMediaInputStickerGridItem.swift @@ -168,7 +168,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem { if let dimensions = stickerItem.file.dimensions { self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true)) - self.stickerFetchedDisposable.set(freeMediaFileInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file)).start()) + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start()) self.currentState = (account, stickerItem, dimensions) self.setNeedsLayout() @@ -183,7 +183,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { super.layout() let bounds = self.bounds - let sideSize: CGFloat = min(75.0 - 6.0, bounds.width) + let sideSize: CGFloat = min(75.0 - 10.0, bounds.width) let boundingSize = CGSize(width: sideSize, height: sideSize) if let (_, _, mediaDimensions) = self.currentState { @@ -198,7 +198,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode { return } if let interfaceInteraction = self.interfaceInteraction, let (_, item, _) = self.currentState, case .ended = recognizer.state { - interfaceInteraction.sendSticker(.standalone(media: item.file)) + interfaceInteraction.sendSticker(.standalone(media: item.file), false) self.imageNode.layer.animateAlpha(from: 0.5, to: 1.0, duration: 1.0) } } diff --git a/TelegramUI/ChatMediaInputStickerPackItem.swift b/TelegramUI/ChatMediaInputStickerPackItem.swift index 22c07c992c..59892a6911 100644 --- a/TelegramUI/ChatMediaInputStickerPackItem.swift +++ b/TelegramUI/ChatMediaInputStickerPackItem.swift @@ -114,7 +114,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode { let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets())) imageApply() self.imageNode.setSignal(chatMessageSticker(account: account, file: item.file, small: true)) - self.stickerFetchedDisposable.set(freeMediaFileInteractiveFetched(account: account, fileReference: stickerPackFileReference(item.file)).start()) + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(item.file), resource: chatMessageStickerResource(file: item.file, small: true)).start()) self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize) } diff --git a/TelegramUI/ChatMediaInputTrendingPane.swift b/TelegramUI/ChatMediaInputTrendingPane.swift index 85ffddd323..377348edb5 100644 --- a/TelegramUI/ChatMediaInputTrendingPane.swift +++ b/TelegramUI/ChatMediaInputTrendingPane.swift @@ -163,7 +163,7 @@ final class ChatMediaInputTrendingPane: ChatMediaInputPane { let controller = StickerPackPreviewController(account: strongSelf.account, stickerPack: .id(id: info.id.id, accessHash: info.accessHash), parentNavigationController: strongSelf.controllerInteraction.navigationController()) controller.sendSticker = { fileReference in if let strongSelf = self { - strongSelf.controllerInteraction.sendSticker(fileReference) + strongSelf.controllerInteraction.sendSticker(fileReference, false) } } strongSelf.controllerInteraction.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) diff --git a/TelegramUI/ChatMessageActionItemNode.swift b/TelegramUI/ChatMessageActionItemNode.swift index 44886a9a83..6c80b25f39 100644 --- a/TelegramUI/ChatMessageActionItemNode.swift +++ b/TelegramUI/ChatMessageActionItemNode.swift @@ -574,7 +574,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { apply() } - strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: item.account, photoReference: .message(message: MessageReference(item.message), media: image), storeToDownloads: false).start()) + strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: item.account, photoReference: .message(message: MessageReference(item.message), media: image), storeToDownloadsPeerType: nil).start()) let updateImageSignal = chatMessagePhoto(postbox: item.account.postbox, photoReference: .message(message: MessageReference(item.message), media: image)) imageNode.setSignal(updateImageSignal) diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index 89a71b7cba..37c53449fb 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -381,12 +381,12 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { contentInstantVideoSizeAndApply = (videoLayout, apply) } else if file.isVideo { let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) - let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) + let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } else if file.isSticker, let _ = file.dimensions { let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: file) - let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) + let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, file, automaticDownload, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } else { @@ -411,7 +411,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } else if let image = media as? TelegramMediaImage { if !flags.contains(.preferMediaInline) { let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: image) - let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, image, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) + let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, image, automaticDownload, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } else if let dimensions = largestImageRepresentation(image.representations)?.dimensions { @@ -423,7 +423,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { } } else if let image = media as? TelegramMediaWebFile { let automaticDownload = shouldDownloadMediaAutomatically(settings: automaticDownloadSettings, peerType: associatedData.automaticDownloadPeerType, networkType: associatedData.automaticDownloadNetworkType, media: image) - let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, image, automaticDownload, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) + let (_, initialImageWidth, refineLayout) = contentImageLayout(account, presentationData.theme.theme, presentationData.strings, message, image, automaticDownload, associatedData.automaticDownloadPeerType, automaticDownloadSettings.autoplayGifs, .constrained(CGSize(width: constrainedSize.width - horizontalInsets.left - horizontalInsets.right, height: constrainedSize.height)), layoutConstants) initialWidth = initialImageWidth + horizontalInsets.left + horizontalInsets.right refineContentImageLayout = refineLayout } diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index 43d5cca637..2e0646a76f 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -125,7 +125,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } } - func asyncLayout() -> (_ account: Account, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: Bool, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition) -> Void))) { + func asyncLayout() -> (_ account: Account, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: Bool, _ peerType: AutomaticMediaDownloadPeerType, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition) -> Void))) { let currentMessage = self.message let currentMedia = self.media let imageLayout = self.imageNode.asyncLayout() @@ -134,7 +134,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { let hasCurrentVideoNode = currentVideoNode != nil let previousAutomaticDownload = self.automaticDownload - return { [weak self] account, theme, strings, message, media, automaticDownload, automaticPlayback, sizeCalculation, layoutConstants in + return { [weak self] account, theme, strings, message, media, automaticDownload, peerType, automaticPlayback, sizeCalculation, layoutConstants in var nativeSize: CGSize let isSecretMedia = message.containsSecretMedia @@ -150,10 +150,10 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } } - var storeToDownloads: Bool = false + var storeToDownloadsPeerType: AutomaticMediaDownloadPeerType? for media in message.media { if media is TelegramMediaImage { - storeToDownloads = true + storeToDownloadsPeerType = peerType } } @@ -264,9 +264,9 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { updatedFetchControls = FetchControls(fetch: { manual in if let strongSelf = self { if !manual { - strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: .message(message: MessageReference(message), media: image), storeToDownloads: storeToDownloads).start()) + strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: .message(message: MessageReference(message), media: image), storeToDownloadsPeerType: storeToDownloadsPeerType).start()) } else if let resource = largestRepresentationForPhoto(image)?.resource { - strongSelf.fetchDisposable.set(messageMediaImageInteractiveFetched(account: account, message: message, image: image, resource: resource, storeToDownloads: storeToDownloads).start()) + strongSelf.fetchDisposable.set(messageMediaImageInteractiveFetched(account: account, message: message, image: image, resource: resource, storeToDownloadsPeerType: storeToDownloadsPeerType).start()) } } }, cancel: { @@ -672,12 +672,12 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { } } - static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ account: Account, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: Bool, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition) -> ChatMessageInteractiveMediaNode))) { + static func asyncLayout(_ node: ChatMessageInteractiveMediaNode?) -> (_ account: Account, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: Bool, _ peerType: AutomaticMediaDownloadPeerType, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition) -> ChatMessageInteractiveMediaNode))) { let currentAsyncLayout = node?.asyncLayout() - return { account, theme, strings, message, media, automaticDownload, automaticPlayback, sizeCalculation, layoutConstants in + return { account, theme, strings, message, media, automaticDownload, peerType, automaticPlayback, sizeCalculation, layoutConstants in var imageNode: ChatMessageInteractiveMediaNode - var imageLayout: (_ account: Account, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: Bool, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition) -> Void))) + var imageLayout: (_ account: Account, _ theme: PresentationTheme, _ strings: PresentationStrings, _ message: Message, _ media: Media, _ automaticDownload: Bool, _ peerType: AutomaticMediaDownloadPeerType, _ automaticPlayback: Bool, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants) -> (CGSize, CGFloat, (CGSize, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ContainedViewLayoutTransition) -> Void))) if let node = node, let currentAsyncLayout = currentAsyncLayout { imageNode = node @@ -687,7 +687,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode { imageLayout = imageNode.asyncLayout() } - let (unboundSize, initialWidth, continueLayout) = imageLayout(account, theme, strings, message, media, automaticDownload, automaticPlayback, sizeCalculation, layoutConstants) + let (unboundSize, initialWidth, continueLayout) = imageLayout(account, theme, strings, message, media, automaticDownload, peerType, automaticPlayback, sizeCalculation, layoutConstants) return (unboundSize, initialWidth, { constrainedSize, corners in let (finalWidth, finalLayout) = continueLayout(constrainedSize, corners) diff --git a/TelegramUI/ChatMessageMediaBubbleContentNode.swift b/TelegramUI/ChatMessageMediaBubbleContentNode.swift index 961a3d12dd..6a1e1e00da 100644 --- a/TelegramUI/ChatMessageMediaBubbleContentNode.swift +++ b/TelegramUI/ChatMessageMediaBubbleContentNode.swift @@ -78,7 +78,7 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { sizeCalculation = .unconstrained } - let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.account, item.presentationData.theme.theme, item.presentationData.strings, item.message, selectedMedia!, automaticDownload, item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs, sizeCalculation, layoutConstants) + let (unboundSize, initialWidth, refineLayout) = interactiveImageLayout(item.account, item.presentationData.theme.theme, item.presentationData.strings, item.message, selectedMedia!, automaticDownload, item.associatedData.automaticDownloadPeerType, item.controllerInteraction.automaticMediaDownloadSettings.autoplayGifs, sizeCalculation, layoutConstants) var forceFullCorners = false if let media = selectedMedia as? TelegramMediaFile, media.isAnimated { diff --git a/TelegramUI/ChatMessageStickerItemNode.swift b/TelegramUI/ChatMessageStickerItemNode.swift index 91897e98e7..0e6319e47e 100644 --- a/TelegramUI/ChatMessageStickerItemNode.swift +++ b/TelegramUI/ChatMessageStickerItemNode.swift @@ -189,9 +189,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let displayLeftInset = params.leftInset + layoutConstants.bubble.edgeInset + avatarInset - let imageFrame = CGRect(origin: CGPoint(x: (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - imageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left)), y: 0.0), size: imageSize) + let imageFrame = CGRect(origin: CGPoint(x: 0.0 + (incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset + layoutConstants.bubble.contentInsets.left) : (params.width - params.rightInset - imageSize.width - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left)), y: 0.0), size: CGSize(width: imageSize.width + 0.0, height: imageSize.height + 0.0)) - let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageFrame.size, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets()) + let arguments = TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: CGSize(width: imageSize.width + 0.0, height: imageSize.height + 0.0), intrinsicInsets: UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)) let imageApply = imageLayout(arguments) diff --git a/TelegramUI/ChatRecentActionsControllerNode.swift b/TelegramUI/ChatRecentActionsControllerNode.swift index 23b0dabe24..138265f7bf 100644 --- a/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/TelegramUI/ChatRecentActionsControllerNode.swift @@ -178,7 +178,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self?.openPeerMention(name) }, openMessageContextMenu: { [weak self] message, node, frame in self?.openMessageContextMenu(message: message, node: node, frame: frame) - }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _ in + }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _ in self?.openUrl(url) }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { diff --git a/TelegramUI/DataAndStorageSettingsController.swift b/TelegramUI/DataAndStorageSettingsController.swift index 42ffc03d95..7907fb8026 100644 --- a/TelegramUI/DataAndStorageSettingsController.swift +++ b/TelegramUI/DataAndStorageSettingsController.swift @@ -13,11 +13,11 @@ private final class DataAndStorageControllerArguments { let openAutomaticDownloadCategory: (AutomaticDownloadCategory) -> Void let resetAutomaticDownload: () -> Void let openVoiceUseLessData: () -> Void - let toggleSaveIncomingPhotos: (Bool) -> Void + let openSaveIncomingPhotos: () -> Void let toggleSaveEditedPhotos: (Bool) -> Void let toggleAutoplayGifs: (Bool) -> Void - init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, toggleAutomaticDownloadMaster: @escaping (Bool) -> Void, openAutomaticDownloadCategory: @escaping (AutomaticDownloadCategory) -> Void, resetAutomaticDownload: @escaping () -> Void, openVoiceUseLessData: @escaping () -> Void, toggleSaveIncomingPhotos: @escaping (Bool) -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void) { + init(openStorageUsage: @escaping () -> Void, openNetworkUsage: @escaping () -> Void, openProxy: @escaping () -> Void, toggleAutomaticDownloadMaster: @escaping (Bool) -> Void, openAutomaticDownloadCategory: @escaping (AutomaticDownloadCategory) -> Void, resetAutomaticDownload: @escaping () -> Void, openVoiceUseLessData: @escaping () -> Void, openSaveIncomingPhotos: @escaping () -> Void, toggleSaveEditedPhotos: @escaping (Bool) -> Void, toggleAutoplayGifs: @escaping (Bool) -> Void) { self.openStorageUsage = openStorageUsage self.openNetworkUsage = openNetworkUsage self.openProxy = openProxy @@ -25,7 +25,7 @@ private final class DataAndStorageControllerArguments { self.openAutomaticDownloadCategory = openAutomaticDownloadCategory self.resetAutomaticDownload = resetAutomaticDownload self.openVoiceUseLessData = openVoiceUseLessData - self.toggleSaveIncomingPhotos = toggleSaveIncomingPhotos + self.openSaveIncomingPhotos = openSaveIncomingPhotos self.toggleSaveEditedPhotos = toggleSaveEditedPhotos self.toggleAutoplayGifs = toggleAutoplayGifs } @@ -53,7 +53,7 @@ private enum DataAndStorageEntry: ItemListNodeEntry { case voiceCallsHeader(PresentationTheme, String) case useLessVoiceData(PresentationTheme, String, String) case otherHeader(PresentationTheme, String) - case saveIncomingPhotos(PresentationTheme, String, Bool) + case saveIncomingPhotos(PresentationTheme, String) case saveEditedPhotos(PresentationTheme, String, Bool) case autoplayGifs(PresentationTheme, String, Bool) case connectionHeader(PresentationTheme, String) @@ -195,8 +195,8 @@ private enum DataAndStorageEntry: ItemListNodeEntry { } else { return false } - case let .saveIncomingPhotos(lhsTheme, lhsText, lhsValue): - if case let .saveIncomingPhotos(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + case let .saveIncomingPhotos(lhsTheme, lhsText): + if case let .saveIncomingPhotos(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true } else { return false @@ -292,9 +292,9 @@ private enum DataAndStorageEntry: ItemListNodeEntry { }) case let .otherHeader(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) - case let .saveIncomingPhotos(theme, text, value): - return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in - arguments.toggleSaveIncomingPhotos(value) + case let .saveIncomingPhotos(theme, text): + return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .blocks, action: { + arguments.openSaveIncomingPhotos() }) case let .saveEditedPhotos(theme, text, value): return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in @@ -368,7 +368,7 @@ private func dataAndStorageControllerEntries(state: DataAndStorageControllerStat entries.append(.useLessVoiceData(presentationData.theme, presentationData.strings.CallSettings_UseLessData, stringForUseLessDataSetting(strings: presentationData.strings, settings: data.voiceCallSettings))) entries.append(.otherHeader(presentationData.theme, presentationData.strings.ChatSettings_Other)) - entries.append(.saveIncomingPhotos(presentationData.theme, presentationData.strings.Settings_SaveIncomingPhotos, data.automaticMediaDownloadSettings.saveIncomingPhotos)) + entries.append(.saveIncomingPhotos(presentationData.theme, presentationData.strings.Settings_SaveIncomingPhotos)) entries.append(.saveEditedPhotos(presentationData.theme, presentationData.strings.Settings_SaveEditedPhotos, data.generatedMediaStoreSettings.storeEditedPhotos)) entries.append(.autoplayGifs(presentationData.theme, presentationData.strings.ChatSettings_AutoPlayAnimations, data.automaticMediaDownloadSettings.autoplayGifs)) @@ -469,12 +469,8 @@ func dataAndStorageController(account: Account) -> ViewController { presentControllerImpl?(actionSheet, nil) }, openVoiceUseLessData: { pushControllerImpl?(voiceCallDataSavingController(account: account)) - }, toggleSaveIncomingPhotos: { value in - let _ = updateMediaDownloadSettingsInteractively(postbox: account.postbox, { settings in - var settings = settings - settings.saveIncomingPhotos = value - return settings - }).start() + }, openSaveIncomingPhotos: { + pushControllerImpl?(saveIncomingMediaController(account: account)) }, toggleSaveEditedPhotos: { value in let _ = updateGeneratedMediaStoreSettingsInteractively(postbox: account.postbox, { current in return current.withUpdatedStoreEditedPhotos(value) diff --git a/TelegramUI/FastBlur.h b/TelegramUI/FastBlur.h index 4f01258ee1..fa372b6b29 100644 --- a/TelegramUI/FastBlur.h +++ b/TelegramUI/FastBlur.h @@ -4,5 +4,6 @@ #import void telegramFastBlur(int imageWidth, int imageHeight, int imageStride, void *pixels); +void stickerThumbnailAlphaBlur(int imageWidth, int imageHeight, int imageStride, void *pixels); #endif diff --git a/TelegramUI/FastBlur.m b/TelegramUI/FastBlur.m index 3cad442542..e25537789d 100644 --- a/TelegramUI/FastBlur.m +++ b/TelegramUI/FastBlur.m @@ -1,5 +1,7 @@ #import "FastBlur.h" +#import + static inline uint64_t get_colors (const uint8_t *p) { return p[0] + (p[1] << 16) + ((uint64_t)p[2] << 32); } @@ -100,3 +102,24 @@ yi += stride; free(rgb); } + +void stickerThumbnailAlphaBlur(int imageWidth, int imageHeight, int imageStride, void *pixels) { + vImage_Buffer srcBuffer; + srcBuffer.width = imageWidth; + srcBuffer.height = imageHeight; + srcBuffer.rowBytes = imageStride; + srcBuffer.data = pixels; + + { + vImage_Buffer dstBuffer; + dstBuffer.width = imageWidth; + dstBuffer.height = imageHeight; + dstBuffer.rowBytes = imageStride; + dstBuffer.data = pixels; + + int boxSize = 2; + boxSize = boxSize - (boxSize % 2) + 1; + + vImageBoxConvolve_ARGB8888(&srcBuffer, &dstBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend); + } +} diff --git a/TelegramUI/FetchManager.swift b/TelegramUI/FetchManager.swift index dcc36d8706..0614e8e904 100644 --- a/TelegramUI/FetchManager.swift +++ b/TelegramUI/FetchManager.swift @@ -35,7 +35,7 @@ private final class FetchManagerLocationEntry { let statsCategory: MediaResourceStatsCategory var userInitiated: Bool = false - var storeToDownloads: Bool = false + var storeToDownloadsPeerType: AutomaticMediaDownloadPeerType? var referenceCount: Int32 = 0 var elevatedPriorityReferenceCount: Int32 = 0 var userInitiatedPriorityIndices: [Int32] = [] @@ -165,8 +165,8 @@ private final class FetchManagerCategoryContext { let storeManager = self.storeManager activeContext.disposable = (fetchedMediaResource(postbox: self.postbox, reference: entry.resourceReference, statsCategory: entry.statsCategory, reportResultStatus: true) |> mapToSignal { type -> Signal in - if let storeManager = storeManager, let mediaReference = entry.mediaReference, case .remote = type, entry.storeToDownloads { - return storeDownloadedMedia(storeManager: storeManager, media: mediaReference) + if let storeManager = storeManager, let mediaReference = entry.mediaReference, case .remote = type, let peerType = entry.storeToDownloadsPeerType { + return storeDownloadedMedia(storeManager: storeManager, media: mediaReference, peerType: peerType) |> mapToSignal { _ -> Signal in return .complete() } @@ -241,8 +241,8 @@ private final class FetchManagerCategoryContext { let storeManager = self.storeManager activeContext.disposable = (fetchedMediaResource(postbox: self.postbox, reference: entry.resourceReference, statsCategory: entry.statsCategory, reportResultStatus: true) |> mapToSignal { type -> Signal in - if let storeManager = storeManager, let mediaReference = entry.mediaReference, case .remote = type, entry.storeToDownloads { - return storeDownloadedMedia(storeManager: storeManager, media: mediaReference) + if let storeManager = storeManager, let mediaReference = entry.mediaReference, case .remote = type, let peerType = entry.storeToDownloadsPeerType { + return storeDownloadedMedia(storeManager: storeManager, media: mediaReference, peerType: peerType) |> mapToSignal { _ -> Signal in return .complete() } @@ -377,7 +377,7 @@ final class FetchManager { } } - func interactivelyFetched(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, mediaReference: AnyMediaReference?, resourceReference: MediaResourceReference, statsCategory: MediaResourceStatsCategory, elevatedPriority: Bool, userInitiated: Bool, storeToDownloads: Bool = false) -> Signal { + func interactivelyFetched(category: FetchManagerCategory, location: FetchManagerLocation, locationKey: FetchManagerLocationKey, mediaReference: AnyMediaReference?, resourceReference: MediaResourceReference, statsCategory: MediaResourceStatsCategory, elevatedPriority: Bool, userInitiated: Bool, storeToDownloadsPeerType: AutomaticMediaDownloadPeerType? = nil) -> Signal { let queue = self.queue return Signal { [weak self] subscriber in if let strongSelf = self { @@ -390,8 +390,8 @@ final class FetchManager { if userInitiated { entry.userInitiated = true } - if storeToDownloads { - entry.storeToDownloads = true + if let peerType = storeToDownloadsPeerType { + entry.storeToDownloadsPeerType = peerType } entry.referenceCount += 1 if elevatedPriority { diff --git a/TelegramUI/FetchMediaUtils.swift b/TelegramUI/FetchMediaUtils.swift index 33003ad37b..7ef252ede8 100644 --- a/TelegramUI/FetchMediaUtils.swift +++ b/TelegramUI/FetchMediaUtils.swift @@ -7,6 +7,10 @@ func freeMediaFileInteractiveFetched(account: Account, fileReference: FileMediaR return fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(fileReference.media.resource)) } +func freeMediaFileResourceInteractiveFetched(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal { + return fetchedMediaResource(postbox: account.postbox, reference: fileReference.resourceReference(resource)) +} + func cancelFreeMediaFileInteractiveFetch(account: Account, file: TelegramMediaFile) { account.postbox.mediaBox.cancelInteractiveResourceFetch(file.resource) } @@ -30,9 +34,9 @@ func messageMediaFileCancelInteractiveFetch(account: Account, messageId: Message account.telegramApplicationContext.fetchManager.cancelInteractiveFetches(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: file.resource) } -func messageMediaImageInteractiveFetched(account: Account, message: Message, image: TelegramMediaImage, resource: MediaResource, storeToDownloads: Bool) -> Signal { +func messageMediaImageInteractiveFetched(account: Account, message: Message, image: TelegramMediaImage, resource: MediaResource, storeToDownloadsPeerType: AutomaticMediaDownloadPeerType?) -> Signal { let mediaReference = AnyMediaReference.message(message: MessageReference(message), media: image) - return account.telegramApplicationContext.fetchManager.interactivelyFetched(category: .image, location: .chat(message.id.peerId), locationKey: .messageId(message.id), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(resource), statsCategory: .image, elevatedPriority: false, userInitiated: true, storeToDownloads: storeToDownloads) + return account.telegramApplicationContext.fetchManager.interactivelyFetched(category: .image, location: .chat(message.id.peerId), locationKey: .messageId(message.id), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(resource), statsCategory: .image, elevatedPriority: false, userInitiated: true, storeToDownloadsPeerType: storeToDownloadsPeerType) } func messageMediaImageCancelInteractiveFetch(account: Account, messageId: MessageId, image: TelegramMediaImage, resource: MediaResource) { diff --git a/TelegramUI/HorizontalStickerGridItem.swift b/TelegramUI/HorizontalStickerGridItem.swift index 22f9a9e1f7..f8eb759542 100644 --- a/TelegramUI/HorizontalStickerGridItem.swift +++ b/TelegramUI/HorizontalStickerGridItem.swift @@ -78,7 +78,7 @@ final class HorizontalStickerGridItemNode: GridItemNode { if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1.file.id != item.file.id { if let dimensions = item.file.dimensions { self.imageNode.setSignal(chatMessageSticker(account: account, file: item.file, small: true)) - self.stickerFetchedDisposable.set(freeMediaFileInteractiveFetched(account: account, fileReference: stickerPackFileReference(item.file)).start()) + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(item.file), resource: chatMessageStickerResource(file: item.file, small: true)).start()) self.currentState = (account, item, dimensions) self.setNeedsLayout() diff --git a/TelegramUI/HorizontalStickersChatContextPanelNode.swift b/TelegramUI/HorizontalStickersChatContextPanelNode.swift index 7b85aecc6f..cd51df6c4b 100644 --- a/TelegramUI/HorizontalStickersChatContextPanelNode.swift +++ b/TelegramUI/HorizontalStickersChatContextPanelNode.swift @@ -162,7 +162,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { var menuItems: [PeekControllerMenuItem] = [] menuItems = [ PeekControllerMenuItem(title: strongSelf.strings.StickerPack_Send, color: .accent, font: .bold, action: { - controllerInteraction.sendSticker(.standalone(media: item.file)) + controllerInteraction.sendSticker(.standalone(media: item.file), true) }), PeekControllerMenuItem(title: isStarred ? strongSelf.strings.Stickers_RemoveFromFavorites : strongSelf.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { if let strongSelf = self { @@ -182,7 +182,7 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode { let controller = StickerPackPreviewController(account: strongSelf.account, stickerPack: packReference, parentNavigationController: controllerInteraction.navigationController()) controller.sendSticker = { file in if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { - controllerInteraction.sendSticker(file) + controllerInteraction.sendSticker(file, true) } } diff --git a/TelegramUI/InstantPageImageNode.swift b/TelegramUI/InstantPageImageNode.swift index 02de77b9df..527c88096e 100644 --- a/TelegramUI/InstantPageImageNode.swift +++ b/TelegramUI/InstantPageImageNode.swift @@ -36,7 +36,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { if let image = media.media as? TelegramMediaImage { let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) self.imageNode.setSignal(chatMessagePhoto(postbox: account.postbox, photoReference: imageReference)) - self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: imageReference, storeToDownloads: false).start()) + self.fetchedDisposable.set(chatMessagePhotoInteractiveFetched(account: account, photoReference: imageReference, storeToDownloadsPeerType: nil).start()) } else if let file = media.media as? TelegramMediaFile { let fileReference = FileMediaReference.webPage(webPage: WebpageReference(webPage), media: file) self.imageNode.setSignal(chatMessageVideo(postbox: account.postbox, videoReference: fileReference)) diff --git a/TelegramUI/MediaInputPaneTrendingItem.swift b/TelegramUI/MediaInputPaneTrendingItem.swift index 92c4289ccf..3e8e92f451 100644 --- a/TelegramUI/MediaInputPaneTrendingItem.swift +++ b/TelegramUI/MediaInputPaneTrendingItem.swift @@ -268,7 +268,7 @@ class MediaInputPaneTrendingItemNode: ListViewItemNode { if file.fileId != node.file?.fileId { node.file = file node.setSignal(chatMessageSticker(account: item.account, file: file, small: true)) - node.loadDisposable.set(freeMediaFileInteractiveFetched(account: item.account, fileReference: stickerPackFileReference(file)).start()) + node.loadDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.account, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: true)).start()) } if let dimensions = file.dimensions { let imageSize = dimensions.aspectFitted(itemSize) diff --git a/TelegramUI/OverlayPlayerControllerNode.swift b/TelegramUI/OverlayPlayerControllerNode.swift index a8acd66f85..ddf67e39d9 100644 --- a/TelegramUI/OverlayPlayerControllerNode.swift +++ b/TelegramUI/OverlayPlayerControllerNode.swift @@ -54,7 +54,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec } else { return false } - }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, navigationController: { return nil }, presentGlobalOverlayController: { _, _ in diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index e0192536bb..9c67104545 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -165,7 +165,7 @@ public class PeerMediaCollectionController: TelegramController { strongSelf.updateInterfaceState(animated: true, { $0.withToggledSelectedMessages(ids, value: value) }) } }, sendMessage: { _ in - },sendSticker: { _ in + },sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index 38582b4d3c..8eb0c91427 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -1430,12 +1430,12 @@ func chatMessagePhotoStatus(account: Account, messageId: MessageId, photoReferen } } -public func chatMessagePhotoInteractiveFetched(account: Account, photoReference: ImageMediaReference, storeToDownloads: Bool) -> Signal { +public func chatMessagePhotoInteractiveFetched(account: Account, photoReference: ImageMediaReference, storeToDownloadsPeerType: AutomaticMediaDownloadPeerType?) -> Signal { if let largestRepresentation = largestRepresentationForPhoto(photoReference.media) { return fetchedMediaResource(postbox: account.postbox, reference: photoReference.resourceReference(largestRepresentation.resource), statsCategory: .image, reportResultStatus: true) |> mapToSignal { type -> Signal in - if case .remote = type, storeToDownloads { - return storeDownloadedMedia(storeManager: account.telegramApplicationContext.mediaManager?.downloadedMediaStoreManager, media: photoReference.abstract) + if case .remote = type, let peerType = storeToDownloadsPeerType { + return storeDownloadedMedia(storeManager: account.telegramApplicationContext.mediaManager?.downloadedMediaStoreManager, media: photoReference.abstract, peerType: peerType) |> mapToSignal { _ -> Signal in return .complete() } diff --git a/TelegramUI/SaveIncomingMediaController.swift b/TelegramUI/SaveIncomingMediaController.swift new file mode 100644 index 0000000000..ef4f4e6e04 --- /dev/null +++ b/TelegramUI/SaveIncomingMediaController.swift @@ -0,0 +1,128 @@ +import Foundation +import Display +import SwiftSignalKit +import Postbox +import TelegramCore + +private enum PeerType { + case contact + case otherPrivate + case group + case channel +} + +private final class SaveIncomingMediaControllerArguments { + let toggle: (PeerType) -> Void + + init(toggle: @escaping (PeerType) -> Void) { + self.toggle = toggle + } +} + +enum SaveIncomingMediaSection: ItemListSectionId { + case peers +} + +private enum SaveIncomingMediaEntry: ItemListNodeEntry { + case header(PresentationTheme, String) + case contacts(PresentationTheme, String, Bool) + case otherPrivate(PresentationTheme, String, Bool) + case groups(PresentationTheme, String, Bool) + case channels(PresentationTheme, String, Bool) + + var section: ItemListSectionId { + return SaveIncomingMediaSection.peers.rawValue + } + + var stableId: Int32 { + switch self { + case .header: + return 0 + case .contacts: + return 1 + case .otherPrivate: + return 2 + case .groups: + return 3 + case .channels: + return 4 + } + } + + static func <(lhs: SaveIncomingMediaEntry, rhs: SaveIncomingMediaEntry) -> Bool { + return lhs.stableId < rhs.stableId + } + + func item(_ arguments: SaveIncomingMediaControllerArguments) -> ListViewItem { + switch self { + case let .header(theme, text): + return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) + case let .contacts(theme, text, value): + return ItemListSwitchItem(theme: theme, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + arguments.toggle(.contact) + }) + case let .otherPrivate(theme, text, value): + return ItemListSwitchItem(theme: theme, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + arguments.toggle(.otherPrivate) + }) + case let .groups(theme, text, value): + return ItemListSwitchItem(theme: theme, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + arguments.toggle(.group) + }) + case let .channels(theme, text, value): + return ItemListSwitchItem(theme: theme, title: text, value: value, enableInteractiveChanges: true, enabled: true, sectionId: self.section, style: .blocks, updated: { value in + arguments.toggle(.channel) + }) + } + } +} + +private func saveIncomingMediaControllerEntries(presentationData: PresentationData, settings: AutomaticMediaDownloadSettings) -> [SaveIncomingMediaEntry] { + var entries: [SaveIncomingMediaEntry] = [] + + entries.append(.header(presentationData.theme, presentationData.strings.SaveIncomingPhotosSettings_From)) + entries.append(.contacts(presentationData.theme, presentationData.strings.AutoDownloadSettings_Contacts, settings.peers.contacts.saveDownloadedPhotos)) + entries.append(.otherPrivate(presentationData.theme, presentationData.strings.AutoDownloadSettings_PrivateChats, settings.peers.otherPrivate.saveDownloadedPhotos)) + entries.append(.groups(presentationData.theme, presentationData.strings.AutoDownloadSettings_GroupChats, settings.peers.groups.saveDownloadedPhotos)) + entries.append(.channels(presentationData.theme, presentationData.strings.AutoDownloadSettings_Channels, settings.peers.channels.saveDownloadedPhotos)) + + return entries +} + +func saveIncomingMediaController(account: Account) -> ViewController { + let arguments = SaveIncomingMediaControllerArguments(toggle: { type in + let _ = updateMediaDownloadSettingsInteractively(postbox: account.postbox, { settings in + var settings = settings + switch type { + case .contact: + settings.peers.contacts.saveDownloadedPhotos = !settings.peers.contacts.saveDownloadedPhotos + case .otherPrivate: + settings.peers.otherPrivate.saveDownloadedPhotos = !settings.peers.otherPrivate.saveDownloadedPhotos + case .group: + settings.peers.groups.saveDownloadedPhotos = !settings.peers.groups.saveDownloadedPhotos + case .channel: + settings.peers.channels.saveDownloadedPhotos = !settings.peers.channels.saveDownloadedPhotos + } + return settings + }).start() + }) + + let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, account.postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings])) |> deliverOnMainQueue + |> map { presentationData, prefs -> (ItemListControllerState, (ItemListNodeState, SaveIncomingMediaEntry.ItemGenerationArguments)) in + let automaticMediaDownloadSettings: AutomaticMediaDownloadSettings + if let value = prefs.values[ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings] as? AutomaticMediaDownloadSettings { + automaticMediaDownloadSettings = value + } else { + automaticMediaDownloadSettings = AutomaticMediaDownloadSettings.defaultSettings + } + + let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.SaveIncomingPhotosSettings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) + let listState = ItemListNodeState(entries: saveIncomingMediaControllerEntries(presentationData: presentationData, settings: automaticMediaDownloadSettings), style: .blocks, emptyStateItem: nil, animateChanges: false) + + return (controllerState, (listState, arguments)) + } + + let controller = ItemListController(account: account, state: signal) + return controller +} + diff --git a/TelegramUI/StickerPackPreviewGridItem.swift b/TelegramUI/StickerPackPreviewGridItem.swift index d691cf1887..45ff59af41 100644 --- a/TelegramUI/StickerPackPreviewGridItem.swift +++ b/TelegramUI/StickerPackPreviewGridItem.swift @@ -101,7 +101,8 @@ final class StickerPackPreviewGridItemNode: GridItemNode { self.textNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: .black, paragraphAlignment: .right) if let dimensions = stickerItem.file.dimensions { self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true)) - self.stickerFetchedDisposable.set(freeMediaFileInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file)).start()) + + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start()) self.currentState = (account, stickerItem, dimensions) self.setNeedsLayout() diff --git a/TelegramUI/StickerPaneSearchContainerNode.swift b/TelegramUI/StickerPaneSearchContainerNode.swift index fc7e74a3f1..f196b6f3fd 100644 --- a/TelegramUI/StickerPaneSearchContainerNode.swift +++ b/TelegramUI/StickerPaneSearchContainerNode.swift @@ -237,7 +237,7 @@ final class StickerPaneSearchContainerNode: ASDisplayNode { } }, sendSticker: { [weak self] file in if let strongSelf = self { - strongSelf.controllerInteraction.sendSticker(file) + strongSelf.controllerInteraction.sendSticker(file, false) } }, getItemIsPreviewed: { item in return inputNodeInteraction.previewedStickerPackItem == .pack(item) diff --git a/TelegramUI/StickerPaneSearchGlobaltem.swift b/TelegramUI/StickerPaneSearchGlobaltem.swift index 6cee2eda96..e30ad5bd9f 100644 --- a/TelegramUI/StickerPaneSearchGlobaltem.swift +++ b/TelegramUI/StickerPaneSearchGlobaltem.swift @@ -261,7 +261,7 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { if file.fileId != node.file?.fileId { node.file = file node.setSignal(chatMessageSticker(account: item.account, file: file, small: true)) - node.loadDisposable.set(freeMediaFileInteractiveFetched(account: item.account, fileReference: stickerPackFileReference(file)).start()) + node.loadDisposable.set(freeMediaFileResourceInteractiveFetched(account: item.account, fileReference: stickerPackFileReference(file), resource: chatMessageStickerResource(file: file, small: true)).start()) } if let dimensions = file.dimensions { let imageSize = dimensions.aspectFitted(itemSize) diff --git a/TelegramUI/StickerPaneSearchStickerItem.swift b/TelegramUI/StickerPaneSearchStickerItem.swift index 77e1bcfc56..ecbd58553c 100644 --- a/TelegramUI/StickerPaneSearchStickerItem.swift +++ b/TelegramUI/StickerPaneSearchStickerItem.swift @@ -138,7 +138,7 @@ final class StickerPaneSearchStickerItemNode: GridItemNode { if self.currentState == nil || self.currentState!.0 !== account || self.currentState!.1 != stickerItem { if let dimensions = stickerItem.file.dimensions { self.imageNode.setSignal(chatMessageSticker(account: account, file: stickerItem.file, small: true)) - self.stickerFetchedDisposable.set(freeMediaFileInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file)).start()) + self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: account, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start()) self.currentState = (account, stickerItem, dimensions) self.setNeedsLayout() diff --git a/TelegramUI/StickerResources.swift b/TelegramUI/StickerResources.swift index edec29d278..bf36f1ce0a 100644 --- a/TelegramUI/StickerResources.swift +++ b/TelegramUI/StickerResources.swift @@ -34,8 +34,21 @@ private func imageFromAJpeg(data: Data) -> (UIImage, UIImage)? { return nil } +func chatMessageStickerResource(file: TelegramMediaFile, small: Bool) -> MediaResource { + let resource: MediaResource + if small, let smallest = largestImageRepresentation(file.previewRepresentations) { + resource = smallest.resource + } else { + resource = file.resource + } + return resource +} + private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, small: Bool, fetched: Bool, onlyFullSize: Bool) -> Signal<(Data?, Data?, Bool), NoError> { - let maybeFetched = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: false) + let thumbnailResource = chatMessageStickerResource(file: file, small: true) + let resource = chatMessageStickerResource(file: file, small: small) + + let maybeFetched = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: false) return maybeFetched |> take(1) @@ -45,14 +58,15 @@ private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, return .single((nil, loadedData, true)) } else { - let fullSizeData = account.postbox.mediaBox.cachedResourceRepresentation(file.resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: onlyFullSize) + let thumbnailData = account.postbox.mediaBox.cachedResourceRepresentation(thumbnailResource, representation: CachedStickerAJpegRepresentation(size: nil), complete: false) + let fullSizeData = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedStickerAJpegRepresentation(size: small ? CGSize(width: 160.0, height: 160.0) : nil), complete: onlyFullSize) |> map { next in return (next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe), next.complete) } if fetched { return Signal { subscriber in - let fetch = fetchedMediaResource(postbox: account.postbox, reference: stickerPackFileReference(file).resourceReference(file.resource)).start() + let fetch = fetchedMediaResource(postbox: account.postbox, reference: stickerPackFileReference(file).resourceReference(resource)).start() let disposable = (fullSizeData |> map { (data, complete) -> (Data?, Data?, Bool) in return (nil, data, complete) }).start(next: { next in @@ -69,9 +83,26 @@ private func chatMessageStickerDatas(account: Account, file: TelegramMediaFile, } } } else { - return fullSizeData - |> map { (data, complete) -> (Data?, Data?, Bool) in - return (nil, data, complete) + return Signal { subscriber in + var fetchThumbnail: Disposable? + if !thumbnailResource.id.isEqual(to: resource.id) { + fetchThumbnail = fetchedMediaResource(postbox: account.postbox, reference: stickerPackFileReference(file).resourceReference(thumbnailResource)).start() + } + let disposable = (combineLatest(thumbnailData, fullSizeData) + |> map { thumbnailData, fullSizeData -> (Data?, Data?, Bool) in + return (thumbnailData.complete ? try? Data(contentsOf: URL(fileURLWithPath: thumbnailData.path)) : nil, fullSizeData.0, fullSizeData.1) + }).start(next: { next in + subscriber.putNext(next) + }, error: { error in + subscriber.putError(error) + }, completed: { + subscriber.putCompletion() + }) + + return ActionDisposable { + fetchThumbnail?.dispose() + disposable.dispose() + } } } } @@ -161,18 +192,33 @@ public func chatMessageSticker(account: Account, file: TelegramMediaFile, small: } } - let thumbnailImage: CGImage? = nil + var thumbnailImage: (UIImage, UIImage)? + if fullSizeImage == nil, let thumbnailData = thumbnailData { + if let image = imageFromAJpeg(data: thumbnailData) { + thumbnailImage = image + } + } var blurredThumbnailImage: UIImage? + let thumbnailInset: CGFloat = 10.0 if let thumbnailImage = thumbnailImage { - let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height) - let thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) - let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) + let thumbnailSize = thumbnailImage.0.size + var thumbnailContextSize = thumbnailSize.aspectFitted(CGSize(width: 150.0, height: 150.0)) + let thumbnailDrawingSize = thumbnailContextSize + thumbnailContextSize.width += thumbnailInset * 2.0 + thumbnailContextSize.height += thumbnailInset * 2.0 + let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0, clear: true) thumbnailContext.withFlippedContext { c in - c.interpolationQuality = .none - c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize)) + if let cgImage = thumbnailImage.0.cgImage, let cgImageAlpha = thumbnailImage.1.cgImage { + c.setBlendMode(.normal) + c.interpolationQuality = .medium + + let mask = CGImage(maskWidth: cgImageAlpha.width, height: cgImageAlpha.height, bitsPerComponent: cgImageAlpha.bitsPerComponent, bitsPerPixel: cgImageAlpha.bitsPerPixel, bytesPerRow: cgImageAlpha.bytesPerRow, provider: cgImageAlpha.dataProvider!, decode: nil, shouldInterpolate: true) + + c.draw(cgImage.masking(mask!)!, in: CGRect(origin: CGPoint(x: thumbnailInset, y: thumbnailInset), size: thumbnailDrawingSize)) + } } - telegramFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) + stickerThumbnailAlphaBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes) blurredThumbnailImage = thumbnailContext.generateImage() } @@ -181,7 +227,8 @@ public func chatMessageSticker(account: Account, file: TelegramMediaFile, small: c.setBlendMode(.copy) if let blurredThumbnailImage = blurredThumbnailImage { c.interpolationQuality = .low - c.draw(blurredThumbnailImage.cgImage!, in: fittedRect) + let thumbnailScaledInset = thumbnailInset * (fittedRect.width / blurredThumbnailImage.size.width) + c.draw(blurredThumbnailImage.cgImage!, in: fittedRect.insetBy(dx: -thumbnailScaledInset, dy: -thumbnailScaledInset)) } if let fullSizeImage = fullSizeImage, let cgImage = fullSizeImage.0.cgImage, let cgImageAlpha = fullSizeImage.1.cgImage { diff --git a/TelegramUI/StoreDownloadedMedia.swift b/TelegramUI/StoreDownloadedMedia.swift index ac1d85942f..bef471f7c7 100644 --- a/TelegramUI/StoreDownloadedMedia.swift +++ b/TelegramUI/StoreDownloadedMedia.swift @@ -55,21 +55,43 @@ private final class DownloadedMediaStoreContext { self.disposable?.dispose() } - func start(postbox: Postbox, collection: Signal, storeSettings: Signal, timestamp: Int32, media: AnyMediaReference, completed: @escaping () -> Void) { + func start(postbox: Postbox, collection: Signal, storeSettings: Signal, peerType: AutomaticMediaDownloadPeerType, timestamp: Int32, media: AnyMediaReference, completed: @escaping () -> Void) { var resource: TelegramMediaResource? if let image = media.media as? TelegramMediaImage { resource = largestImageRepresentation(image.representations)?.resource } if let resource = resource { - self.disposable = (combineLatest(collection |> take(1), storeSettings |> take(1)) - |> mapToSignal { collection, storeSettings -> Signal<(PHAssetCollection, Bool, MediaResourceData), NoError> in - return postbox.mediaBox.resourceData(resource) - |> map { (collection, storeSettings, $0) } - } - |> deliverOn(queue)).start(next: { collection, storeSettings, data in - if !storeSettings { - return + self.disposable = (storeSettings + |> map { storeSettings -> Bool in + switch peerType { + case .contact: + if !storeSettings.peers.contacts.saveDownloadedPhotos { + return false + } + case .otherPrivate: + if !storeSettings.peers.otherPrivate.saveDownloadedPhotos { + return false + } + case .group: + if !storeSettings.peers.groups.saveDownloadedPhotos { + return false + } + case .channel: + if !storeSettings.peers.channels.saveDownloadedPhotos { + return false + } } + return true + } + |> take(1) + |> mapToSignal { store -> Signal<(PHAssetCollection, MediaResourceData), NoError> in + if !store { + return .complete() + } else { + return combineLatest(collection |> take(1), postbox.mediaBox.resourceData(resource)) + } + } + |> deliverOn(queue)).start(next: { collection, data in if !data.complete { return } @@ -141,7 +163,7 @@ private final class DownloadedMediaStoreManagerImpl { private var storeContexts: [MediaId: DownloadedMediaStoreContext] = [:] private let appSpecificAssetCollectionValue: Promise - private let storeSettings = Promise() + private let storeSettings = Promise() init(queue: Queue, postbox: Postbox) { self.queue = queue @@ -149,11 +171,12 @@ private final class DownloadedMediaStoreManagerImpl { self.appSpecificAssetCollectionValue = Promise(initializeOnFirstAccess: appSpecificAssetCollection()) self.storeSettings.set(postbox.preferencesView(keys: [ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings]) - |> map { view -> Bool in + |> map { view -> AutomaticMediaDownloadSettings in if let settings = view.values[ApplicationSpecificPreferencesKeys.automaticMediaDownloadSettings] as? AutomaticMediaDownloadSettings { - return settings.saveIncomingPhotos + return settings + } else { + return .defaultSettings } - return false }) } @@ -167,7 +190,7 @@ private final class DownloadedMediaStoreManagerImpl { return nextId } - func store(_ media: AnyMediaReference, timestamp: Int32) { + func store(_ media: AnyMediaReference, timestamp: Int32, peerType: AutomaticMediaDownloadPeerType) { guard let id = media.media.id else { return } @@ -175,7 +198,7 @@ private final class DownloadedMediaStoreManagerImpl { let context = DownloadedMediaStoreContext(queue: self.queue) self.storeContexts[id] = context let appSpecificAssetCollectionValue = self.appSpecificAssetCollectionValue - context.start(postbox: self.postbox, collection: deferred { appSpecificAssetCollectionValue.get() }, storeSettings: self.storeSettings.get(), timestamp: timestamp, media: media, completed: { [weak self, weak context] in + context.start(postbox: self.postbox, collection: deferred { appSpecificAssetCollectionValue.get() }, storeSettings: self.storeSettings.get(), peerType: peerType, timestamp: timestamp, media: media, completed: { [weak self, weak context] in guard let strongSelf = self, let context = context else { return } @@ -199,20 +222,20 @@ final class DownloadedMediaStoreManager { }) } - func store(_ media: AnyMediaReference, timestamp: Int32) { + func store(_ media: AnyMediaReference, timestamp: Int32, peerType: AutomaticMediaDownloadPeerType) { self.impl.with { impl in - impl.store(media, timestamp: timestamp) + impl.store(media, timestamp: timestamp, peerType: peerType) } } } -func storeDownloadedMedia(storeManager: DownloadedMediaStoreManager?, media: AnyMediaReference) -> Signal { - guard case let .message(message, _) = media, let peer = message.peer, case .user = peer, let timestamp = message.timestamp, let incoming = message.isIncoming, incoming, let secret = message.isSecret, !secret else { +func storeDownloadedMedia(storeManager: DownloadedMediaStoreManager?, media: AnyMediaReference, peerType: AutomaticMediaDownloadPeerType) -> Signal { + guard case let .message(message, _) = media, let timestamp = message.timestamp, let incoming = message.isIncoming, incoming, let secret = message.isSecret, !secret else { return .complete() } return Signal { [weak storeManager] subscriber in - storeManager?.store(media, timestamp: timestamp) + storeManager?.store(media, timestamp: timestamp, peerType: peerType) subscriber.putCompletion() return EmptyDisposable } diff --git a/TelegramUI/ThemeSettingsChatPreviewItem.swift b/TelegramUI/ThemeSettingsChatPreviewItem.swift index e6bf8a8ea3..823dd85c3d 100644 --- a/TelegramUI/ThemeSettingsChatPreviewItem.swift +++ b/TelegramUI/ThemeSettingsChatPreviewItem.swift @@ -91,7 +91,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { self.containerNode.subnodeTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0) self.controllerInteraction = ChatControllerInteraction(openMessage: { _ in - return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _ in }, sendGif: { _ in }, requestMessageActionCallback: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, navigationController: { return nil }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in