From 564ee0d9665ae75ed87209285e33f3d0be1a2de1 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 9 Nov 2020 00:45:44 +0400 Subject: [PATCH] Various fixes --- .../ContactMultiselectionController.swift | 4 +- .../ChatListUI/Sources/ChatContextMenus.swift | 36 +- .../ChatListFilterPresetController.swift | 4 +- .../Sources/ChatListSearchContainerNode.swift | 34 +- .../ChatItemGalleryFooterContentNode.swift | 5 +- .../Sources/InstantPageWebEmbedNode.swift | 1 + .../Sources/TGMediaAssetsController.m | 7 +- .../Sources/TGMediaAssetsPickerController.m | 7 +- .../TGMediaPickerGalleryVideoItemView.m | 7 +- .../Sources/LegacyICloudFilePicker.swift | 15 +- .../Sources/LegacyMediaPickers.swift | 13 + .../ShareItems/Sources/ShareItems.swift | 38 ++- .../PendingMessageUploadedContent.swift | 15 +- .../Resources/WebEmbed/VimeoUserScript.js | 2 +- .../TelegramUI/Sources/ChatController.swift | 36 +- .../Sources/ChatHistoryListNode.swift | 12 +- .../ChatMessageInteractiveMediaNode.swift | 2 +- .../ChatSearchResultsContollerNode.swift | 2 +- .../ContactMultiselectionController.swift | 13 +- .../Sources/StickerShimmerEffectNode.swift | 312 +++++++++++++----- .../Sources/VimeoEmbedImplementation.swift | 5 + .../Sources/WebEmbedPlayerNode.swift | 5 +- 22 files changed, 415 insertions(+), 160 deletions(-) diff --git a/submodules/AccountContext/Sources/ContactMultiselectionController.swift b/submodules/AccountContext/Sources/ContactMultiselectionController.swift index b355aae84b..14238d48fd 100644 --- a/submodules/AccountContext/Sources/ContactMultiselectionController.swift +++ b/submodules/AccountContext/Sources/ContactMultiselectionController.swift @@ -46,13 +46,15 @@ public final class ContactMultiselectionControllerParams { public let options: [ContactListAdditionalOption] public let filters: [ContactListFilter] public let alwaysEnabled: Bool + public let limit: Int32? - public init(context: AccountContext, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], alwaysEnabled: Bool = false) { + public init(context: AccountContext, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], alwaysEnabled: Bool = false, limit: Int32? = nil) { self.context = context self.mode = mode self.options = options self.filters = filters self.alwaysEnabled = alwaysEnabled + self.limit = limit } } diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 7743bc1ad4..d01c91f1dc 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -244,18 +244,18 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch }))) } } - - if isUnread { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = togglePeerUnreadMarkInteractively(postbox: context.account.postbox, viewTracker: context.account.viewTracker, peerId: peerId).start() - f(.default) - }))) - } else { - items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsUnread, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsUnread"), color: theme.contextMenu.primaryColor) }, action: { _, f in - let _ = togglePeerUnreadMarkInteractively(postbox: context.account.postbox, viewTracker: context.account.viewTracker, peerId: peerId).start() - f(.default) - }))) - } + } + + if isUnread { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsRead, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsRead"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = togglePeerUnreadMarkInteractively(postbox: context.account.postbox, viewTracker: context.account.viewTracker, peerId: peerId).start() + f(.default) + }))) + } else { + items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_MarkAsUnread, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MarkAsUnread"), color: theme.contextMenu.primaryColor) }, action: { _, f in + let _ = togglePeerUnreadMarkInteractively(postbox: context.account.postbox, viewTracker: context.account.viewTracker, peerId: peerId).start() + f(.default) + }))) } let groupAndIndex = transaction.getPeerChatListIndex(peerId) @@ -288,17 +288,19 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch }))) } - if case let .chatList(filter) = source { +// if { let location: TogglePeerChatPinnedLocation - if let filter = filter { - location = .filter(filter.id) + var chatListFilter: ChatListFilter? + if case let .chatList(filter) = source, let chatFilter = filter { + chatListFilter = chatFilter + location = .filter(chatFilter.id) } else { location = .group(group) } let isPinned = getPinnedItemIds(transaction: transaction, location: location).contains(.peer(peerId)) - if isPinned || filter == nil || peerId.namespace != Namespaces.Peer.SecretChat { + if isPinned || chatListFilter == nil || peerId.namespace != Namespaces.Peer.SecretChat { items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in let _ = (toggleItemPinned(postbox: context.account.postbox, location: location, itemId: .peer(peerId)) |> deliverOnMainQueue).start(next: { result in @@ -312,7 +314,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch }) }))) } - } +// } if !isSavedMessages, let notificationSettings = transaction.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings { var isMuted = false diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index ea0e8be2d0..cd43b75fa8 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -593,7 +593,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f } } - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_IncludeChatsTitle, selectedChats: Set(filter.data.includePeers.peers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters), options: [], filters: [], alwaysEnabled: true)) + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_IncludeChatsTitle, selectedChats: Set(filter.data.includePeers.peers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters), options: [], filters: [], alwaysEnabled: true, limit: 100)) controller.navigationPresentation = .modal let _ = (controller.result |> take(1) @@ -680,7 +680,7 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex selectedCategories.insert(AdditionalExcludeCategoryId.archived.rawValue) } - let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_ExcludeChatsTitle, selectedChats: Set(filter.data.excludePeers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters), options: [], filters: [], alwaysEnabled: true)) + let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_ExcludeChatsTitle, selectedChats: Set(filter.data.excludePeers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters), options: [], filters: [], alwaysEnabled: true, limit: 100)) controller.navigationPresentation = .modal let _ = (controller.result |> take(1) diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index ecfc3045ed..c20932c03e 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -306,16 +306,19 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo return .single([]) } } + + let accountPeer = self.context.account.postbox.loadedPeerWithId(self.context.account.peerId) + |> take(1) - self.suggestedFiltersDisposable.set((combineLatest(suggestedPeers, self.suggestedDates.get(), self.selectedFilterKeyPromise.get(), self.searchQuery.get()) - |> mapToSignal { peers, dates, selectedFilter, searchQuery -> Signal<([Peer], [(Date?, Date, String?)], ChatListSearchFilterEntryId?), NoError> in + self.suggestedFiltersDisposable.set((combineLatest(suggestedPeers, self.suggestedDates.get(), self.selectedFilterKeyPromise.get(), self.searchQuery.get(), accountPeer) + |> mapToSignal { peers, dates, selectedFilter, searchQuery, accountPeer -> Signal<([Peer], [(Date?, Date, String?)], ChatListSearchFilterEntryId?, String?, Peer?), NoError> in if searchQuery?.isEmpty ?? true { - return .single((peers, dates, selectedFilter)) + return .single((peers, dates, selectedFilter, searchQuery, accountPeer)) } else { return (.complete() |> delay(0.25, queue: Queue.mainQueue())) - |> then(.single((peers, dates, selectedFilter))) + |> then(.single((peers, dates, selectedFilter, searchQuery, accountPeer))) } - } |> map { peers, dates, selectedFilter -> [ChatListSearchFilter] in + } |> map { peers, dates, selectedFilter, searchQuery, accountPeer -> [ChatListSearchFilter] in var suggestedFilters: [ChatListSearchFilter] = [] if !dates.isEmpty { let formatter = DateFormatter() @@ -327,8 +330,18 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo suggestedFilters.append(.date(minDate.flatMap { Int32($0.timeIntervalSince1970) }, Int32(maxDate.timeIntervalSince1970), title)) } } + let presentationData = context.sharedContext.currentPresentationData.with { $0 } if !peers.isEmpty && selectedFilter != .filter(ChatListSearchFilter.chats.id) { + var existingPeerIds = Set() + var peers = peers + if let accountPeer = accountPeer, let lowercasedQuery = searchQuery?.lowercased(), lowercasedQuery.count > 1 && (presentationData.strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery)) { + peers.insert(accountPeer, at: 0) + } + for peer in peers { + if existingPeerIds.contains(peer.id) { + continue + } let isGroup: Bool if peer.id.namespace == Namespaces.Peer.SecretChat { continue @@ -339,8 +352,15 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo } else { isGroup = false } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - suggestedFilters.append(.peer(peer.id, isGroup, peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), peer.compactDisplayTitle)) + + var title: String = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + var compactDisplayTitle = peer.compactDisplayTitle + if peer.id == accountPeer?.id { + title = presentationData.strings.DialogList_SavedMessages + compactDisplayTitle = title + } + suggestedFilters.append(.peer(peer.id, isGroup, title, compactDisplayTitle)) + existingPeerIds.insert(peer.id) } } return suggestedFilters diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 94bf2b259d..e8ef3354e6 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -441,6 +441,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } if origin == nil { + self.editButton.isHidden = true self.deleteButton.isHidden = true } } @@ -516,9 +517,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } messageText = galleryCaptionStringWithAppliedEntities(message.text, entities: entities) } - - self.editButton.isHidden = message.containsSecretMedia - + if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || canShare != !self.actionButton.isHidden || canEdit != !self.editButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText { self.currentMessageText = messageText diff --git a/submodules/InstantPageUI/Sources/InstantPageWebEmbedNode.swift b/submodules/InstantPageUI/Sources/InstantPageWebEmbedNode.swift index 31de3d5bdd..be2a1e3e8b 100644 --- a/submodules/InstantPageUI/Sources/InstantPageWebEmbedNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageWebEmbedNode.swift @@ -56,6 +56,7 @@ final class InstantPageWebEmbedNode: ASDisplayNode, InstantPageNode { configuration.userContentController = userController let webView = WKWebView(frame: CGRect(origin: CGPoint(), size: frame.size), configuration: configuration) + webView.allowsBackForwardNavigationGestures = false if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { webView.allowsLinkPreview = false } diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index df17228fe4..390b6a425f 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -309,8 +309,13 @@ return false; bool onlyGroupableMedia = true; - for (TGMediaAsset *asset in strongSelf->_selectionContext.selectedItems) + for (TGMediaAsset *item in strongSelf->_selectionContext.selectedItems) { + TGMediaAsset *asset = asset; + if ([asset isKindOfClass:[TGCameraCapturedVideo class]]) { + asset = [(TGCameraCapturedVideo *)item originalAsset]; + } + if (asset.type == TGMediaAssetGifType) { onlyGroupableMedia = false; diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m index 61c68aec85..1d1d74287c 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsPickerController.m @@ -236,7 +236,12 @@ - (SSignal *)_signalForItem:(id)item { - SSignal *assetSignal = [TGMediaAssetImageSignals imageForAsset:item imageType:TGMediaAssetImageTypeThumbnail size:[_layoutMetrics imageSize]]; + TGMediaAsset *concreteAsset = (TGMediaAsset *)item; + if ([concreteAsset isKindOfClass:[TGCameraCapturedVideo class]]) { + concreteAsset = [(TGCameraCapturedVideo *)item originalAsset]; + } + + SSignal *assetSignal = [TGMediaAssetImageSignals imageForAsset:concreteAsset imageType:TGMediaAssetImageTypeThumbnail size:[_layoutMetrics imageSize]]; if (self.editingContext == nil) return assetSignal; diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m index 2a52ae7986..dfdea64019 100644 --- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m +++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryVideoItemView.m @@ -504,8 +504,13 @@ if (_attributesDisposable == nil) _attributesDisposable = [[SMetaDisposable alloc] init]; + TGMediaAsset *asset = item.asset; + if ([asset isKindOfClass:[TGCameraCapturedVideo class]]) { + asset = [(TGCameraCapturedVideo *)asset originalAsset]; + } + _fileInfoLabel.text = nil; - [_attributesDisposable setDisposable:[[[TGMediaAssetImageSignals fileAttributesForAsset:item.asset] deliverOn:[SQueue mainQueue]] startWithNext:^(TGMediaAssetImageFileAttributes *next) + [_attributesDisposable setDisposable:[[[TGMediaAssetImageSignals fileAttributesForAsset:asset] deliverOn:[SQueue mainQueue]] startWithNext:^(TGMediaAssetImageFileAttributes *next) { __strong TGMediaPickerGalleryVideoItemView *strongSelf = weakSelf; if (strongSelf == nil) diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyICloudFilePicker.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyICloudFilePicker.swift index ac8cd7dcef..375e9ffc9b 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyICloudFilePicker.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyICloudFilePicker.swift @@ -4,6 +4,16 @@ import Display import TelegramPresentationData import LegacyUI +private class DocumentPickerViewController: UIDocumentPickerViewController { + var didDisappear: (() -> Void)? + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + self.didDisappear?() + } +} + private final class LegacyICloudFileController: LegacyController, UIDocumentPickerDelegate { let completion: ([URL]) -> Void @@ -52,7 +62,10 @@ public func legacyICloudFilePicker(theme: PresentationTheme, mode: LegacyICloudF }) legacyController.statusBar.statusBarStyle = .Black - let controller = UIDocumentPickerViewController(documentTypes: documentTypes, in: mode.documentPickerMode) + let controller = DocumentPickerViewController(documentTypes: documentTypes, in: mode.documentPickerMode) + controller.didDisappear = { + dismissImpl?() + } controller.delegate = legacyController if #available(iOSApplicationExtension 11.0, iOS 11.0, *), case .default = mode { controller.allowsMultipleSelection = true diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 0a859df6fc..ee8ea08f11 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -429,6 +429,14 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) - } } + var preset: TGMediaVideoConversionPreset = TGMediaVideoConversionPresetCompressedMedium + if let selectedPreset = adjustments?.preset { + preset = selectedPreset + } + if asAnimation { + preset = TGMediaVideoConversionPresetAnimation + } + if !asAnimation { finalDimensions = TGMediaVideoConverter.dimensions(for: finalDimensions, adjustments: adjustments, preset: TGMediaVideoConversionPresetCompressedMedium) } @@ -465,6 +473,8 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) - } } + let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: !asAnimation) + var fileAttributes: [TelegramMediaFileAttribute] = [] fileAttributes.append(.FileName(fileName: fileName)) if asAnimation { @@ -478,6 +488,9 @@ public func legacyAssetPickerEnqueueMessages(account: Account, signals: [Any]) - } } } + if estimatedSize > 5 * 1024 * 1024 { + fileAttributes.append(.hintFileIsLarge) + } var attributes: [MessageAttribute] = [] diff --git a/submodules/ShareItems/Sources/ShareItems.swift b/submodules/ShareItems/Sources/ShareItems.swift index 0add03e2a1..472729a7f4 100644 --- a/submodules/ShareItems/Sources/ShareItems.swift +++ b/submodules/ShareItems/Sources/ShareItems.swift @@ -110,7 +110,9 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri } } var finalDuration: Double = CMTimeGetSeconds(asset.duration) - let finalDimensions = TGMediaVideoConverter.dimensions(for: asset.originalSize, adjustments: adjustments, preset: adjustments?.preset ?? TGMediaVideoConversionPresetCompressedMedium) + + let preset = adjustments?.preset ?? TGMediaVideoConversionPresetCompressedMedium + let finalDimensions = TGMediaVideoConverter.dimensions(for: asset.originalSize, adjustments: adjustments, preset: preset) var resourceAdjustments: VideoMediaResourceAdjustments? if let adjustments = adjustments { @@ -123,8 +125,10 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri resourceAdjustments = VideoMediaResourceAdjustments(data: adjustmentsData, digest: digest) } + let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true) + let resource = LocalFileVideoMediaResource(randomId: arc4random64(), path: asset.url.path, adjustments: resourceAdjustments) - return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: finalDuration > 3.0 * 60.0) + return standaloneUploadedFile(account: account, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: Int(finalDuration), size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags)], hintFileIsLarge: estimatedSize > 5 * 1024 * 1024) |> mapError { _ -> Void in return Void() } @@ -229,7 +233,7 @@ private func preparedShareItem(account: Account, to peerId: PeerId, value: [Stri if let audioData = try? Data(contentsOf: url, options: [.mappedIfSafe]) { let fileName = url.lastPathComponent let duration = (value["duration"] as? NSNumber)?.doubleValue ?? 0.0 - let isVoice = ((value["isVoice"] as? NSNumber)?.boolValue ?? false) || (duration.isZero && duration < 30.0) + let isVoice = ((value["isVoice"] as? NSNumber)?.boolValue ?? false) let title = value["title"] as? String let artist = value["artist"] as? String @@ -357,21 +361,23 @@ public func sentShareItems(account: Account, to peerIds: [PeerId], items: [Prepa var messages: [EnqueueMessage] = [] var groupingKey: Int64? var mediaTypes: (photo: Bool, video: Bool, music: Bool, other: Bool) = (false, false, false, false) - for item in items { - if case let .media(result) = item, case let .media(media) = result { - if media.media is TelegramMediaImage { - mediaTypes.photo = true - } else if let media = media.media as? TelegramMediaFile { - if media.isVideo { - mediaTypes.video = true - } else if let fileName = media.fileName, fileName.hasPrefix("mp3") || fileName.hasPrefix("m4a") { - mediaTypes.music = true + if items.count > 1 { + for item in items { + if case let .media(result) = item, case let .media(media) = result { + if media.media is TelegramMediaImage { + mediaTypes.photo = true + } else if let media = media.media as? TelegramMediaFile { + if media.isVideo { + mediaTypes.video = true + } else if let fileName = media.fileName, fileName.hasPrefix("mp3") || fileName.hasPrefix("m4a") { + mediaTypes.music = true + } else { + mediaTypes.other = true + } } else { - mediaTypes.other = true + mediaTypes = (false, false, false, false) + break } - } else { - mediaTypes = (false, false, false, false) - break } } } diff --git a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift index bcd4f4d3f1..275e03300c 100644 --- a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift @@ -604,16 +604,15 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili hintSize = Int(size) } - loop: for attr in file.attributes { - switch attr { - case .hintFileIsLarge: - hintFileIsLarge = true - break loop - default: - break loop + loop: for attribute in file.attributes { + switch attribute { + case .hintFileIsLarge: + hintFileIsLarge = true + break loop + default: + break loop } } - let fileReference: AnyMediaReference if let partialReference = file.partialReference { diff --git a/submodules/TelegramUI/Resources/WebEmbed/VimeoUserScript.js b/submodules/TelegramUI/Resources/WebEmbed/VimeoUserScript.js index dae2e18316..daba76762d 100644 --- a/submodules/TelegramUI/Resources/WebEmbed/VimeoUserScript.js +++ b/submodules/TelegramUI/Resources/WebEmbed/VimeoUserScript.js @@ -1,5 +1,5 @@ function initialize() { - var controls = document.getElementsByClassName("controls")[0]; + var controls = document.getElementsByClassName("vp-controls-wrapper")[0]; if (controls != null) { controls.style.display = "none"; } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index a0e90dd2ea..9b11b5c2c5 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -457,12 +457,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return true } - if let _ = strongSelf.presentationInterfaceState.inputTextPanelState.mediaRecordingState { - strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Conversation_DiscardVoiceMessageDescription, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Conversation_DiscardVoiceMessageAction, action: { - self?.stopMediaRecorder() - action() - })]), in: .window(.root)) - + if strongSelf.presentVoiceMessageDiscardAlert(action: action) { return false } return true @@ -472,6 +467,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) else { return false } + strongSelf.commitPurposefulAction() strongSelf.dismissAllTooltips() @@ -7785,13 +7781,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var groupingKey: Int64? var fileTypes: (music: Bool, other: Bool) = (false, false) - for item in results { - if let item = item { - let pathExtension = (item.fileName as NSString).pathExtension.lowercased() - if ["mp3", "m4a"].contains(pathExtension) { - fileTypes.music = true - } else { - fileTypes.other = true + if results.count > 1 { + for item in results { + if let item = item { + let pathExtension = (item.fileName as NSString).pathExtension.lowercased() + if ["mp3", "m4a"].contains(pathExtension) { + fileTypes.music = true + } else { + fileTypes.other = true + } } } } @@ -11205,6 +11203,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(controller, in: .window(.root)) } + private func presentVoiceMessageDiscardAlert(action: @escaping () -> Void = {}) -> Bool { + if let _ = self.presentationInterfaceState.inputTextPanelState.mediaRecordingState { + self.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.Conversation_DiscardVoiceMessageDescription, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_DiscardVoiceMessageAction, action: { [weak self] in + self?.stopMediaRecorder() + action() + })]), in: .window(.root)) + + return true + } + return false + } + private var effectiveNavigationController: NavigationController? { if let navigationController = self.navigationController as? NavigationController { return navigationController diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 13a781edf6..b80b10f2cc 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -640,8 +640,16 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { self.refreshMediaProcessingManager.process = { [weak context] messageIds in context?.account.viewTracker.refreshSecretMediaMediaForMessageIds(messageIds: messageIds) } - self.messageMentionProcessingManager.process = { [weak context] messageIds in - context?.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds) + + self.messageMentionProcessingManager.process = { [weak self, weak context] messageIds in + if let strongSelf = self { + let _ = (strongSelf.canReadHistory.get() + |> take(1)).start(next: { [weak context] canReadHistory in + if canReadHistory { + context?.account.viewTracker.updateMarkMentionsSeenForMessageIds(messageIds: messageIds) + } + }) + } } self.preloadPages = false diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift index a5c1b5107e..d0e7ae388f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift @@ -1035,7 +1035,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio badgeContent = .text(inset: 0.0, backgroundColor: messageTheme.mediaDateAndStatusFillColor, foregroundColor: messageTheme.mediaDateAndStatusTextColor, text: string) } var animated: Bool = animated - if let updatingMedia = attributes.updatingMedia { + if let updatingMedia = attributes.updatingMedia, case .update = updatingMedia.media { state = .progress(color: messageTheme.mediaOverlayControlColors.foregroundColor, lineWidth: nil, value: CGFloat(updatingMedia.progress), cancelEnabled: true) } else if var fetchStatus = self.fetchStatus { var playerPosition: Int32? diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index 93a5f73095..d87d5094fa 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -160,7 +160,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe peer = RenderedPeer(peer: channelPeer) } } - entries.append(.message(message, peer, nil, presentationData)) + entries.append(.message(message, peer, searchResult.readStates[peer.peerId], presentationData)) } return entries diff --git a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift index 2d18b17a73..0684eab5bc 100644 --- a/submodules/TelegramUI/Sources/ContactMultiselectionController.swift +++ b/submodules/TelegramUI/Sources/ContactMultiselectionController.swift @@ -78,6 +78,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection private var initialPeersDisposable: Disposable? private let options: [ContactListAdditionalOption] private let filters: [ContactListFilter] + private let limit: Int32? init(_ params: ContactMultiselectionControllerParams) { self.params = params @@ -85,6 +86,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection self.mode = params.mode self.options = params.options self.filters = params.filters + self.limit = params.limit self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } self.titleView = CounterContollerTitleView(theme: self.presentationData.theme) @@ -228,6 +230,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection self?.presentingViewController?.dismiss(animated: true, completion: nil) } + let limit = self.limit self.contactsNode.openPeer = { [weak self] peer in if let strongSelf = self, case let .peer(peer, _, _) = peer { var updatedCount: Int? @@ -261,8 +264,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection } } case let .chats(chatsNode): - chatsNode.updateState { state in - var state = state + chatsNode.updateState { initialState in + var state = initialState if state.selectedPeerIds.contains(peer.id) { state.selectedPeerIds.remove(peer.id) removedTokenId = peer.id @@ -271,6 +274,12 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection state.selectedPeerIds.insert(peer.id) } updatedCount = state.selectedPeerIds.count + if let limit = limit, let count = updatedCount, count > limit { + updatedCount = nil + removedTokenId = nil + addedToken = nil + return initialState + } var updatedState = ContactListNodeGroupSelectionState() for peerId in state.selectedPeerIds { updatedState = updatedState.withToggledPeerId(.peer(peerId)) diff --git a/submodules/TelegramUI/Sources/StickerShimmerEffectNode.swift b/submodules/TelegramUI/Sources/StickerShimmerEffectNode.swift index 7e35bfc1ce..5beed38a08 100644 --- a/submodules/TelegramUI/Sources/StickerShimmerEffectNode.swift +++ b/submodules/TelegramUI/Sources/StickerShimmerEffectNode.swift @@ -179,90 +179,21 @@ class StickerShimmerEffectNode: ASDisplayNode { context.setFillColor(UIColor.black.cgColor) } - + if let data = data, let unpackedData = TGGUnzipData(data, 5 * 1024 * 1024), let path = String(data: unpackedData, encoding: .utf8) { + if data.count == 141 { + print() + } + var path = path + if !path.hasPrefix("z") { + path = "\(path)z" + } let reader = PathDataReader(input: path) let segments = reader.read() - - var currentX: Double = 0.0 - var currentY: Double = 0.0 - - let mul: Double = Double(size.width) / 512.0 - - for segment in segments { - switch segment.type { - case .M, .m: - let x = segment.data[0] - let y = segment.data[1] - - if segment.isAbsolute() { - currentX = x - currentY = y - } else { - currentX += x - currentY += y - } - - context.move(to: CGPoint(x: currentX * mul, y: currentY * mul)) - case .L, .l: - let x = segment.data[0] - let y = segment.data[1] - - let effectiveX: Double - let effectiveY: Double - if segment.isAbsolute() { - effectiveX = x - effectiveY = y - } else { - effectiveX = currentX + x - effectiveY = currentY + y - } - - currentX = effectiveX - currentY = effectiveY - - context.addLine(to: CGPoint(x: effectiveX * mul, y: effectiveY * mul)) - case .C, .c: - let x1 = segment.data[0] - let y1 = segment.data[1] - let x2 = segment.data[2] - let y2 = segment.data[3] - let x = segment.data[4] - let y = segment.data[5] - - let effectiveX1: Double - let effectiveY1: Double - let effectiveX2: Double - let effectiveY2: Double - let effectiveX: Double - let effectiveY: Double - - if segment.isAbsolute() { - effectiveX1 = x1 - effectiveY1 = y1 - effectiveX2 = x2 - effectiveY2 = y2 - effectiveX = x - effectiveY = y - } else { - effectiveX1 = currentX + x1 - effectiveY1 = currentY + y1 - effectiveX2 = currentX + x2 - effectiveY2 = currentY + y2 - effectiveX = currentX + x - effectiveY = currentY + y - } - - currentX = effectiveX - currentY = effectiveY - - context.addCurve(to: CGPoint(x: effectiveX * mul, y: effectiveY * mul), control1: CGPoint(x: effectiveX1 * mul, y: effectiveY1 * mul), control2: CGPoint(x: effectiveX2 * mul, y: effectiveY2 * mul)) - case .z: - context.fillPath() - default: - break - } - } + + let scale = size.width / 512.0 + context.scaleBy(x: scale, y: scale) + renderPath(segments, context: context) } else { let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), byRoundingCorners: [.topLeft, .topRight, .bottomLeft, .bottomRight], cornerRadii: CGSize(width: 10.0, height: 10.0)) UIGraphicsPushContext(context) @@ -348,6 +279,225 @@ open class PathSegment: Equatable { } } +private func renderPath(_ segments: [PathSegment], context: CGContext) { + var currentPoint: CGPoint? + var cubicPoint: CGPoint? + var quadrPoint: CGPoint? + var initialPoint: CGPoint? + + func M(_ x: Double, y: Double) { + let point = CGPoint(x: CGFloat(x), y: CGFloat(y)) + context.move(to: point) + setInitPoint(point) + } + + func m(_ x: Double, y: Double) { + if let cur = currentPoint { + let next = CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y) + context.move(to: next) + setInitPoint(next) + } else { + M(x, y: y) + } + } + + func L(_ x: Double, y: Double) { + lineTo(CGPoint(x: CGFloat(x), y: CGFloat(y))) + } + + func l(_ x: Double, y: Double) { + if let cur = currentPoint { + lineTo(CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y)) + } else { + L(x, y: y) + } + } + + func H(_ x: Double) { + if let cur = currentPoint { + lineTo(CGPoint(x: CGFloat(x), y: CGFloat(cur.y))) + } + } + + func h(_ x: Double) { + if let cur = currentPoint { + lineTo(CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(cur.y))) + } + } + + func V(_ y: Double) { + if let cur = currentPoint { + lineTo(CGPoint(x: CGFloat(cur.x), y: CGFloat(y))) + } + } + + func v(_ y: Double) { + if let cur = currentPoint { + lineTo(CGPoint(x: CGFloat(cur.x), y: CGFloat(y) + cur.y)) + } + } + + func lineTo(_ p: CGPoint) { + context.addLine(to: p) + setPoint(p) + } + + func c(_ x1: Double, y1: Double, x2: Double, y2: Double, x: Double, y: Double) { + if let cur = currentPoint { + let endPoint = CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y) + let controlPoint1 = CGPoint(x: CGFloat(x1) + cur.x, y: CGFloat(y1) + cur.y) + let controlPoint2 = CGPoint(x: CGFloat(x2) + cur.x, y: CGFloat(y2) + cur.y) + context.addCurve(to: endPoint, control1: controlPoint1, control2: controlPoint2) + setCubicPoint(endPoint, cubic: controlPoint2) + } + } + + func C(_ x1: Double, y1: Double, x2: Double, y2: Double, x: Double, y: Double) { + let endPoint = CGPoint(x: CGFloat(x), y: CGFloat(y)) + let controlPoint1 = CGPoint(x: CGFloat(x1), y: CGFloat(y1)) + let controlPoint2 = CGPoint(x: CGFloat(x2), y: CGFloat(y2)) + context.addCurve(to: endPoint, control1: controlPoint1, control2: controlPoint2) + setCubicPoint(endPoint, cubic: controlPoint2) + } + + func s(_ x2: Double, y2: Double, x: Double, y: Double) { + if let cur = currentPoint { + let nextCubic = CGPoint(x: CGFloat(x2) + cur.x, y: CGFloat(y2) + cur.y) + let next = CGPoint(x: CGFloat(x) + cur.x, y: CGFloat(y) + cur.y) + + let xy1: CGPoint + if let curCubicVal = cubicPoint { + xy1 = CGPoint(x: CGFloat(2 * cur.x) - curCubicVal.x, y: CGFloat(2 * cur.y) - curCubicVal.y) + } else { + xy1 = cur + } + context.addCurve(to: next, control1: xy1, control2: nextCubic) + setCubicPoint(next, cubic: nextCubic) + } + } + + func S(_ x2: Double, y2: Double, x: Double, y: Double) { + if let cur = currentPoint { + let nextCubic = CGPoint(x: CGFloat(x2), y: CGFloat(y2)) + let next = CGPoint(x: CGFloat(x), y: CGFloat(y)) + let xy1: CGPoint + if let curCubicVal = cubicPoint { + xy1 = CGPoint(x: CGFloat(2 * cur.x) - curCubicVal.x, y: CGFloat(2 * cur.y) - curCubicVal.y) + } else { + xy1 = cur + } + context.addCurve(to: next, control1: xy1, control2: nextCubic) + setCubicPoint(next, cubic: nextCubic) + } + } + + func z() { + context.fillPath() + } + + func setQuadrPoint(_ p: CGPoint, quadr: CGPoint) { + currentPoint = p + quadrPoint = quadr + cubicPoint = nil + } + + func setCubicPoint(_ p: CGPoint, cubic: CGPoint) { + currentPoint = p + cubicPoint = cubic + quadrPoint = nil + } + + func setInitPoint(_ p: CGPoint) { + setPoint(p) + initialPoint = p + } + + func setPoint(_ p: CGPoint) { + currentPoint = p + cubicPoint = nil + quadrPoint = nil + } + + for segment in segments { + var data = segment.data + switch segment.type { + case .M: + M(data[0], y: data[1]) + data.removeSubrange(Range(uncheckedBounds: (lower: 0, upper: 2))) + while data.count >= 2 { + L(data[0], y: data[1]) + data.removeSubrange((0 ..< 2)) + } + case .m: + m(data[0], y: data[1]) + data.removeSubrange((0 ..< 2)) + while data.count >= 2 { + l(data[0], y: data[1]) + data.removeSubrange((0 ..< 2)) + } + case .L: + while data.count >= 2 { + L(data[0], y: data[1]) + data.removeSubrange((0 ..< 2)) + } + case .l: + while data.count >= 2 { + l(data[0], y: data[1]) + data.removeSubrange((0 ..< 2)) + } + case .H: + H(data[0]) + case .h: + h(data[0]) + case .V: + V(data[0]) + case .v: + v(data[0]) + case .C: + while data.count >= 6 { + C(data[0], y1: data[1], x2: data[2], y2: data[3], x: data[4], y: data[5]) + data.removeSubrange((0 ..< 6)) + } + case .c: + while data.count >= 6 { + c(data[0], y1: data[1], x2: data[2], y2: data[3], x: data[4], y: data[5]) + data.removeSubrange((0 ..< 6)) + } + case .S: + while data.count >= 4 { + S(data[0], y2: data[1], x: data[2], y: data[3]) + data.removeSubrange((0 ..< 4)) + } + case .s: + while data.count >= 4 { + s(data[0], y2: data[1], x: data[2], y: data[3]) + data.removeSubrange((0 ..< 4)) + } +// case .Q: +// Q(data[0], y1: data[1], x: data[2], y: data[3]) +// case .q: +// q(data[0], y1: data[1], x: data[2], y: data[3]) +// case .T: +// T(data[0], y: data[1]) +// case .t: +// t(data[0], y: data[1]) +// case .A: +// A(data[0], ry: data[1], angle: data[2], largeArc: num2bool(data[3]), sweep: num2bool(data[4]), x: data[5], y: data[6]) +// case .a: +// a(data[0], ry: data[1], angle: data[2], largeArc: num2bool(data[3]), sweep: num2bool(data[4]), x: data[5], y: data[6]) +// case .E: +// E(data[0], y: data[1], w: data[2], h: data[3], startAngle: data[4], arcAngle: data[5]) +// case .e: +// e(data[0], y: data[1], w: data[2], h: data[3], startAngle: data[4], arcAngle: data[5]) + case .z: + z() + default: + print("unknown") + break + } + } +} + private class PathDataReader { private let input: String private var current: UnicodeScalar? diff --git a/submodules/TelegramUniversalVideoContent/Sources/VimeoEmbedImplementation.swift b/submodules/TelegramUniversalVideoContent/Sources/VimeoEmbedImplementation.swift index bd0db6638f..453208b145 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/VimeoEmbedImplementation.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/VimeoEmbedImplementation.swift @@ -224,6 +224,11 @@ final class VimeoEmbedImplementation: WebEmbedImplementation { playbackStatus = .buffering(initial: true, whilePlaying: false, progress: 0.0, display: true) } + if case .playing = playbackStatus, !self.started { + self.started = true + self.onPlaybackStarted?() + } + self.status = MediaPlayerStatus(generationTimestamp: self.status.generationTimestamp, duration: Double(duration), dimensions: self.status.dimensions, timestamp: newTimestamp, baseRate: 1.0, seekId: self.status.seekId, status: playbackStatus, soundEnabled: true) updateStatus(self.status) } diff --git a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift index fb7c4e070e..2de523f71a 100644 --- a/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift +++ b/submodules/TelegramUniversalVideoContent/Sources/WebEmbedPlayerNode.swift @@ -136,6 +136,10 @@ final class WebEmbedPlayerNode: ASDisplayNode, WKNavigationDelegate { } deinit { + let webView = self.webView + Queue.mainQueue().after(1.0) { + print(webView.debugDescription) + } func disableGestures(view: UIView) { if let recognizers = view.gestureRecognizers { for recognizer in recognizers { @@ -167,7 +171,6 @@ final class WebEmbedPlayerNode: ASDisplayNode, WKNavigationDelegate { } func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { - print("w") } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {