From 8446fd3ab1ddcda1e2fa51ab13c197f287cc9bd8 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 28 Feb 2024 19:22:43 +0400 Subject: [PATCH 1/8] Business fixes --- .../BrowserUI/Sources/BrowserScreen.swift | 1 + .../ChatListFilterPresetListItem.swift | 11 +- .../Sources/Node/ChatListItem.swift | 16 ++- .../Sources/ViewControllerComponent.swift | 7 + .../DrawingUI/Sources/DrawingScreen.swift | 1 + .../GalleryData/Sources/GalleryData.swift | 5 + .../ChatItemGalleryFooterContentNode.swift | 2 +- .../GalleryUI/Sources/GalleryController.swift | 15 +- .../Sources/ItemListAddressItem.swift | 9 +- .../Sources/LegacyAttachmentMenu.swift | 31 ++-- .../Sources/LegacyMediaPickerGallery.swift | 6 +- .../Sources/DeviceContactInfoController.swift | 2 +- .../Sources/ReplaceBoostScreen.swift | 1 + .../Sources/DateFormat.swift | 16 ++- .../CameraScreen/Sources/CameraScreen.swift | 1 + .../LegacyCamera/Sources/LegacyCamera.swift | 19 +-- .../Sources/MediaCutoutScreen.swift | 1 + .../Sources/MediaEditorScreen.swift | 1 + .../Sources/MediaToolsScreen.swift | 1 + .../Sources/SaveProgressScreen.swift | 1 + .../ListItems/PeerInfoScreenAddressItem.swift | 16 ++- .../PeerInfoScreenBusinessHoursItem.swift | 75 ++++++++-- .../Sources/PeerInfoScreen.swift | 39 ++++- ...aticBusinessMessageSetupChatContents.swift | 1 + .../AutomaticBusinessMessageSetupScreen.swift | 81 ++++++++++- .../QuickReplyEmptyStateComponent.swift | 6 +- .../Sources/QuickReplySetupScreen.swift | 9 +- .../Sources/BusinessHoursSetupScreen.swift | 4 +- .../QuickReplyNameAlertController.swift | 15 +- .../Sources/VideoMessageCameraScreen.swift | 1 + .../TelegramUI/Sources/ChatController.swift | 80 ++++++----- .../Sources/ChatControllerEditChat.swift | 1 + .../ChatControllerOpenAttachmentMenu.swift | 136 ++++++++++-------- .../ChatInterfaceStateContextQueries.swift | 7 +- .../TranslateUI/Sources/TranslateScreen.swift | 1 + 35 files changed, 448 insertions(+), 171 deletions(-) diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index 95e06060e3..1bfe441e3b 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -618,6 +618,7 @@ public class BrowserScreen: ViewController { bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right ), + additionalInsets: layout.additionalInsets, inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift index 70d2b0d5df..fc88aafcbb 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListItem.swift @@ -411,13 +411,17 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN strongSelf.arrowNode.isHidden = item.isAllChats if let sharedIconImage = strongSelf.sharedIconNode.image { - strongSelf.sharedIconNode.frame = CGRect(origin: CGPoint(x: strongSelf.arrowNode.frame.minX + 2.0 - sharedIconImage.size.width, y: floorToScreenPixels((layout.contentSize.height - sharedIconImage.size.height) / 2.0) + 1.0), size: sharedIconImage.size) + var sharedIconFrame = CGRect(origin: CGPoint(x: strongSelf.arrowNode.frame.minX + 2.0 - sharedIconImage.size.width, y: floorToScreenPixels((layout.contentSize.height - sharedIconImage.size.height) / 2.0) + 1.0), size: sharedIconImage.size) + if item.tagColor != nil { + sharedIconFrame.origin.x -= 34.0 + } + strongSelf.sharedIconNode.frame = sharedIconFrame } var isShared = false if case let .filter(_, _, _, data) = item.preset, data.isShared { isShared = true } - strongSelf.sharedIconNode.isHidden = !isShared || item.tagColor != nil + strongSelf.sharedIconNode.isHidden = !isShared if let tagColor = item.tagColor { let tagIconView: UIImageView @@ -534,6 +538,9 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN var sharedIconFrame = self.sharedIconNode.frame sharedIconFrame.origin.x = arrowFrame.minX + 2.0 - sharedIconFrame.width + if self.item?.tagColor != nil { + sharedIconFrame.origin.x -= 34.0 + } transition.updateFrame(node: self.sharedIconNode, frame: sharedIconFrame) if let tagIconView = self.tagIconView { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 19c729f770..f61565f5ca 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2270,11 +2270,19 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { } } else if inlineAuthorPrefix == nil, let draftState = draftState { hasDraft = true - authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor) - let draftText = stringWithAppliedEntities(draftState.text, entities: draftState.entities, baseColor: theme.messageTextColor, linkColor: theme.messageTextColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, message: nil) - attributedText = foldLineBreaks(draftText) + if !itemTags.isEmpty { + let tempAttributedText = foldLineBreaks(draftText) + let attributedTextWithDraft = NSMutableAttributedString() + attributedTextWithDraft.append(NSAttributedString(string: item.presentationData.strings.DialogList_Draft + ": ", font: textFont, textColor: theme.messageDraftTextColor)) + attributedTextWithDraft.append(tempAttributedText) + attributedText = attributedTextWithDraft + } else { + authorAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Draft, font: textFont, textColor: theme.messageDraftTextColor) + + attributedText = foldLineBreaks(draftText) + } } else if let message = messages.first { var composedString: NSMutableAttributedString @@ -3856,7 +3864,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { var badgeFrame: CGRect if textLayout.numberOfLines > 1 { - badgeFrame = CGRect(origin: CGPoint(x: textLayout.trailingLineWidth, y: textNodeFrame.height - 3.0 - badgeSize.height), size: badgeSize) + badgeFrame = CGRect(origin: CGPoint(x: textLayout.trailingLineWidth + 4.0, y: textNodeFrame.height - 3.0 - badgeSize.height), size: badgeSize) } else { let firstLineFrame = textLayout.linesRects().first ?? CGRect(origin: CGPoint(), size: textNodeFrame.size) badgeFrame = CGRect(origin: CGPoint(x: 0.0, y: firstLineFrame.height + 5.0), size: badgeSize) diff --git a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift index aedc76242e..d86b99280a 100644 --- a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift +++ b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift @@ -46,6 +46,7 @@ open class ViewControllerComponentContainer: ViewController { public let statusBarHeight: CGFloat public let navigationHeight: CGFloat public let safeInsets: UIEdgeInsets + public let additionalInsets: UIEdgeInsets public let inputHeight: CGFloat public let metrics: LayoutMetrics public let deviceMetrics: DeviceMetrics @@ -60,6 +61,7 @@ open class ViewControllerComponentContainer: ViewController { statusBarHeight: CGFloat, navigationHeight: CGFloat, safeInsets: UIEdgeInsets, + additionalInsets: UIEdgeInsets, inputHeight: CGFloat, metrics: LayoutMetrics, deviceMetrics: DeviceMetrics, @@ -73,6 +75,7 @@ open class ViewControllerComponentContainer: ViewController { self.statusBarHeight = statusBarHeight self.navigationHeight = navigationHeight self.safeInsets = safeInsets + self.additionalInsets = additionalInsets self.inputHeight = inputHeight self.metrics = metrics self.deviceMetrics = deviceMetrics @@ -98,6 +101,9 @@ open class ViewControllerComponentContainer: ViewController { if lhs.safeInsets != rhs.safeInsets { return false } + if lhs.additionalInsets != rhs.additionalInsets { + return false + } if lhs.inputHeight != rhs.inputHeight { return false } @@ -167,6 +173,7 @@ open class ViewControllerComponentContainer: ViewController { statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), + additionalInsets: layout.additionalInsets, inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index b996d290e1..8ce1998d79 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -2634,6 +2634,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right ), + additionalInsets: layout.additionalInsets, inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 916c791d54..26087254b0 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -236,6 +236,11 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati }*/ } + var source = source + if standalone { + source = .standaloneMessage(message) + } + if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") { let gallery = GalleryController(context: context, source: source ?? .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(id: message.id.peerId), customTag: chatFilterTag, chatLocationContextHolder: chatLocationContextHolder ?? Atomic(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in navigationController?.replaceTopController(controller, animated: false, ready: ready) diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 62da3c1a45..d4fd0ad64c 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -822,7 +822,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll } var canDelete: Bool - var canShare = !message.containsSecretMedia + var canShare = !message.containsSecretMedia && !Namespaces.Message.allNonRegular.contains(message.id.namespace) var canFullscreen = false diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift index 131a2319b4..9b4817a56d 100644 --- a/submodules/GalleryUI/Sources/GalleryController.swift +++ b/submodules/GalleryUI/Sources/GalleryController.swift @@ -248,13 +248,18 @@ public func galleryItemForEntry( if let result = addLocallyGeneratedEntities(text, enabledTypes: [.timecode], entities: entities, mediaDuration: file.duration.flatMap(Double.init)) { entities = result } + + var originData = GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp) + if Namespaces.Message.allNonRegular.contains(message.id.namespace) { + originData = GalleryItemOriginData(title: nil, timestamp: nil) + } let caption = galleryCaptionStringWithAppliedEntities(context: context, text: text, entities: entities, message: message) return UniversalVideoGalleryItem( context: context, presentationData: presentationData, content: content, - originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), + originData: originData, indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, @@ -348,11 +353,17 @@ public func galleryItemForEntry( } description = galleryCaptionStringWithAppliedEntities(context: context, text: descriptionText, entities: entities, message: message) } + + var originData = GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp) + if Namespaces.Message.allNonRegular.contains(message.id.namespace) { + originData = GalleryItemOriginData(title: nil, timestamp: nil) + } + return UniversalVideoGalleryItem( context: context, presentationData: presentationData, content: content, - originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), + originData: originData, indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), diff --git a/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift b/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift index 537134acd2..950b799e93 100644 --- a/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift +++ b/submodules/ItemListAddressItem/Sources/ItemListAddressItem.swift @@ -20,12 +20,12 @@ public final class ItemListAddressItem: ListViewItem, ItemListItem { let style: ItemListStyle let displayDecorations: Bool let action: (() -> Void)? - let longTapAction: (() -> Void)? + let longTapAction: ((ASDisplayNode, String) -> Void)? let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? public let tag: Any? - public init(theme: PresentationTheme, label: String, text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, selected: Bool? = nil, sectionId: ItemListSectionId, style: ItemListStyle, displayDecorations: Bool = true, action: (() -> Void)?, longTapAction: (() -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { + public init(theme: PresentationTheme, label: String, text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, selected: Bool? = nil, sectionId: ItemListSectionId, style: ItemListStyle, displayDecorations: Bool = true, action: (() -> Void)?, longTapAction: ((ASDisplayNode, String) -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, tag: Any? = nil) { self.theme = theme self.label = label self.text = text @@ -396,7 +396,10 @@ public class ItemListAddressItemNode: ListViewItemNode { } override public func longTapped() { - self.item?.longTapAction?() + guard let item = self.item else { + return + } + item.longTapAction?(self, item.text) } public var tag: Any? { diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift index e6101f8cc4..6f668dfad3 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift @@ -222,7 +222,7 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: }) } -public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitle: String?, chatLocation: ChatLocation, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal), parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: NSAttributedString, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentJpegConversionAlert: @escaping (@escaping (Bool) -> Void) -> Void, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, ((String) -> UIView?)?, @escaping () -> Void) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController { +public func legacyAttachmentMenu(context: AccountContext, peer: Peer?, threadTitle: String?, chatLocation: ChatLocation, editMediaOptions: LegacyAttachmentMenuMediaEditing?, saveEditedPhotos: Bool, allowGrouping: Bool, hasSchedule: Bool, canSendPolls: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal), parentController: LegacyController, recentlyUsedInlineBots: [Peer], initialCaption: NSAttributedString, openGallery: @escaping () -> Void, openCamera: @escaping (TGAttachmentCameraView?, TGMenuSheetController?) -> Void, openFileGallery: @escaping () -> Void, openWebSearch: @escaping () -> Void, openMap: @escaping () -> Void, openContacts: @escaping () -> Void, openPoll: @escaping () -> Void, presentSelectionLimitExceeded: @escaping () -> Void, presentCantSendMultipleFiles: @escaping () -> Void, presentJpegConversionAlert: @escaping (@escaping (Bool) -> Void) -> Void, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32, ((String) -> UIView?)?, @escaping () -> Void) -> Void, selectRecentlyUsedInlineBot: @escaping (Peer) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, present: @escaping (ViewController, Any?) -> Void) -> TGMenuSheetController { let defaultVideoPreset = defaultVideoPresetForContext(context) UserDefaults.standard.set(defaultVideoPreset.rawValue as NSNumber, forKey: "TG_preferredVideoPreset_v0") @@ -230,18 +230,20 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitl let recipientName: String if let threadTitle { recipientName = threadTitle - } else { + } else if let peer { if peer.id == context.account.peerId { recipientName = presentationData.strings.DialogList_SavedMessages } else { recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) } + } else { + recipientName = "" } let actionSheetTheme = ActionSheetControllerTheme(presentationData: presentationData) let fontSize = floor(actionSheetTheme.baseFontSize * 20.0 / 17.0) - let isSecretChat = peer.id.namespace == Namespaces.Peer.SecretChat + let isSecretChat = peer?.id.namespace == Namespaces.Peer.SecretChat let controller = TGMenuSheetController(context: parentController.context, dark: false)! controller.dismissesByOutsideTap = true @@ -314,14 +316,14 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitl carouselItem.selectionLimitExceeded = { presentSelectionLimitExceeded() } - if peer.id != context.account.peerId { + if let peer, peer.id != context.account.peerId { if peer is TelegramUser { carouselItem.hasTimer = hasSchedule } carouselItem.hasSilentPosting = true } carouselItem.hasSchedule = hasSchedule - carouselItem.reminder = peer.id == context.account.peerId + carouselItem.reminder = peer?.id == context.account.peerId carouselItem.presentScheduleController = { media, done in presentSchedulePicker(media, { time in done?(time) @@ -449,7 +451,12 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitl navigationController.setNavigationBarHidden(true, animated: false) legacyController.bind(controller: navigationController) - let recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + let recipientName: String + if let peer { + recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + } else { + recipientName = "" + } legacyController.enableSizeClassSignal = true @@ -489,12 +496,14 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitl itemViews.append(locationItem) var peerSupportsPolls = false - if peer is TelegramGroup || peer is TelegramChannel { - peerSupportsPolls = true - } else if let user = peer as? TelegramUser, let _ = user.botInfo { - peerSupportsPolls = true + if let peer { + if peer is TelegramGroup || peer is TelegramChannel { + peerSupportsPolls = true + } else if let user = peer as? TelegramUser, let _ = user.botInfo { + peerSupportsPolls = true + } } - if peerSupportsPolls && canSendMessagesToPeer(peer) && canSendPolls { + if let peer, peerSupportsPolls, canSendMessagesToPeer(peer) && canSendPolls { let pollItem = TGMenuSheetButtonItemView(title: presentationData.strings.AttachmentMenu_Poll, type: TGMenuSheetButtonTypeDefault, fontSize: fontSize, action: { [weak controller] in controller?.dismiss(animated: true) openPoll() diff --git a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift index a2495febd0..d1a9f17cae 100644 --- a/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift +++ b/submodules/MediaPickerUI/Sources/LegacyMediaPickerGallery.swift @@ -224,7 +224,7 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, }) } } - if !isScheduledMessages { + if !isScheduledMessages && peer != nil { model.interfaceView.doneLongPressed = { [weak selectionContext, weak editingContext, weak legacyController, weak model] item in if let legacyController = legacyController, let item = item as? TGMediaPickerGalleryItem, let model = model, let selectionContext = selectionContext { var effectiveHasSchedule = hasSchedule @@ -269,8 +269,8 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?, } let _ = (sendWhenOnlineAvailable - |> take(1) - |> deliverOnMainQueue).start(next: { sendWhenOnlineAvailable in + |> take(1) + |> deliverOnMainQueue).start(next: { sendWhenOnlineAvailable in let legacySheetController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil) let sheetController = TGMediaPickerSendActionSheetController(context: legacyController.context, isDark: true, sendButtonFrame: model.interfaceView.doneButtonFrame, canSendSilently: hasSilentPosting, canSendWhenOnline: sendWhenOnlineAvailable && effectiveHasSchedule, canSchedule: effectiveHasSchedule, reminder: reminder, hasTimer: hasTimer) let dismissImpl = { [weak model] in diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index 777edd846f..67827a24f1 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -510,7 +510,7 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { } else { arguments.openAddress(value) } - }, longTapAction: { + }, longTapAction: { _, _ in if selected == nil { arguments.displayCopyContextMenu(.info(index), string) } diff --git a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift index 973e4d3fb4..3f596f9ca9 100644 --- a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift +++ b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift @@ -477,6 +477,7 @@ public class ReplaceBoostScreen: ViewController { statusBarHeight: 0.0, navigationHeight: navigationHeight, safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), + additionalInsets: layout.additionalInsets, inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, diff --git a/submodules/TelegramStringFormatting/Sources/DateFormat.swift b/submodules/TelegramStringFormatting/Sources/DateFormat.swift index 54759b97c7..fc3e439fa5 100644 --- a/submodules/TelegramStringFormatting/Sources/DateFormat.swift +++ b/submodules/TelegramStringFormatting/Sources/DateFormat.swift @@ -2,7 +2,7 @@ import Foundation import TelegramPresentationData import TelegramUIPreferences -public func stringForShortTimestamp(hours: Int32, minutes: Int32, dateTimeFormat: PresentationDateTimeFormat) -> String { +public func stringForShortTimestamp(hours: Int32, minutes: Int32, dateTimeFormat: PresentationDateTimeFormat, formatAsPlainText: Bool = false) -> String { switch dateTimeFormat.timeFormat { case .regular: let hourString: String @@ -20,10 +20,18 @@ public func stringForShortTimestamp(hours: Int32, minutes: Int32, dateTimeFormat } else { periodString = "AM" } - if minutes >= 10 { - return "\(hourString):\(minutes)\u{00a0}\(periodString)" + + let spaceCharacter: String + if formatAsPlainText { + spaceCharacter = " " } else { - return "\(hourString):0\(minutes)\u{00a0}\(periodString)" + spaceCharacter = "\u{00a0}" + } + + if minutes >= 10 { + return "\(hourString):\(minutes)\(spaceCharacter)\(periodString)" + } else { + return "\(hourString):0\(minutes)\(spaceCharacter)\(periodString)" } case .military: return String(format: "%02d:%02d", arguments: [Int(hours), Int(minutes)]) diff --git a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift index 84de31e778..0eb3f6033e 100644 --- a/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift +++ b/submodules/TelegramUI/Components/CameraScreen/Sources/CameraScreen.swift @@ -2384,6 +2384,7 @@ public class CameraScreen: ViewController { bottom: bottomInset, right: layout.safeInsets.right ), + additionalInsets: layout.additionalInsets, inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, diff --git a/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift b/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift index a9c14e92e5..17eccfc3ea 100644 --- a/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift +++ b/submodules/TelegramUI/Components/LegacyCamera/Sources/LegacyCamera.swift @@ -10,7 +10,7 @@ import ShareController import LegacyUI import LegacyMediaPickerUI -public func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: NSAttributedString, hasSchedule: Bool, enablePhoto: Bool, enableVideo: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) { +public func presentedLegacyCamera(context: AccountContext, peer: Peer?, chatLocation: ChatLocation, cameraView: TGAttachmentCameraView?, menuController: TGMenuSheetController?, parentController: ViewController, attachmentController: ViewController? = nil, editingMedia: Bool, saveCapturedPhotos: Bool, mediaGrouping: Bool, initialCaption: NSAttributedString, hasSchedule: Bool, enablePhoto: Bool, enableVideo: Bool, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, recognizedQRCode: @escaping (String) -> Void = { _ in }, presentSchedulePicker: @escaping (Bool, @escaping (Int32) -> Void) -> Void, presentTimerPicker: @escaping (@escaping (Int32) -> Void) -> Void, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, dismissedWithResult: @escaping () -> Void = {}, finishedTransitionIn: @escaping () -> Void = {}) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme) legacyController.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait) @@ -18,7 +18,7 @@ public func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocat legacyController.deferScreenEdgeGestures = [.top] - let isSecretChat = peer.id.namespace == Namespaces.Peer.SecretChat + let isSecretChat = peer?.id.namespace == Namespaces.Peer.SecretChat let controller: TGCameraController if let cameraView = cameraView, let previewView = cameraView.previewView() { @@ -87,15 +87,18 @@ public func presentedLegacyCamera(context: AccountContext, peer: Peer, chatLocat controller.allowCaptionEntities = true controller.allowGrouping = mediaGrouping controller.inhibitDocumentCaptions = false - controller.recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) - if peer.id != context.account.peerId { - if peer is TelegramUser { - controller.hasTimer = hasSchedule + + if let peer { + controller.recipientName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) + if peer.id != context.account.peerId { + if peer is TelegramUser { + controller.hasTimer = hasSchedule + } + controller.hasSilentPosting = true } - controller.hasSilentPosting = true } controller.hasSchedule = hasSchedule - controller.reminder = peer.id == context.account.peerId + controller.reminder = peer?.id == context.account.peerId let screenSize = parentController.view.bounds.size var startFrame = CGRect(x: 0, y: screenSize.height, width: screenSize.width, height: screenSize.height) diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift index 2e411f9cf7..714d247480 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift @@ -267,6 +267,7 @@ public final class MediaCutoutScreen: ViewController { bottom: bottomInset, right: layout.safeInsets.right ), + additionalInsets: layout.additionalInsets, inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 2f078dad8e..0c82521387 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -3998,6 +3998,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate bottom: bottomInset, right: layout.safeInsets.right ), + additionalInsets: layout.additionalInsets, inputHeight: layoutInputHeight, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift index 250f26efe5..11f3086dcb 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaToolsScreen.swift @@ -1031,6 +1031,7 @@ public final class MediaToolsScreen: ViewController { bottom: bottomInset, right: layout.safeInsets.right ), + additionalInsets: layout.additionalInsets, inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/SaveProgressScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/SaveProgressScreen.swift index 7de762ec79..2aeb3f1dfa 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/SaveProgressScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/SaveProgressScreen.swift @@ -448,6 +448,7 @@ public final class SaveProgressScreen: ViewController { bottom: topInset, right: layout.safeInsets.right ), + additionalInsets: layout.additionalInsets, inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenAddressItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenAddressItem.swift index de95db7203..c604282113 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenAddressItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenAddressItem.swift @@ -13,7 +13,7 @@ final class PeerInfoScreenAddressItem: PeerInfoScreenItem { let text: String let imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? let action: (() -> Void)? - let longTapAction: (() -> Void)? + let longTapAction: ((ASDisplayNode, String) -> Void)? let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? init( @@ -22,7 +22,7 @@ final class PeerInfoScreenAddressItem: PeerInfoScreenItem { text: String, imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?, action: (() -> Void)?, - longTapAction: (() -> Void)? = nil, + longTapAction: ((ASDisplayNode, String) -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil ) { self.id = id @@ -66,6 +66,16 @@ private final class PeerInfoScreenAddressItemNode: PeerInfoScreenItemNode { self.addSubnode(self.bottomSeparatorNode) self.addSubnode(self.selectionNode) self.addSubnode(self.maskNode) + + self.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGesture(_:)))) + } + + @objc private func longPressGesture(_ recognizer: UILongPressGestureRecognizer) { + if case .began = recognizer.state { + if let item = self.item { + item.longTapAction?(self, item.text) + } + } } override func update(width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, hasCorners: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { @@ -81,7 +91,7 @@ private final class PeerInfoScreenAddressItemNode: PeerInfoScreenItemNode { self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor - let addressItem = ItemListAddressItem(theme: presentationData.theme, label: item.label, text: item.text, imageSignal: item.imageSignal, sectionId: 0, style: .blocks, displayDecorations: false, action: nil, longTapAction: item.longTapAction, linkItemAction: item.linkItemAction) + let addressItem = ItemListAddressItem(theme: presentationData.theme, label: item.label, text: item.text, imageSignal: item.imageSignal, sectionId: 0, style: .blocks, displayDecorations: false, action: nil, longTapAction: nil, linkItemAction: item.linkItemAction) let params = ListViewItemLayoutParams(width: width, leftInset: safeInsets.left, rightInset: safeInsets.right, availableHeight: 1000.0) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift index efdb3850fa..166c39df8f 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/ListItems/PeerInfoScreenBusinessHoursItem.swift @@ -13,7 +13,7 @@ import MultilineTextComponent import BundleIconComponent import PlainButtonComponent -private func dayBusinessHoursText(_ day: TelegramBusinessHours.WeekDay, offsetMinutes: Int) -> String { +private func dayBusinessHoursText(presentationData: PresentationData, day: TelegramBusinessHours.WeekDay, offsetMinutes: Int, formatAsPlainText: Bool = false) -> String { var businessHoursText: String = "" switch day { case .open: @@ -30,14 +30,18 @@ private func dayBusinessHoursText(_ day: TelegramBusinessHours.WeekDay, offsetMi let range = TelegramBusinessHours.WorkingTimeInterval(startMinute: range.startMinute + offsetMinutes, endMinute: range.endMinute + offsetMinutes) if !resultText.isEmpty { - resultText.append("\n") + if formatAsPlainText { + resultText.append(", ") + } else { + resultText.append("\n") + } } let startHours = clipMinutes(range.startMinute) / 60 let startMinutes = clipMinutes(range.startMinute) % 60 - let startText = stringForShortTimestamp(hours: Int32(startHours), minutes: Int32(startMinutes), dateTimeFormat: PresentationDateTimeFormat()) + let startText = stringForShortTimestamp(hours: Int32(startHours), minutes: Int32(startMinutes), dateTimeFormat: presentationData.dateTimeFormat, formatAsPlainText: formatAsPlainText) let endHours = clipMinutes(range.endMinute) / 60 let endMinutes = clipMinutes(range.endMinute) % 60 - let endText = stringForShortTimestamp(hours: Int32(endHours), minutes: Int32(endMinutes), dateTimeFormat: PresentationDateTimeFormat()) + let endText = stringForShortTimestamp(hours: Int32(endHours), minutes: Int32(endMinutes), dateTimeFormat: presentationData.dateTimeFormat, formatAsPlainText: formatAsPlainText) resultText.append("\(startText) - \(endText)") } businessHoursText += resultText @@ -51,17 +55,20 @@ final class PeerInfoScreenBusinessHoursItem: PeerInfoScreenItem { let label: String let businessHours: TelegramBusinessHours let requestLayout: (Bool) -> Void + let longTapAction: (ASDisplayNode, String) -> Void init( id: AnyHashable, label: String, businessHours: TelegramBusinessHours, - requestLayout: @escaping (Bool) -> Void + requestLayout: @escaping (Bool) -> Void, + longTapAction: @escaping (ASDisplayNode, String) -> Void ) { self.id = id self.label = label self.businessHours = businessHours self.requestLayout = requestLayout + self.longTapAction = longTapAction } func node() -> PeerInfoScreenItemNode { @@ -92,6 +99,7 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode private let activateArea: AccessibilityAreaNode private var item: PeerInfoScreenBusinessHoursItem? + private var presentationData: PresentationData? private var theme: PresentationTheme? private var currentTimezone: TimeZone @@ -168,8 +176,8 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode super.didLoad() let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) - recognizer.tapActionAtPoint = { point in - return .keepWithSingleTap + recognizer.tapActionAtPoint = { _ in + return .waitForSingleTap } recognizer.highlight = { [weak self] point in guard let strongSelf = self else { @@ -185,9 +193,55 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode case .ended: if let (gesture, _) = recognizer.lastRecognizedGestureAndLocation { switch gesture { - case .tap, .longTap: + case .tap: self.isExpanded = !self.isExpanded self.item?.requestLayout(true) + case .longTap: + if let item = self.item, let presentationData = self.presentationData { + var text = "" + + var timezoneOffsetMinutes: Int = 0 + if self.displayLocalTimezone { + var currentCalendar = Calendar(identifier: .gregorian) + currentCalendar.timeZone = TimeZone(identifier: item.businessHours.timezoneId) ?? TimeZone.current + + timezoneOffsetMinutes = (self.currentTimezone.secondsFromGMT() - currentCalendar.timeZone.secondsFromGMT()) / 60 + } + + let businessDays: [TelegramBusinessHours.WeekDay] = self.cachedDays + + for i in 0 ..< businessDays.count { + let dayTitleValue: String + //TODO:localize + switch i { + case 0: + dayTitleValue = "Monday" + case 1: + dayTitleValue = "Tuesday" + case 2: + dayTitleValue = "Wednesday" + case 3: + dayTitleValue = "Thursday" + case 4: + dayTitleValue = "Friday" + case 5: + dayTitleValue = "Saturday" + case 6: + dayTitleValue = "Sunday" + default: + dayTitleValue = " " + } + + let businessHoursText = dayBusinessHoursText(presentationData: presentationData, day: businessDays[i], offsetMinutes: timezoneOffsetMinutes, formatAsPlainText: true) + + if !text.isEmpty { + text.append("\n") + } + text.append("\(dayTitleValue): \(businessHoursText)") + } + + item.longTapAction(self, text) + } default: break } @@ -212,6 +266,7 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode } self.item = item + self.presentationData = presentationData self.theme = presentationData.theme let sideInset: CGFloat = 16.0 + safeInsets.left @@ -272,7 +327,7 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode //TODO:localize let openStatusText = isOpen ? "Open" : "Closed" - var currentDayStatusText = currentDayIndex >= 0 && currentDayIndex < businessDays.count ? dayBusinessHoursText(businessDays[currentDayIndex], offsetMinutes: timezoneOffsetMinutes) : " " + var currentDayStatusText = currentDayIndex >= 0 && currentDayIndex < businessDays.count ? dayBusinessHoursText(presentationData: presentationData, day: businessDays[currentDayIndex], offsetMinutes: timezoneOffsetMinutes) : " " if !isOpen { for range in self.cachedWeekMinuteSet.rangeView { @@ -465,7 +520,7 @@ private final class PeerInfoScreenBusinessHoursItemNode: PeerInfoScreenItemNode dayTitleValue = " " } - let businessHoursText = dayBusinessHoursText(businessDays[i], offsetMinutes: timezoneOffsetMinutes) + let businessHoursText = dayBusinessHoursText(presentationData: presentationData, day: businessDays[i], offsetMinutes: timezoneOffsetMinutes) let dayTitleSize = dayTitle.update( transition: .immediate, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 76c9930e79..1db0cbd1eb 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -481,6 +481,8 @@ private enum PeerInfoContextSubject { case bio case phone(String) case link(customLink: String?) + case businessHours(String) + case businessLocation(String) } private enum PeerInfoSettingsSection { @@ -1168,6 +1170,10 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese //TODO:localize items[.peerInfo]!.append(PeerInfoScreenBusinessHoursItem(id: 300, label: "business hours", businessHours: businessHours, requestLayout: { animated in interaction.requestLayout(animated) + }, longTapAction: { sourceNode, text in + if !text.isEmpty { + interaction.openPeerInfoContextMenu(.businessHours(text), sourceNode, nil) + } })) } @@ -1182,6 +1188,11 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese imageSignal: imageSignal, action: { interaction.openLocation() + }, + longTapAction: { sourceNode, text in + if !text.isEmpty { + interaction.openPeerInfoContextMenu(.businessLocation(text), sourceNode, nil) + } } )) } else { @@ -1190,7 +1201,12 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese label: "location", text: businessLocation.address, imageSignal: nil, - action: nil + action: nil, + longTapAction: { sourceNode, text in + if !text.isEmpty { + interaction.openPeerInfoContextMenu(.businessLocation(text), sourceNode, nil) + } + } )) } } @@ -7879,6 +7895,27 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return nil } })) + case .businessHours(let text), .businessLocation(let text): + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + + let actions: [ContextMenuAction] = [ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in + UIPasteboard.general.string = text + + self?.controller?.present(UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + })] + + let contextMenuController = makeContextMenuController(actions: actions) + controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in + if let controller = self?.controller, let sourceNode = sourceNode { + var rect = sourceNode.bounds.insetBy(dx: 0.0, dy: 2.0) + if let sourceRect = sourceRect { + rect = sourceRect.insetBy(dx: 0.0, dy: 2.0) + } + return (sourceNode, rect, controller.displayNode, controller.view.bounds) + } else { + return nil + } + })) } } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift index d4d24fd7e1..3fae6b81b8 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupChatContents.swift @@ -154,6 +154,7 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco } func quickReplyUpdateShortcut(value: String) { + self.shortcut = value if let shortcutId = self.shortcutId { self.context.engine.accountData.editMessageShortcut(id: shortcutId, shortcut: value) } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift index 37ec12c4b4..de02d36ed0 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/AutomaticBusinessMessageSetupScreen.swift @@ -188,6 +188,53 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { return true } + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } + + if self.isOn { + if self.hasAccessToAllChatsByDefault && self.additionalPeerList.categories.isEmpty && self.additionalPeerList.peers.isEmpty { + //TODO:localize + self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "No recipients selected. Reset?", actions: [ + TextAlertAction(type: .genericAction, title: "Cancel", action: { + }), + TextAlertAction(type: .defaultAction, title: "Reset", action: { + complete() + }) + ]), in: .window(.root)) + + return false + } + + if case .away = component.mode, case .custom = self.schedule { + //TODO:localize + var errorText: String? + if let customScheduleStart = self.customScheduleStart, let customScheduleEnd = self.customScheduleEnd { + if customScheduleStart >= customScheduleEnd { + errorText = "Custom schedule end time must be larger than start time." + } + } else { + if self.customScheduleStart == nil && self.customScheduleEnd == nil { + errorText = "Custom schedule time is missing." + } else if self.customScheduleStart == nil { + errorText = "Custom schedule start time is missing." + } else { + errorText = "Custom schedule end time is missing." + } + } + + if let errorText { + //TODO:localize + self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: errorText, actions: [ + TextAlertAction(type: .genericAction, title: "Cancel", action: { + }), + TextAlertAction(type: .defaultAction, title: "Reset", action: { + complete() + }) + ]), in: .window(.root)) + return false + } + } + } + var mappedCategories: TelegramBusinessRecipients.Categories = [] if self.additionalPeerList.categories.contains(.existingChats) { mappedCategories.insert(.existingChats) @@ -555,6 +602,21 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { if let awayMessage = component.initialData.awayMessage { self.isOn = true initialRecipients = awayMessage.recipients + + switch awayMessage.schedule { + case .always: + self.schedule = .always + case let .custom(beginTimestamp, endTimestamp): + self.schedule = .custom + self.customScheduleStart = Date(timeIntervalSince1970: Double(beginTimestamp)) + self.customScheduleEnd = Date(timeIntervalSince1970: Double(endTimestamp)) + case .outsideWorkingHours: + if component.initialData.businessHours != nil { + self.schedule = .outsideBusinessHours + } else { + self.schedule = .always + } + } } } @@ -839,7 +901,7 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { if case .away = component.mode { //TODO:localize var scheduleSectionItems: [AnyComponentWithIdentity] = [] - for i in 0 ..< 3 { + optionLoop: for i in 0 ..< 3 { let title: String let schedule: Schedule switch i { @@ -847,6 +909,10 @@ final class AutomaticBusinessMessageSetupScreenComponent: Component { title = "Always Send" schedule = .always case 1: + if component.initialData.businessHours == nil { + continue optionLoop + } + title = "Outside of Business Hours" schedule = .outsideBusinessHours default: @@ -1396,19 +1462,22 @@ public final class AutomaticBusinessMessageSetupScreen: ViewControllerComponentC fileprivate let greetingMessage: TelegramBusinessGreetingMessage? fileprivate let awayMessage: TelegramBusinessAwayMessage? fileprivate let additionalPeers: [EnginePeer.Id: AutomaticBusinessMessageSetupScreenComponent.AdditionalPeerList.Peer] + fileprivate let businessHours: TelegramBusinessHours? fileprivate init( accountPeer: EnginePeer?, shortcutMessageList: ShortcutMessageList, greetingMessage: TelegramBusinessGreetingMessage?, awayMessage: TelegramBusinessAwayMessage?, - additionalPeers: [EnginePeer.Id: AutomaticBusinessMessageSetupScreenComponent.AdditionalPeerList.Peer] + additionalPeers: [EnginePeer.Id: AutomaticBusinessMessageSetupScreenComponent.AdditionalPeerList.Peer], + businessHours: TelegramBusinessHours? ) { self.accountPeer = accountPeer self.shortcutMessageList = shortcutMessageList self.greetingMessage = greetingMessage self.awayMessage = awayMessage self.additionalPeers = additionalPeers + self.businessHours = businessHours } } @@ -1468,13 +1537,14 @@ public final class AutomaticBusinessMessageSetupScreen: ViewControllerComponentC context.engine.data.get( TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), TelegramEngine.EngineData.Item.Peer.BusinessGreetingMessage(id: context.account.peerId), - TelegramEngine.EngineData.Item.Peer.BusinessAwayMessage(id: context.account.peerId) + TelegramEngine.EngineData.Item.Peer.BusinessAwayMessage(id: context.account.peerId), + TelegramEngine.EngineData.Item.Peer.BusinessHours(id: context.account.peerId) ), context.engine.accountData.shortcutMessageList() |> take(1) ) |> mapToSignal { data, shortcutMessageList -> Signal in - let (accountPeer, greetingMessage, awayMessage) = data + let (accountPeer, greetingMessage, awayMessage, businessHours) = data var additionalPeerIds = Set() if let greetingMessage { @@ -1505,7 +1575,8 @@ public final class AutomaticBusinessMessageSetupScreen: ViewControllerComponentC shortcutMessageList: shortcutMessageList, greetingMessage: greetingMessage, awayMessage: awayMessage, - additionalPeers: additionalPeers + additionalPeers: additionalPeers, + businessHours: businessHours ) } } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift index f0a9d10fca..41372475cf 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplyEmptyStateComponent.swift @@ -109,7 +109,7 @@ final class QuickReplyEmptyStateComponent: Component { transition: .immediate, component: AnyComponent(LottieComponent( content: LottieComponent.AppBundleContent(name: "WriteEmoji"), - loop: true + loop: false )), environment: {}, containerSize: CGSize(width: 120.0, height: 120.0) @@ -144,9 +144,11 @@ final class QuickReplyEmptyStateComponent: Component { var centralContentsY: CGFloat = topInset + floor((buttonFrame.minY - topInset - centralContentsHeight) * 0.426) let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: centralContentsY), size: iconSize) - if let iconView = self.icon.view { + + if let iconView = self.icon.view as? LottieComponent.View { if iconView.superview == nil { self.addSubview(iconView) + iconView.playOnce() } transition.setFrame(view: iconView, frame: iconFrame) } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 3dbc6c29a1..d205f89469 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -590,6 +590,7 @@ final class QuickReplySetupScreenComponent: Component { return } + alertController?.view.endEditing(true) alertController?.dismissAnimated() self.openQuickReplyChat(shortcut: value, shortcutId: nil) } @@ -638,6 +639,7 @@ final class QuickReplySetupScreenComponent: Component { } else { component.context.engine.accountData.editMessageShortcut(id: id, shortcut: value) + alertController?.view.endEditing(true) alertController?.dismissAnimated() } } @@ -923,7 +925,7 @@ final class QuickReplySetupScreenComponent: Component { component: AnyComponent(QuickReplyEmptyStateComponent( theme: environment.theme, strings: environment.strings, - insets: UIEdgeInsets(top: environment.navigationHeight, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right), + insets: UIEdgeInsets(top: environment.navigationHeight, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom + environment.additionalInsets.bottom, right: environment.safeInsets.right), action: { [weak self] in guard let self else { return @@ -955,7 +957,7 @@ final class QuickReplySetupScreenComponent: Component { statusBarHeight = max(statusBarHeight, 1.0) } - var listBottomInset = environment.safeInsets.bottom + var listBottomInset = environment.safeInsets.bottom + environment.additionalInsets.bottom let navigationHeight = self.updateNavigationBar( component: component, theme: environment.theme, @@ -1090,12 +1092,13 @@ final class QuickReplySetupScreenComponent: Component { animateScale: false, animateContents: false ))), - insets: UIEdgeInsets(top: 4.0, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom, right: environment.safeInsets.right) + insets: UIEdgeInsets(top: 4.0, left: environment.safeInsets.left, bottom: environment.safeInsets.bottom + environment.additionalInsets.bottom, right: environment.safeInsets.right) )), environment: {}, containerSize: availableSize ) let selectionPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - selectionPanelSize.height), size: selectionPanelSize) + print("selectionPanelFrame: \(selectionPanelFrame.minY)") listBottomInset = selectionPanelSize.height if let selectionPanelView = selectionPanel.view { var animateIn = false diff --git a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift index 334ac82839..7db651f4f2 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessHoursSetupScreen/Sources/BusinessHoursSetupScreen.swift @@ -304,9 +304,7 @@ final class BusinessHoursSetupScreenComponent: Component { let businessHours = try self.daysState.asBusinessHours() let _ = component.context.engine.accountData.updateAccountBusinessHours(businessHours: businessHours).startStandalone() return true - } catch let error { - let _ = error - + } catch _ { let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } //TODO:localize self.environment?.controller()?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "Business hours are intersecting. Reset?", actions: [ diff --git a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift index 6779c39757..1d3e282c89 100644 --- a/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift +++ b/submodules/TelegramUI/Components/Settings/QuickReplyNameAlertController/Sources/QuickReplyNameAlertController.swift @@ -225,11 +225,7 @@ public final class QuickReplyNameAlertContentNode: AlertContentNode { private let hapticFeedback = HapticFeedback() - var complete: (() -> Void)? { - didSet { - self.inputFieldNode.complete = self.complete - } - } + var complete: (() -> Void)? override public var dismissOnOutsideTap: Bool { return self.isUserInteractionEnabled @@ -295,6 +291,15 @@ public final class QuickReplyNameAlertContentNode: AlertContentNode { } self.updateTheme(theme) + + self.inputFieldNode.complete = { [weak self] in + guard let self else { + return + } + if let lastNode = self.actionNodes.last, lastNode.actionEnabled { + self.complete?() + } + } } deinit { diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 850d7d4883..7f8af9e065 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -1094,6 +1094,7 @@ public class VideoMessageCameraScreen: ViewController { bottom: 44.0, right: layout.safeInsets.right ), + additionalInsets: layout.additionalInsets, inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 0ed8b8b15d..e40ab50ba2 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9494,15 +9494,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } + self.updateChatPresentationInterfaceState(animated: true, interactive: false, { + $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } + }) + if !self.presentationInterfaceState.isPremium { let controller = PremiumIntroScreen(context: self.context, source: .settings) self.push(controller) return } - self.updateChatPresentationInterfaceState(animated: true, interactive: false, { - $0.updatedInterfaceState { $0.withUpdatedReplyMessageSubject(nil).withUpdatedComposeInputState(ChatTextInputState(inputText: NSAttributedString(string: ""))).withUpdatedComposeDisableUrlPreviews([]) } - }) self.context.engine.accountData.sendMessageShortcut(peerId: peerId, id: shortcutId) /*self.chatDisplayNode.setupSendActionOnViewUpdate({ [weak self] in @@ -9906,37 +9907,36 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { - return - } var bannedMediaInput = false - if let channel = peer as? TelegramChannel { - if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil { - bannedMediaInput = true - } else if channel.hasBannedPermission(.banSendVoice) != nil { - if channel.hasBannedPermission(.banSendInstantVideos) == nil { - strongSelf.displayMediaRecordingTooltip() - return + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + if let channel = peer as? TelegramChannel { + if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil { + bannedMediaInput = true + } else if channel.hasBannedPermission(.banSendVoice) != nil { + if channel.hasBannedPermission(.banSendInstantVideos) == nil { + strongSelf.displayMediaRecordingTooltip() + return + } + } else if channel.hasBannedPermission(.banSendInstantVideos) != nil { + if channel.hasBannedPermission(.banSendVoice) == nil { + strongSelf.displayMediaRecordingTooltip() + return + } } - } else if channel.hasBannedPermission(.banSendInstantVideos) != nil { - if channel.hasBannedPermission(.banSendVoice) == nil { - strongSelf.displayMediaRecordingTooltip() - return - } - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) { - bannedMediaInput = true - } else if group.hasBannedPermission(.banSendVoice) { - if !group.hasBannedPermission(.banSendInstantVideos) { - strongSelf.displayMediaRecordingTooltip() - return - } - } else if group.hasBannedPermission(.banSendInstantVideos) { - if !group.hasBannedPermission(.banSendVoice) { - strongSelf.displayMediaRecordingTooltip() - return + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) { + bannedMediaInput = true + } else if group.hasBannedPermission(.banSendVoice) { + if !group.hasBannedPermission(.banSendInstantVideos) { + strongSelf.displayMediaRecordingTooltip() + return + } + } else if group.hasBannedPermission(.banSendInstantVideos) { + if !group.hasBannedPermission(.banSendVoice) { + strongSelf.displayMediaRecordingTooltip() + return + } } } } @@ -14202,10 +14202,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func requestVideoRecorder() { - guard let peerId = self.chatLocation.peerId else { - return - } - if self.videoRecorderValue == nil { if let currentInputPanelFrame = self.chatDisplayNode.currentInputPanelFrame() { if self.recorderFeedback == nil { @@ -14219,6 +14215,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } var isBot = false + + var allowLiveUpload = false + var viewOnceAvailable = false + if let peerId = self.chatLocation.peerId { + allowLiveUpload = peerId.namespace != Namespaces.Peer.SecretChat + viewOnceAvailable = !isScheduledMessages && peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId && !isBot + } else if case .customChatContents = self.chatLocation { + allowLiveUpload = true + } + if let user = self.presentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil { isBot = true } @@ -14226,8 +14232,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let controller = VideoMessageCameraScreen( context: self.context, updatedPresentationData: self.updatedPresentationData, - allowLiveUpload: peerId.namespace != Namespaces.Peer.SecretChat, - viewOnceAvailable: !isScheduledMessages && peerId.namespace == Namespaces.Peer.CloudUser && peerId != self.context.account.peerId && !isBot, + allowLiveUpload: allowLiveUpload, + viewOnceAvailable: viewOnceAvailable, inputPanelFrame: (currentInputPanelFrame, self.chatDisplayNode.inputNode != nil), chatNode: self.chatDisplayNode.historyNode, completion: { [weak self] message, silentPosting, scheduleTime in diff --git a/submodules/TelegramUI/Sources/ChatControllerEditChat.swift b/submodules/TelegramUI/Sources/ChatControllerEditChat.swift index a9c0c525e2..2df490c037 100644 --- a/submodules/TelegramUI/Sources/ChatControllerEditChat.swift +++ b/submodules/TelegramUI/Sources/ChatControllerEditChat.swift @@ -49,6 +49,7 @@ extension ChatControllerImpl { } } else { self.chatTitleView?.titleContent = .custom("\(value)", nil, false) + alertController?.view.endEditing(true) alertController?.dismissAnimated() if case let .customChatContents(customChatContents) = self.subject { diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 73b19737f5..f4aafd30c8 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -320,16 +320,12 @@ extension ChatControllerImpl { attachmentController?.dismiss(animated: true) self?.presentICloudFileGallery() }, send: { [weak self] mediaReference in - guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId else { + guard let strongSelf = self else { return } + let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, threadId: strongSelf.chatLocation.threadId, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) - let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: strongSelf.transformEnqueueMessages([message])) - |> deliverOnMainQueue).startStandalone(next: { [weak self] _ in - if let strongSelf = self, strongSelf.presentationInterfaceState.subject != .scheduledMessages { - strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() - } - }) + strongSelf.sendMessages([message], media: true) }) if let controller = controller as? AttachmentFileControllerImpl { let _ = currentFilesController.swap(controller) @@ -684,26 +680,28 @@ extension ChatControllerImpl { return entry ?? GeneratedMediaStoreSettings.defaultSettings } |> deliverOnMainQueue).startStandalone(next: { [weak self] settings in - guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { + guard let strongSelf = self else { return } strongSelf.chatDisplayNode.dismissInput() var bannedSendMedia: (Int32, Bool)? var canSendPolls = true - if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendMedia) { - bannedSendMedia = value - } - if channel.hasBannedPermission(.banSendPolls) != nil { - canSendPolls = false - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendMedia) { - bannedSendMedia = (Int32.max, false) - } - if group.hasBannedPermission(.banSendPolls) { - canSendPolls = false + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + if let channel = peer as? TelegramChannel { + if let value = channel.hasBannedPermission(.banSendMedia) { + bannedSendMedia = value + } + if channel.hasBannedPermission(.banSendPolls) != nil { + canSendPolls = false + } + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(.banSendMedia) { + bannedSendMedia = (Int32.max, false) + } + if group.hasBannedPermission(.banSendPolls) { + canSendPolls = false + } } } @@ -771,11 +769,15 @@ extension ChatControllerImpl { } var slowModeEnabled = false - if let channel = peer as? TelegramChannel, channel.isRestrictedBySlowmode { - slowModeEnabled = true + var hasSchedule = false + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + if let channel = peer as? TelegramChannel, channel.isRestrictedBySlowmode { + slowModeEnabled = true + } + hasSchedule = strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat } - let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, canSendPolls: canSendPolls, updatedPresentationData: strongSelf.updatedPresentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText, openGallery: { + let controller = legacyAttachmentMenu(context: strongSelf.context, peer: strongSelf.presentationInterfaceState.renderedPeer?.peer, threadTitle: strongSelf.threadInfo?.title, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: hasSchedule, canSendPolls: canSendPolls, updatedPresentationData: strongSelf.updatedPresentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText, openGallery: { self?.presentOldMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals, silentPosting, scheduleTime in if !inputText.string.isEmpty { strongSelf.clearInputText() @@ -787,7 +789,7 @@ extension ChatControllerImpl { } }) }, openCamera: { [weak self] cameraView, menuController in - if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + if let strongSelf = self { var enablePhoto = true var enableVideo = true @@ -798,19 +800,21 @@ extension ChatControllerImpl { var bannedSendPhotos: (Int32, Bool)? var bannedSendVideos: (Int32, Bool)? - if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = value - } - if let value = channel.hasBannedPermission(.banSendVideos) { - bannedSendVideos = value - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendVideos) { - bannedSendVideos = (Int32.max, false) + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + if let channel = peer as? TelegramChannel { + if let value = channel.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = value + } + if let value = channel.hasBannedPermission(.banSendVideos) { + bannedSendVideos = value + } + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = (Int32.max, false) + } + if group.hasBannedPermission(.banSendVideos) { + bannedSendVideos = (Int32.max, false) + } } } @@ -821,7 +825,15 @@ extension ChatControllerImpl { enableVideo = false } - presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: peer.id.namespace != Namespaces.Peer.SecretChat, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in + var storeCapturedPhotos = false + var hasSchedule = false + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + storeCapturedPhotos = peer.id.namespace != Namespaces.Peer.SecretChat + + hasSchedule = strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat + } + + presentedLegacyCamera(context: strongSelf.context, peer: strongSelf.presentationInterfaceState.renderedPeer?.peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: menuController, parentController: strongSelf, editingMedia: editMediaOptions != nil, saveCapturedPhotos: storeCapturedPhotos, mediaGrouping: true, initialCaption: inputText, hasSchedule: hasSchedule, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in if let strongSelf = self { if editMediaOptions != nil { strongSelf.editMessageMediaWithLegacySignals(signals!) @@ -979,7 +991,7 @@ extension ChatControllerImpl { func presentICloudFileGallery(editingMessage: Bool = false) { let _ = (self.context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId), + TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId), TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false), TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true) ) @@ -1610,17 +1622,12 @@ extension ChatControllerImpl { } func openCamera(cameraView: TGAttachmentCameraView? = nil) { - guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { - return - } - let _ = peer - let _ = (self.context.sharedContext.accountManager.transaction { transaction -> GeneratedMediaStoreSettings in let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self) return entry ?? GeneratedMediaStoreSettings.defaultSettings } |> deliverOnMainQueue).startStandalone(next: { [weak self] settings in - guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { + guard let strongSelf = self else { return } @@ -1634,19 +1641,21 @@ extension ChatControllerImpl { var bannedSendPhotos: (Int32, Bool)? var bannedSendVideos: (Int32, Bool)? - if let channel = peer as? TelegramChannel { - if let value = channel.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = value - } - if let value = channel.hasBannedPermission(.banSendVideos) { - bannedSendVideos = value - } - } else if let group = peer as? TelegramGroup { - if group.hasBannedPermission(.banSendPhotos) { - bannedSendPhotos = (Int32.max, false) - } - if group.hasBannedPermission(.banSendVideos) { - bannedSendVideos = (Int32.max, false) + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + if let channel = peer as? TelegramChannel { + if let value = channel.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = value + } + if let value = channel.hasBannedPermission(.banSendVideos) { + bannedSendVideos = value + } + } else if let group = peer as? TelegramGroup { + if group.hasBannedPermission(.banSendPhotos) { + bannedSendPhotos = (Int32.max, false) + } + if group.hasBannedPermission(.banSendVideos) { + bannedSendVideos = (Int32.max, false) + } } } @@ -1657,10 +1666,15 @@ extension ChatControllerImpl { enableVideo = false } - let storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat + var storeCapturedMedia = false + var hasSchedule = false + if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + storeCapturedMedia = peer.id.namespace != Namespaces.Peer.SecretChat + hasSchedule = strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat + } let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText - presentedLegacyCamera(context: strongSelf.context, peer: peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in + presentedLegacyCamera(context: strongSelf.context, peer: strongSelf.presentationInterfaceState.renderedPeer?.peer, chatLocation: strongSelf.chatLocation, cameraView: cameraView, menuController: nil, parentController: strongSelf, attachmentController: self?.attachmentController, editingMedia: false, saveCapturedPhotos: storeCapturedMedia, mediaGrouping: true, initialCaption: inputText, hasSchedule: hasSchedule, enablePhoto: enablePhoto, enableVideo: enableVideo, sendMessagesWithSignals: { [weak self] signals, silentPosting, scheduleTime in if let strongSelf = self { strongSelf.enqueueMediaMessages(signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime > 0 ? scheduleTime : nil) if !inputText.string.isEmpty { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index fb7f548a16..4c2cf03d08 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -243,8 +243,11 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee return false } var sortedCommands = filteredCommands.map(ChatInputTextCommand.command) - for shortcut in shortcuts { - sortedCommands.append(.shortcut(shortcut)) + if !shortcuts.isEmpty { + sortedCommands.removeAll() + for shortcut in shortcuts { + sortedCommands.append(.shortcut(shortcut)) + } } return { _ in return .commands(ChatInputQueryCommandsResult( commands: sortedCommands, diff --git a/submodules/TranslateUI/Sources/TranslateScreen.swift b/submodules/TranslateUI/Sources/TranslateScreen.swift index ba0f4d1ba7..94f61b42ae 100644 --- a/submodules/TranslateUI/Sources/TranslateScreen.swift +++ b/submodules/TranslateUI/Sources/TranslateScreen.swift @@ -720,6 +720,7 @@ public class TranslateScreen: ViewController { statusBarHeight: 0.0, navigationHeight: navigationHeight, safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), + additionalInsets: layout.additionalInsets, inputHeight: layout.inputHeight ?? 0.0, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, From ce3a6c32023f140cf2c17a5ad19ba4cdd0a18912 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 28 Feb 2024 23:17:35 +0400 Subject: [PATCH 2/8] Various fixes --- .../Sources/PremiumIntroScreen.swift | 99 ++++++++++++++++--- .../Sources/PremiumLimitsListScreen.swift | 66 ++++++++----- .../TelegramNotices/Sources/Notices.swift | 26 +++++ .../AvatarStoryIndicatorComponent.swift | 9 +- 4 files changed, 161 insertions(+), 39 deletions(-) diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index d5d9c5fbd6..21d6f91c16 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -1531,8 +1531,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { ApplicationSpecificNotice.dismissedPremiumColorsBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedMessageTagsBadge(accountManager: context.sharedContext.accountManager), ApplicationSpecificNotice.dismissedLastSeenBadge(accountManager: context.sharedContext.accountManager), - ApplicationSpecificNotice.dismissedMessagePrivacyBadge(accountManager: context.sharedContext.accountManager) - ).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge, dismissedMessageTagsBadge, dismissedLastSeenBadge, dismissedMessagePrivacyBadge in + ApplicationSpecificNotice.dismissedMessagePrivacyBadge(accountManager: context.sharedContext.accountManager), + ApplicationSpecificNotice.dismissedBusinessBadge(accountManager: context.sharedContext.accountManager) + ).startStrict(next: { [weak self] dismissedPremiumAppIconsBadge, dismissedPremiumWallpapersBadge, dismissedPremiumColorsBadge, dismissedMessageTagsBadge, dismissedLastSeenBadge, dismissedMessagePrivacyBadge, dismissedBusinessBadge in guard let self else { return } @@ -1552,8 +1553,9 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { if !dismissedMessagePrivacyBadge { newPerks.append(PremiumPerk.messagePrivacy.identifier) } - //TODO: - newPerks.append(PremiumPerk.business.identifier) + if !dismissedBusinessBadge { + newPerks.append(PremiumPerk.business.identifier) + } self.newPerks = newPerks self.updated() }) @@ -1937,17 +1939,31 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { if case .business = context.component.mode, case .business = perk { continue } + + let isNew = state.newPerks.contains(perk.identifier) + let titleComponent = AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: perk.title(strings: strings), + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 0 + )) + + let titleCombinedComponent: AnyComponent + if isNew { + titleCombinedComponent = AnyComponent(HStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: titleComponent), + AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(BadgeComponent(color: gradientColors[i], text: strings.Premium_New))) + ], spacing: 5.0)) + } else { + titleCombinedComponent = AnyComponent(HStack([AnyComponentWithIdentity(id: AnyHashable(0), component: titleComponent)], spacing: 0.0)) + } + perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, title: AnyComponent(VStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: perk.title(strings: strings), - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 0 - ))), + AnyComponentWithIdentity(id: AnyHashable(0), component: titleCombinedComponent), AnyComponentWithIdentity(id: AnyHashable(1), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( string: perk.subtitle(strings: strings), @@ -2013,6 +2029,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let _ = ApplicationSpecificNotice.setDismissedMessagePrivacyBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() case .business: demoSubject = .business + let _ = ApplicationSpecificNotice.setDismissedBusinessBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone() default: demoSubject = .doubleLimits } @@ -3721,3 +3738,61 @@ private final class EmojiActionIconComponent: Component { return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) } } + +private final class BadgeComponent: CombinedComponent { + let color: UIColor + let text: String + + init( + color: UIColor, + text: String + ) { + self.color = color + self.text = text + } + + static func ==(lhs: BadgeComponent, rhs: BadgeComponent) -> Bool { + if lhs.color != rhs.color { + return false + } + if lhs.text != rhs.text { + return false + } + return true + } + + static var body: Body { + let badgeBackground = Child(RoundedRectangle.self) + let badgeText = Child(MultilineTextComponent.self) + + return { context in + let component = context.component + + let badgeText = badgeText.update( + component: MultilineTextComponent(text: .plain(NSAttributedString(string: component.text, font: Font.semibold(11.0), textColor: .white))), + availableSize: context.availableSize, + transition: context.transition + ) + + let badgeSize = CGSize(width: badgeText.size.width + 7.0, height: 16.0) + let badgeBackground = badgeBackground.update( + component: RoundedRectangle( + color: component.color, + cornerRadius: 5.0 + ), + availableSize: badgeSize, + transition: context.transition + ) + + context.add(badgeBackground + .position(CGPoint(x: badgeSize.width / 2.0, y: badgeSize.height / 2.0)) + ) + + context.add(badgeText + .position(CGPoint(x: badgeSize.width / 2.0, y: badgeSize.height / 2.0)) + ) + + return badgeSize + } + } +} diff --git a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift index 3ae7dc6294..4fd0a10755 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitsListScreen.swift @@ -384,7 +384,27 @@ public class PremiumLimitsListScreen: ViewController { let theme = self.presentationData.theme let strings = self.presentationData.strings - if let stickers = self.stickers, let appIcons = self.appIcons, let configuration = self.promoConfiguration { + let videos: [String: TelegramMediaFile] = self.promoConfiguration?.videos ?? [:] + let stickers = self.stickers ?? [] + let appIcons = self.appIcons ?? [] + + let isReady: Bool + switch controller.subject { + case .premiumStickers: + isReady = !stickers.isEmpty + case .appIcons: + isReady = !appIcons.isEmpty + case .stories: + isReady = true + case .doubleLimits: + isReady = true + case .business: + isReady = true + default: + isReady = !videos.isEmpty + } + + if isReady { let context = controller.context let textColor = theme.actionSheet.primaryTextColor @@ -482,7 +502,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .bottom, - videoFile: configuration.videos["more_upload"], + videoFile: videos["more_upload"], decoration: .dataRain )), title: strings.Premium_UploadSize, @@ -500,7 +520,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["faster_download"], + videoFile: videos["faster_download"], decoration: .fasterStars )), title: strings.Premium_FasterSpeed, @@ -518,7 +538,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["voice_to_text"], + videoFile: videos["voice_to_text"], decoration: .badgeStars )), title: strings.Premium_VoiceToText, @@ -536,7 +556,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .bottom, - videoFile: configuration.videos["no_ads"], + videoFile: videos["no_ads"], decoration: .swirlStars )), title: strings.Premium_NoAds, @@ -554,7 +574,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["infinite_reactions"], + videoFile: videos["infinite_reactions"], decoration: .swirlStars )), title: strings.Premium_InfiniteReactions, @@ -593,7 +613,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["emoji_status"], + videoFile: videos["emoji_status"], decoration: .badgeStars )), title: strings.Premium_EmojiStatus, @@ -611,7 +631,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["advanced_chat_management"], + videoFile: videos["advanced_chat_management"], decoration: .swirlStars )), title: strings.Premium_ChatManagement, @@ -629,7 +649,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["profile_badge"], + videoFile: videos["profile_badge"], decoration: .badgeStars )), title: strings.Premium_Badge, @@ -647,7 +667,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["animated_userpics"], + videoFile: videos["animated_userpics"], decoration: .swirlStars )), title: strings.Premium_Avatar, @@ -681,7 +701,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .bottom, - videoFile: configuration.videos["animated_emoji"], + videoFile: videos["animated_emoji"], decoration: .emoji )), title: strings.Premium_AnimatedEmoji, @@ -700,7 +720,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["translations"], + videoFile: videos["translations"], decoration: .hello )), title: strings.Premium_Translation, @@ -718,7 +738,7 @@ public class PremiumLimitsListScreen: ViewController { content: AnyComponent(PhoneDemoComponent( context: context, position: .top, - videoFile: configuration.videos["peer_colors"], + videoFile: videos["peer_colors"], decoration: .badgeStars )), title: strings.Premium_Colors, @@ -737,7 +757,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["wallpapers"], + videoFile: videos["wallpapers"], decoration: .swirlStars )), title: strings.Premium_Wallpapers, @@ -756,7 +776,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["saved_tags"], + videoFile: videos["saved_tags"], decoration: .tag )), title: strings.Premium_MessageTags, @@ -775,7 +795,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["last_seen"], + videoFile: videos["last_seen"], decoration: .badgeStars )), title: strings.Premium_LastSeen, @@ -794,7 +814,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["message_privacy"], + videoFile: videos["message_privacy"], decoration: .swirlStars )), title: strings.Premium_MessagePrivacy, @@ -846,7 +866,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["business_location"], + videoFile: videos["business_location"], decoration: .business )), title: strings.Business_Location, @@ -866,7 +886,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["business_hours"], + videoFile: videos["business_hours"], decoration: .business )), title: strings.Business_OpeningHours, @@ -886,7 +906,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["quick_replies"], + videoFile: videos["quick_replies"], decoration: .business )), title: strings.Business_QuickReplies, @@ -906,7 +926,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["greeting_message"], + videoFile: videos["greeting_message"], decoration: .business )), title: strings.Business_GreetingMessages, @@ -926,7 +946,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["away_message"], + videoFile: videos["away_message"], decoration: .business )), title: strings.Business_AwayMessages, @@ -946,7 +966,7 @@ public class PremiumLimitsListScreen: ViewController { context: context, position: .top, model: .island, - videoFile: configuration.videos["business_bots"], + videoFile: videos["business_bots"], decoration: .business )), title: strings.Business_Chatbots, diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 627928f5ac..7eee7c482b 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -199,6 +199,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case savedMessageTagLabelSuggestion = 65 case dismissedLastSeenBadge = 66 case dismissedMessagePrivacyBadge = 67 + case dismissedBusinessBadge = 68 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -529,6 +530,10 @@ private struct ApplicationSpecificNoticeKeys { static func dismissedMessagePrivacyBadge() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedMessagePrivacyBadge.key) } + + static func dismissedBusinessBadge() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessBadge.key) + } } public struct ApplicationSpecificNotice { @@ -2223,4 +2228,25 @@ public struct ApplicationSpecificNotice { } |> take(1) } + + public static func setDismissedBusinessBadge(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Void in + if let entry = CodableEntry(ApplicationSpecificBoolNotice()) { + transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessBadge(), entry) + } + } + |> ignoreValues + } + + public static func dismissedBusinessBadge(accountManager: AccountManager) -> Signal { + return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessBadge()) + |> map { view -> Bool in + if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) { + return true + } else { + return false + } + } + |> take(1) + } } diff --git a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift index ebe68f2d33..5cd33daa87 100644 --- a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift @@ -345,16 +345,17 @@ public final class AvatarStoryIndicatorComponent: Component { } let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! - - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + if let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations) { + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions()) + } } } } else { let lineWidth: CGFloat = component.hasUnseen ? component.activeLineWidth : component.inactiveLineWidth context.setLineWidth(lineWidth) if component.isRoundedRect { - context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5), cornerRadius: floor(diameter * 0.25)).cgPath) + let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5), cornerRadius: floor(diameter * 0.27)) + context.addPath(path.cgPath) } else { context.addEllipse(in: CGRect(origin: CGPoint(x: size.width * 0.5 - diameter * 0.5, y: size.height * 0.5 - diameter * 0.5), size: size).insetBy(dx: lineWidth * 0.5, dy: lineWidth * 0.5)) } From c48cadbd7814347efd33a5e05dd963aa3f61a58d Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Wed, 28 Feb 2024 23:35:14 +0400 Subject: [PATCH 3/8] Folder colors --- .../Sources/PeerNameColors.swift | 19 ++ .../ChatListFilterPresetController.swift | 4 +- .../ChatListFilterPresetListController.swift | 2 +- .../Sources/Node/ChatListItem.swift | 2 +- .../Sources/Node/ChatListNode.swift | 42 ++- submodules/TelegramApi/Sources/Api0.swift | 7 +- submodules/TelegramApi/Sources/Api27.swift | 138 ++-------- submodules/TelegramApi/Sources/Api28.swift | 230 ++++++++++------ submodules/TelegramApi/Sources/Api29.swift | 242 ++++++---------- submodules/TelegramApi/Sources/Api30.swift | 164 +++++++++++ submodules/TelegramApi/Sources/Api32.swift | 27 +- submodules/TelegramApi/Sources/Api4.swift | 56 ++-- .../Peers/ChatListFiltering.swift | 259 +++++++++++------- .../Peers/TelegramEnginePeers.swift | 8 +- .../Sources/PeerNameColorItem.swift | 36 ++- .../Sources/ChannelAppearanceScreen.swift | 4 +- .../Sources/PeerNameColorScreen.swift | 2 +- 17 files changed, 704 insertions(+), 538 deletions(-) diff --git a/submodules/AccountContext/Sources/PeerNameColors.swift b/submodules/AccountContext/Sources/PeerNameColors.swift index 0bca8e2346..1966a168ea 100644 --- a/submodules/AccountContext/Sources/PeerNameColors.swift +++ b/submodules/AccountContext/Sources/PeerNameColors.swift @@ -80,6 +80,7 @@ public class PeerNameColors: Equatable { colors: defaultSingleColors, darkColors: [:], displayOrder: [5, 3, 1, 0, 2, 4, 6], + chatFolderTagDisplayOrder: [5, 3, 1, 0, 2, 4, 6], profileColors: [:], profileDarkColors: [:], profilePaletteColors: [:], @@ -97,6 +98,8 @@ public class PeerNameColors: Equatable { public let darkColors: [Int32: Colors] public let displayOrder: [Int32] + public let chatFolderTagDisplayOrder: [Int32] + public let profileColors: [Int32: Colors] public let profileDarkColors: [Int32: Colors] public let profilePaletteColors: [Int32: Colors] @@ -119,6 +122,16 @@ public class PeerNameColors: Equatable { } } + public func getChatFolderTag(_ color: PeerNameColor, dark: Bool = false) -> Colors { + if dark, let colors = self.darkColors[color.rawValue] { + return colors + } else if let colors = self.colors[color.rawValue] { + return colors + } else { + return PeerNameColors.defaultSingleColors[5]! + } + } + public func getProfile(_ color: PeerNameColor, dark: Bool = false, subject: Subject = .background) -> Colors { switch subject { case .background: @@ -152,6 +165,7 @@ public class PeerNameColors: Equatable { colors: [Int32: Colors], darkColors: [Int32: Colors], displayOrder: [Int32], + chatFolderTagDisplayOrder: [Int32], profileColors: [Int32: Colors], profileDarkColors: [Int32: Colors], profilePaletteColors: [Int32: Colors], @@ -166,6 +180,7 @@ public class PeerNameColors: Equatable { self.colors = colors self.darkColors = darkColors self.displayOrder = displayOrder + self.chatFolderTagDisplayOrder = chatFolderTagDisplayOrder self.profileColors = profileColors self.profileDarkColors = profileDarkColors self.profilePaletteColors = profilePaletteColors @@ -257,6 +272,7 @@ public class PeerNameColors: Equatable { colors: colors, darkColors: darkColors, displayOrder: displayOrder, + chatFolderTagDisplayOrder: PeerNameColors.defaultValue.chatFolderTagDisplayOrder, profileColors: profileColors, profileDarkColors: profileDarkColors, profilePaletteColors: profilePaletteColors, @@ -280,6 +296,9 @@ public class PeerNameColors: Equatable { if lhs.displayOrder != rhs.displayOrder { return false } + if lhs.chatFolderTagDisplayOrder != rhs.chatFolderTagDisplayOrder { + return false + } if lhs.profileColors != rhs.profileColors { return false } diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift index b1198e523a..8e81920a02 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift @@ -480,7 +480,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry { return PeerNameColorItem( theme: presentationData.theme, colors: colors, - isProfile: true, + mode: .folderTag, displayEmptyColor: true, currentColor: isPremium ? color : nil, isLocked: !isPremium, @@ -643,7 +643,7 @@ private func chatListFilterPresetControllerEntries(context: AccountContext, pres } var resolvedColor: PeerNameColors.Colors? if let tagColor { - resolvedColor = context.peerNameColors.getProfile(tagColor, dark: presentationData.theme.overallDarkAppearance, subject: .palette) + resolvedColor = context.peerNameColors.getChatFolderTag(tagColor, dark: presentationData.theme.overallDarkAppearance) } entries.append(.tagColorHeader(name: state.name, color: resolvedColor, isPremium: isPremium)) diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 3b496c6b11..dd7ccb6c89 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -190,7 +190,7 @@ private enum ChatListFilterPresetListEntry: ItemListNodeEntry { if displayTags, case let .filter(_, _, _, data) = preset { let tagColor = data.color if let tagColor { - resolvedColor = arguments.context.peerNameColors.getProfile(tagColor, dark: presentationData.theme.overallDarkAppearance, subject: .palette).main + resolvedColor = arguments.context.peerNameColors.getChatFolderTag(tagColor, dark: presentationData.theme.overallDarkAppearance).main } } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index f61565f5ca..0c2544e54f 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -328,7 +328,7 @@ private final class ChatListItemTagListComponent: Component { itemId = tag.id let tagColor = PeerNameColor(rawValue: tag.colorId) - let resolvedColor = component.context.peerNameColors.getProfile(tagColor, dark: component.theme.overallDarkAppearance, subject: .palette) + let resolvedColor = component.context.peerNameColors.getChatFolderTag(tagColor, dark: component.theme.overallDarkAppearance) itemTitle = tag.title.uppercased() itemBackgroundColor = resolvedColor.main.withMultipliedAlpha(0.1) diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 574aaef0ee..0c8ccadb5d 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -350,7 +350,7 @@ public struct ChatListNodeState: Equatable { } } -private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] { +private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] { return entries.map { entry -> ListViewInsertItem in switch entry.entry { case .HeaderEntry: @@ -433,7 +433,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL }, requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging, displayAsTopicList: peerEntry.displayAsTopicList, - tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters) + tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, isPremium: isPremium, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters) )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -751,7 +751,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL } } -private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { +private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] { return entries.map { entry -> ListViewUpdateItem in switch entry.entry { case let .PeerEntry(peerEntry): @@ -811,7 +811,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL }, requiresPremiumForMessaging: peerEntry.requiresPremiumForMessaging, displayAsTopicList: peerEntry.displayAsTopicList, - tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters) + tags: chatListItemTags(location: location, accountPeerId: context.account.peerId, isPremium: isPremium, peer: peer.chatMainPeer, isUnread: combinedReadState?.isUnread ?? false, isMuted: isRemovedFromTotalUnreadCount, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: chatListFilters) )), editing: editing, hasActiveRevealControls: hasActiveRevealControls, @@ -1106,8 +1106,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL } } -private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { - return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade) +private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, isPremium: Bool, filterData: ChatListItemFilterData?, chatListFilters: [ChatListFilter]?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition { + return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: isPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: isPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade) } private final class ChatListOpaqueTransactionState { @@ -2114,8 +2114,17 @@ public final class ChatListNode: ListView { } let previousChatListFilters = Atomic<[ChatListFilter]?>(value: nil) - let chatListNodeViewTransition = combineLatest( - queue: viewProcessingQueue, + let previousAccountIsPremium = Atomic(value: nil) + + let accountIsPremium = context.engine.data.subscribe( + TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId) + ) + |> map { peer -> Bool in + return peer?.isPremium ?? false + } + |> distinctUntilChanged + + let chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue, hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, @@ -2124,9 +2133,10 @@ public final class ChatListNode: ListView { chatListViewUpdate, self.statePromise.get(), contacts, - chatListFilters + chatListFilters, + accountIsPremium ) - |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters) -> Signal in + |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state, contacts, chatListFilters, accountIsPremium) -> Signal in let (update, filter) = updateAndFilter let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault) @@ -2587,9 +2597,12 @@ public final class ChatListNode: ListView { if chatListFilters != previousChatListFiltersValue { forceAllUpdated = true } + if accountIsPremium != previousAccountIsPremium.swap(accountIsPremium) { + forceAllUpdated = true + } return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode, forceAllUpdated: forceAllUpdated) - |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, transition: $0) }) + |> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, isPremium: accountIsPremium, filterData: filterData, chatListFilters: chatListFilters, mode: mode, isPeerEnabled: isPeerEnabled, transition: $0) }) |> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue) } @@ -4206,7 +4219,10 @@ func hideChatListContacts(context: AccountContext) { let _ = ApplicationSpecificNotice.setDisplayChatListContacts(accountManager: context.sharedContext.accountManager).startStandalone() } -func chatListItemTags(location: ChatListControllerLocation, accountPeerId: EnginePeer.Id, peer: EnginePeer?, isUnread: Bool, isMuted: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?) -> [ChatListItemContent.Tag] { +func chatListItemTags(location: ChatListControllerLocation, accountPeerId: EnginePeer.Id, isPremium: Bool, peer: EnginePeer?, isUnread: Bool, isMuted: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?) -> [ChatListItemContent.Tag] { + if !isPremium { + return [] + } if case .chatList = location { } else { return [] @@ -4222,7 +4238,7 @@ func chatListItemTags(location: ChatListControllerLocation, accountPeerId: Engin for case let .filter(id, title, _, data) in chatListFilters { if data.color != nil { let predicate = chatListFilterPredicate(filter: data, accountPeerId: accountPeerId) - if predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) { + if predicate.pinnedPeerIds.contains(peer.id) || predicate.includes(peer: peer._asPeer(), groupId: .root, isRemovedFromTotalUnreadCount: isMuted, isUnread: isUnread, isContact: isContact, messageTagSummaryResult: hasUnseenMentions) { result.append(ChatListItemContent.Tag( id: id, title: title, diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 75f2845fe8..d6ea57e18e 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -216,8 +216,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1135897376] = { return Api.DefaultHistoryTTL.parse_defaultHistoryTTL($0) } dict[-712374074] = { return Api.Dialog.parse_dialog($0) } dict[1908216652] = { return Api.Dialog.parse_dialogFolder($0) } - dict[1949890536] = { return Api.DialogFilter.parse_dialogFilter($0) } - dict[-699792216] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) } + dict[1605718587] = { return Api.DialogFilter.parse_dialogFilter($0) } + dict[-1612542300] = { return Api.DialogFilter.parse_dialogFilterChatlist($0) } dict[909284270] = { return Api.DialogFilter.parse_dialogFilterDefault($0) } dict[2004110666] = { return Api.DialogFilterSuggested.parse_dialogFilterSuggested($0) } dict[-445792507] = { return Api.DialogPeer.parse_dialogPeer($0) } @@ -1175,6 +1175,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1571952873] = { return Api.messages.CheckedHistoryImportPeer.parse_checkedHistoryImportPeer($0) } dict[740433629] = { return Api.messages.DhConfig.parse_dhConfig($0) } dict[-1058912715] = { return Api.messages.DhConfig.parse_dhConfigNotModified($0) } + dict[718878489] = { return Api.messages.DialogFilters.parse_dialogFilters($0) } dict[364538944] = { return Api.messages.Dialogs.parse_dialogs($0) } dict[-253500010] = { return Api.messages.Dialogs.parse_dialogsNotModified($0) } dict[1910543603] = { return Api.messages.Dialogs.parse_dialogsSlice($0) } @@ -2112,6 +2113,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.DhConfig: _1.serialize(buffer, boxed) + case let _1 as Api.messages.DialogFilters: + _1.serialize(buffer, boxed) case let _1 as Api.messages.Dialogs: _1.serialize(buffer, boxed) case let _1 as Api.messages.DiscussionMessage: diff --git a/submodules/TelegramApi/Sources/Api27.swift b/submodules/TelegramApi/Sources/Api27.swift index aba59a202f..5069e6504e 100644 --- a/submodules/TelegramApi/Sources/Api27.swift +++ b/submodules/TelegramApi/Sources/Api27.swift @@ -1291,67 +1291,19 @@ public extension Api.messages { } } public extension Api.messages { - enum Dialogs: TypeConstructorDescription { - case dialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) - case dialogsNotModified(count: Int32) - case dialogsSlice(count: Int32, dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + enum DialogFilters: TypeConstructorDescription { + case dialogFilters(flags: Int32, filters: [Api.DialogFilter]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .dialogs(let dialogs, let messages, let chats, let users): + case .dialogFilters(let flags, let filters): if boxed { - buffer.appendInt32(364538944) + buffer.appendInt32(718878489) } + serializeInt32(flags, buffer: buffer, boxed: false) buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dialogs.count)) - for item in dialogs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - case .dialogsNotModified(let count): - if boxed { - buffer.appendInt32(-253500010) - } - serializeInt32(count, buffer: buffer, boxed: false) - break - case .dialogsSlice(let count, let dialogs, let messages, let chats, let users): - if boxed { - buffer.appendInt32(1910543603) - } - serializeInt32(count, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dialogs.count)) - for item in dialogs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(messages.count)) - for item in messages { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(chats.count)) - for item in chats { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { + buffer.appendInt32(Int32(filters.count)) + for item in filters { item.serialize(buffer, true) } break @@ -1360,80 +1312,22 @@ public extension Api.messages { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .dialogs(let dialogs, let messages, let chats, let users): - return ("dialogs", [("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) - case .dialogsNotModified(let count): - return ("dialogsNotModified", [("count", count as Any)]) - case .dialogsSlice(let count, let dialogs, let messages, let chats, let users): - return ("dialogsSlice", [("count", count as Any), ("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .dialogFilters(let flags, let filters): + return ("dialogFilters", [("flags", flags as Any), ("filters", filters as Any)]) } } - public static func parse_dialogs(_ reader: BufferReader) -> Dialogs? { - var _1: [Api.Dialog]? + public static func parse_dialogFilters(_ reader: BufferReader) -> DialogFilters? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.DialogFilter]? if let _ = reader.readInt32() { - _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) - } - var _2: [Api.Message]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _3: [Api.Chat]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _4: [Api.User]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self) } let _c1 = _1 != nil let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.Dialogs.dialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!) - } - else { - return nil - } - } - public static func parse_dialogsNotModified(_ reader: BufferReader) -> Dialogs? { - var _1: Int32? - _1 = reader.readInt32() - let _c1 = _1 != nil - if _c1 { - return Api.messages.Dialogs.dialogsNotModified(count: _1!) - } - else { - return nil - } - } - public static func parse_dialogsSlice(_ reader: BufferReader) -> Dialogs? { - var _1: Int32? - _1 = reader.readInt32() - var _2: [Api.Dialog]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) - } - var _3: [Api.Message]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) - } - var _4: [Api.Chat]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) - } - var _5: [Api.User]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.messages.Dialogs.dialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!) + if _c1 && _c2 { + return Api.messages.DialogFilters.dialogFilters(flags: _1!, filters: _2!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api28.swift b/submodules/TelegramApi/Sources/Api28.swift index 30748cf3c0..cb218918b1 100644 --- a/submodules/TelegramApi/Sources/Api28.swift +++ b/submodules/TelegramApi/Sources/Api28.swift @@ -1,3 +1,155 @@ +public extension Api.messages { + enum Dialogs: TypeConstructorDescription { + case dialogs(dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + case dialogsNotModified(count: Int32) + case dialogsSlice(count: Int32, dialogs: [Api.Dialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .dialogs(let dialogs, let messages, let chats, let users): + if boxed { + buffer.appendInt32(364538944) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(dialogs.count)) + for item in dialogs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + case .dialogsNotModified(let count): + if boxed { + buffer.appendInt32(-253500010) + } + serializeInt32(count, buffer: buffer, boxed: false) + break + case .dialogsSlice(let count, let dialogs, let messages, let chats, let users): + if boxed { + buffer.appendInt32(1910543603) + } + serializeInt32(count, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(dialogs.count)) + for item in dialogs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(messages.count)) + for item in messages { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .dialogs(let dialogs, let messages, let chats, let users): + return ("dialogs", [("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + case .dialogsNotModified(let count): + return ("dialogsNotModified", [("count", count as Any)]) + case .dialogsSlice(let count, let dialogs, let messages, let chats, let users): + return ("dialogsSlice", [("count", count as Any), ("dialogs", dialogs as Any), ("messages", messages as Any), ("chats", chats as Any), ("users", users as Any)]) + } + } + + public static func parse_dialogs(_ reader: BufferReader) -> Dialogs? { + var _1: [Api.Dialog]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) + } + var _2: [Api.Message]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.Dialogs.dialogs(dialogs: _1!, messages: _2!, chats: _3!, users: _4!) + } + else { + return nil + } + } + public static func parse_dialogsNotModified(_ reader: BufferReader) -> Dialogs? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.messages.Dialogs.dialogsNotModified(count: _1!) + } + else { + return nil + } + } + public static func parse_dialogsSlice(_ reader: BufferReader) -> Dialogs? { + var _1: Int32? + _1 = reader.readInt32() + var _2: [Api.Dialog]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Dialog.self) + } + var _3: [Api.Message]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self) + } + var _4: [Api.Chat]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _5: [Api.User]? + if let _ = reader.readInt32() { + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.messages.Dialogs.dialogsSlice(count: _1!, dialogs: _2!, messages: _3!, chats: _4!, users: _5!) + } + else { + return nil + } + } + + } +} public extension Api.messages { enum DiscussionMessage: TypeConstructorDescription { case discussionMessage(flags: Int32, messages: [Api.Message], maxId: Int32?, readInboxMaxId: Int32?, readOutboxMaxId: Int32?, unreadCount: Int32, chats: [Api.Chat], users: [Api.User]) @@ -1430,81 +1582,3 @@ public extension Api.messages { } } -public extension Api.messages { - enum RecentStickers: TypeConstructorDescription { - case recentStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32]) - case recentStickersNotModified - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .recentStickers(let hash, let packs, let stickers, let dates): - if boxed { - buffer.appendInt32(-1999405994) - } - serializeInt64(hash, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(packs.count)) - for item in packs { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(stickers.count)) - for item in stickers { - item.serialize(buffer, true) - } - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(dates.count)) - for item in dates { - serializeInt32(item, buffer: buffer, boxed: false) - } - break - case .recentStickersNotModified: - if boxed { - buffer.appendInt32(186120336) - } - - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .recentStickers(let hash, let packs, let stickers, let dates): - return ("recentStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any), ("dates", dates as Any)]) - case .recentStickersNotModified: - return ("recentStickersNotModified", []) - } - } - - public static func parse_recentStickers(_ reader: BufferReader) -> RecentStickers? { - var _1: Int64? - _1 = reader.readInt64() - var _2: [Api.StickerPack]? - if let _ = reader.readInt32() { - _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) - } - var _3: [Api.Document]? - if let _ = reader.readInt32() { - _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) - } - var _4: [Int32]? - if let _ = reader.readInt32() { - _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.messages.RecentStickers.recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!) - } - else { - return nil - } - } - public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? { - return Api.messages.RecentStickers.recentStickersNotModified - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index 483f630d4b..fa6a398716 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -1,3 +1,81 @@ +public extension Api.messages { + enum RecentStickers: TypeConstructorDescription { + case recentStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document], dates: [Int32]) + case recentStickersNotModified + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .recentStickers(let hash, let packs, let stickers, let dates): + if boxed { + buffer.appendInt32(-1999405994) + } + serializeInt64(hash, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(packs.count)) + for item in packs { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(stickers.count)) + for item in stickers { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(dates.count)) + for item in dates { + serializeInt32(item, buffer: buffer, boxed: false) + } + break + case .recentStickersNotModified: + if boxed { + buffer.appendInt32(186120336) + } + + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .recentStickers(let hash, let packs, let stickers, let dates): + return ("recentStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any), ("dates", dates as Any)]) + case .recentStickersNotModified: + return ("recentStickersNotModified", []) + } + } + + public static func parse_recentStickers(_ reader: BufferReader) -> RecentStickers? { + var _1: Int64? + _1 = reader.readInt64() + var _2: [Api.StickerPack]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self) + } + var _3: [Api.Document]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) + } + var _4: [Int32]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.messages.RecentStickers.recentStickers(hash: _1!, packs: _2!, stickers: _3!, dates: _4!) + } + else { + return nil + } + } + public static func parse_recentStickersNotModified(_ reader: BufferReader) -> RecentStickers? { + return Api.messages.RecentStickers.recentStickersNotModified + } + + } +} public extension Api.messages { enum SavedDialogs: TypeConstructorDescription { case savedDialogs(dialogs: [Api.SavedDialog], messages: [Api.Message], chats: [Api.Chat], users: [Api.User]) @@ -1386,167 +1464,3 @@ public extension Api.payments { } } -public extension Api.payments { - enum PaymentReceipt: TypeConstructorDescription { - case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User]) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): - if boxed { - buffer.appendInt32(1891958275) - } - serializeInt32(flags, buffer: buffer, boxed: false) - serializeInt32(date, buffer: buffer, boxed: false) - serializeInt64(botId, buffer: buffer, boxed: false) - serializeInt64(providerId, buffer: buffer, boxed: false) - serializeString(title, buffer: buffer, boxed: false) - serializeString(description, buffer: buffer, boxed: false) - if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} - invoice.serialize(buffer, true) - if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} - if Int(flags) & Int(1 << 1) != 0 {shipping!.serialize(buffer, true)} - if Int(flags) & Int(1 << 3) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)} - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(totalAmount, buffer: buffer, boxed: false) - serializeString(credentialsTitle, buffer: buffer, boxed: false) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(users.count)) - for item in users { - item.serialize(buffer, true) - } - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): - return ("paymentReceipt", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("providerId", providerId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("info", info as Any), ("shipping", shipping as Any), ("tipAmount", tipAmount as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("credentialsTitle", credentialsTitle as Any), ("users", users as Any)]) - } - } - - public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - var _4: Int64? - _4 = reader.readInt64() - var _5: String? - _5 = parseString(reader) - var _6: String? - _6 = parseString(reader) - var _7: Api.WebDocument? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _7 = Api.parse(reader, signature: signature) as? Api.WebDocument - } } - var _8: Api.Invoice? - if let signature = reader.readInt32() { - _8 = Api.parse(reader, signature: signature) as? Api.Invoice - } - var _9: Api.PaymentRequestedInfo? - if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { - _9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo - } } - var _10: Api.ShippingOption? - if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { - _10 = Api.parse(reader, signature: signature) as? Api.ShippingOption - } } - var _11: Int64? - if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() } - var _12: String? - _12 = parseString(reader) - var _13: Int64? - _13 = reader.readInt64() - var _14: String? - _14 = parseString(reader) - var _15: [Api.User]? - if let _ = reader.readInt32() { - _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) - } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - let _c4 = _4 != nil - let _c5 = _5 != nil - let _c6 = _6 != nil - let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil - let _c8 = _8 != nil - let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil - let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil - let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil - let _c12 = _12 != nil - let _c13 = _13 != nil - let _c14 = _14 != nil - let _c15 = _15 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { - return Api.payments.PaymentReceipt.paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!) - } - else { - return nil - } - } - - } -} -public extension Api.payments { - indirect enum PaymentResult: TypeConstructorDescription { - case paymentResult(updates: Api.Updates) - case paymentVerificationNeeded(url: String) - - public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { - switch self { - case .paymentResult(let updates): - if boxed { - buffer.appendInt32(1314881805) - } - updates.serialize(buffer, true) - break - case .paymentVerificationNeeded(let url): - if boxed { - buffer.appendInt32(-666824391) - } - serializeString(url, buffer: buffer, boxed: false) - break - } - } - - public func descriptionFields() -> (String, [(String, Any)]) { - switch self { - case .paymentResult(let updates): - return ("paymentResult", [("updates", updates as Any)]) - case .paymentVerificationNeeded(let url): - return ("paymentVerificationNeeded", [("url", url as Any)]) - } - } - - public static func parse_paymentResult(_ reader: BufferReader) -> PaymentResult? { - var _1: Api.Updates? - if let signature = reader.readInt32() { - _1 = Api.parse(reader, signature: signature) as? Api.Updates - } - let _c1 = _1 != nil - if _c1 { - return Api.payments.PaymentResult.paymentResult(updates: _1!) - } - else { - return nil - } - } - public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? { - var _1: String? - _1 = parseString(reader) - let _c1 = _1 != nil - if _c1 { - return Api.payments.PaymentResult.paymentVerificationNeeded(url: _1!) - } - else { - return nil - } - } - - } -} diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index 2a1f98c411..3e5ca3e260 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -1,3 +1,167 @@ +public extension Api.payments { + enum PaymentReceipt: TypeConstructorDescription { + case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): + if boxed { + buffer.appendInt32(1891958275) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt64(botId, buffer: buffer, boxed: false) + serializeInt64(providerId, buffer: buffer, boxed: false) + serializeString(title, buffer: buffer, boxed: false) + serializeString(description, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)} + invoice.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)} + if Int(flags) & Int(1 << 1) != 0 {shipping!.serialize(buffer, true)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)} + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(totalAmount, buffer: buffer, boxed: false) + serializeString(credentialsTitle, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users): + return ("paymentReceipt", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("providerId", providerId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("info", info as Any), ("shipping", shipping as Any), ("tipAmount", tipAmount as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("credentialsTitle", credentialsTitle as Any), ("users", users as Any)]) + } + } + + public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + var _6: String? + _6 = parseString(reader) + var _7: Api.WebDocument? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.WebDocument + } } + var _8: Api.Invoice? + if let signature = reader.readInt32() { + _8 = Api.parse(reader, signature: signature) as? Api.Invoice + } + var _9: Api.PaymentRequestedInfo? + if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo + } } + var _10: Api.ShippingOption? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _10 = Api.parse(reader, signature: signature) as? Api.ShippingOption + } } + var _11: Int64? + if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() } + var _12: String? + _12 = parseString(reader) + var _13: Int64? + _13 = reader.readInt64() + var _14: String? + _14 = parseString(reader) + var _15: [Api.User]? + if let _ = reader.readInt32() { + _15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil + let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil + let _c12 = _12 != nil + let _c13 = _13 != nil + let _c14 = _14 != nil + let _c15 = _15 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 { + return Api.payments.PaymentReceipt.paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!) + } + else { + return nil + } + } + + } +} +public extension Api.payments { + indirect enum PaymentResult: TypeConstructorDescription { + case paymentResult(updates: Api.Updates) + case paymentVerificationNeeded(url: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .paymentResult(let updates): + if boxed { + buffer.appendInt32(1314881805) + } + updates.serialize(buffer, true) + break + case .paymentVerificationNeeded(let url): + if boxed { + buffer.appendInt32(-666824391) + } + serializeString(url, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .paymentResult(let updates): + return ("paymentResult", [("updates", updates as Any)]) + case .paymentVerificationNeeded(let url): + return ("paymentVerificationNeeded", [("url", url as Any)]) + } + } + + public static func parse_paymentResult(_ reader: BufferReader) -> PaymentResult? { + var _1: Api.Updates? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Updates + } + let _c1 = _1 != nil + if _c1 { + return Api.payments.PaymentResult.paymentResult(updates: _1!) + } + else { + return nil + } + } + public static func parse_paymentVerificationNeeded(_ reader: BufferReader) -> PaymentResult? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.payments.PaymentResult.paymentVerificationNeeded(url: _1!) + } + else { + return nil + } + } + + } +} public extension Api.payments { enum SavedInfo: TypeConstructorDescription { case savedInfo(flags: Int32, savedInfo: Api.PaymentRequestedInfo?) diff --git a/submodules/TelegramApi/Sources/Api32.swift b/submodules/TelegramApi/Sources/Api32.swift index 43cd7ddc88..5b224f22fc 100644 --- a/submodules/TelegramApi/Sources/Api32.swift +++ b/submodules/TelegramApi/Sources/Api32.swift @@ -5325,15 +5325,15 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func getDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogFilter]>) { + static func getDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-241247891) + buffer.appendInt32(-271283063) - return (FunctionDescription(name: "messages.getDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogFilter]? in + return (FunctionDescription(name: "messages.getDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.DialogFilters? in let reader = BufferReader(buffer) - var result: [Api.DialogFilter]? - if let _ = reader.readInt32() { - result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self) + var result: Api.messages.DialogFilters? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.messages.DialogFilters } return result }) @@ -7816,6 +7816,21 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func toggleDialogFilterTags(enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-47326647) + enabled.serialize(buffer, true) + return (FunctionDescription(name: "messages.toggleDialogFilterTags", parameters: [("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.messages { static func toggleDialogPin(flags: Int32, peer: Api.InputDialogPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index f1b43a4ae1..6e5cd0aab4 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -1060,20 +1060,21 @@ public extension Api { } public extension Api { enum DialogFilter: TypeConstructorDescription { - case dialogFilter(flags: Int32, id: Int32, title: String, emoticon: String?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer]) - case dialogFilterChatlist(flags: Int32, id: Int32, title: String, emoticon: String?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer]) + case dialogFilter(flags: Int32, id: Int32, title: String, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer], excludePeers: [Api.InputPeer]) + case dialogFilterChatlist(flags: Int32, id: Int32, title: String, emoticon: String?, color: Int32?, pinnedPeers: [Api.InputPeer], includePeers: [Api.InputPeer]) case dialogFilterDefault public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .dialogFilter(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers, let excludePeers): + case .dialogFilter(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers, let excludePeers): if boxed { - buffer.appendInt32(1949890536) + buffer.appendInt32(1605718587) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) buffer.appendInt32(Int32(pinnedPeers.count)) for item in pinnedPeers { @@ -1090,14 +1091,15 @@ public extension Api { item.serialize(buffer, true) } break - case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers): + case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers): if boxed { - buffer.appendInt32(-699792216) + buffer.appendInt32(-1612542300) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) serializeString(title, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 25) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 27) != 0 {serializeInt32(color!, buffer: buffer, boxed: false)} buffer.appendInt32(481674261) buffer.appendInt32(Int32(pinnedPeers.count)) for item in pinnedPeers { @@ -1120,10 +1122,10 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .dialogFilter(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers, let excludePeers): - return ("dialogFilter", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("pinnedPeers", pinnedPeers as Any), ("includePeers", includePeers as Any), ("excludePeers", excludePeers as Any)]) - case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let pinnedPeers, let includePeers): - return ("dialogFilterChatlist", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("pinnedPeers", pinnedPeers as Any), ("includePeers", includePeers as Any)]) + case .dialogFilter(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers, let excludePeers): + return ("dialogFilter", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("color", color as Any), ("pinnedPeers", pinnedPeers as Any), ("includePeers", includePeers as Any), ("excludePeers", excludePeers as Any)]) + case .dialogFilterChatlist(let flags, let id, let title, let emoticon, let color, let pinnedPeers, let includePeers): + return ("dialogFilterChatlist", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("color", color as Any), ("pinnedPeers", pinnedPeers as Any), ("includePeers", includePeers as Any)]) case .dialogFilterDefault: return ("dialogFilterDefault", []) } @@ -1138,10 +1140,8 @@ public extension Api { _3 = parseString(reader) var _4: String? if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) } - var _5: [Api.InputPeer]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) - } + var _5: Int32? + if Int(_1!) & Int(1 << 27) != 0 {_5 = reader.readInt32() } var _6: [Api.InputPeer]? if let _ = reader.readInt32() { _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) @@ -1150,15 +1150,20 @@ public extension Api { if let _ = reader.readInt32() { _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) } + var _8: [Api.InputPeer]? + if let _ = reader.readInt32() { + _8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 25) == 0) || _4 != nil - let _c5 = _5 != nil + let _c5 = (Int(_1!) & Int(1 << 27) == 0) || _5 != nil let _c6 = _6 != nil let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.DialogFilter.dialogFilter(flags: _1!, id: _2!, title: _3!, emoticon: _4, pinnedPeers: _5!, includePeers: _6!, excludePeers: _7!) + let _c8 = _8 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { + return Api.DialogFilter.dialogFilter(flags: _1!, id: _2!, title: _3!, emoticon: _4, color: _5, pinnedPeers: _6!, includePeers: _7!, excludePeers: _8!) } else { return nil @@ -1173,22 +1178,25 @@ public extension Api { _3 = parseString(reader) var _4: String? if Int(_1!) & Int(1 << 25) != 0 {_4 = parseString(reader) } - var _5: [Api.InputPeer]? - if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) - } + var _5: Int32? + if Int(_1!) & Int(1 << 27) != 0 {_5 = reader.readInt32() } var _6: [Api.InputPeer]? if let _ = reader.readInt32() { _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) } + var _7: [Api.InputPeer]? + if let _ = reader.readInt32() { + _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self) + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = (Int(_1!) & Int(1 << 25) == 0) || _4 != nil - let _c5 = _5 != nil + let _c5 = (Int(_1!) & Int(1 << 27) == 0) || _5 != nil let _c6 = _6 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { - return Api.DialogFilter.dialogFilterChatlist(flags: _1!, id: _2!, title: _3!, emoticon: _4, pinnedPeers: _5!, includePeers: _6!) + let _c7 = _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.DialogFilter.dialogFilterChatlist(flags: _1!, id: _2!, title: _3!, emoticon: _4, color: _5, pinnedPeers: _6!, includePeers: _7!) } else { return nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift index cec7854829..a51d704453 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ChatListFiltering.swift @@ -306,7 +306,7 @@ extension ChatListFilter { switch apiFilter { case .dialogFilterDefault: self = .allChats - case let .dialogFilter(flags, id, title, emoticon, pinnedPeers, includePeers, excludePeers): + case let .dialogFilter(flags, id, title, emoticon, color, pinnedPeers, includePeers, excludePeers): self = .filter( id: id, title: title, @@ -353,10 +353,10 @@ extension ChatListFilter { return nil } }, - color: nil + color: color.flatMap(PeerNameColor.init(rawValue:)) ) ) - case let .dialogFilterChatlist(flags, id, title, emoticon, pinnedPeers, includePeers): + case let .dialogFilterChatlist(flags, id, title, emoticon, color, pinnedPeers, includePeers): self = .filter( id: id, title: title, @@ -392,7 +392,7 @@ extension ChatListFilter { } }), excludePeers: [], - color: nil + color: color.flatMap(PeerNameColor.init(rawValue:)) ) ) } @@ -408,7 +408,10 @@ extension ChatListFilter { if emoticon != nil { flags |= 1 << 25 } - return .dialogFilterChatlist(flags: flags, id: id, title: title, emoticon: emoticon, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in + if data.color != nil { + flags |= 1 << 27 + } + return .dialogFilterChatlist(flags: flags, id: id, title: title, emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in return transaction.getPeer(peerId).flatMap(apiInputPeer) }, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in if data.includePeers.pinnedPeers.contains(peerId) { @@ -431,7 +434,10 @@ extension ChatListFilter { if emoticon != nil { flags |= 1 << 25 } - return .dialogFilter(flags: flags, id: id, title: title, emoticon: emoticon, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in + if data.color != nil { + flags |= 1 << 27 + } + return .dialogFilter(flags: flags, id: id, title: title, emoticon: emoticon, color: data.color?.rawValue, pinnedPeers: data.includePeers.pinnedPeers.compactMap { peerId -> Api.InputPeer? in return transaction.getPeer(peerId).flatMap(apiInputPeer) }, includePeers: data.includePeers.peers.compactMap { peerId -> Api.InputPeer? in if data.includePeers.pinnedPeers.contains(peerId) { @@ -488,111 +494,116 @@ private enum RequestChatListFiltersError { case generic } -private func requestChatListFilters(accountPeerId: PeerId, postbox: Postbox, network: Network) -> Signal<[ChatListFilter], RequestChatListFiltersError> { +private func requestChatListFilters(accountPeerId: PeerId, postbox: Postbox, network: Network) -> Signal<([ChatListFilter], Bool), RequestChatListFiltersError> { return network.request(Api.functions.messages.getDialogFilters()) |> mapError { _ -> RequestChatListFiltersError in return .generic } - |> mapToSignal { result -> Signal<[ChatListFilter], RequestChatListFiltersError> in - return postbox.transaction { transaction -> ([ChatListFilter], [Api.InputPeer], [Api.InputPeer]) in - var filters: [ChatListFilter] = [] - var missingPeers: [Api.InputPeer] = [] - var missingChats: [Api.InputPeer] = [] - var missingPeerIds = Set() - var missingChatIds = Set() - for apiFilter in result { - let filter = ChatListFilter(apiFilter: apiFilter) - filters.append(filter) - switch apiFilter { - case .dialogFilterDefault: - break - case let .dialogFilter(_, _, _, _, pinnedPeers, includePeers, excludePeers): - for peer in pinnedPeers + includePeers + excludePeers { - var peerId: PeerId? - switch peer { - case let .inputPeerUser(userId, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) - case let .inputPeerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) - case let .inputPeerChannel(channelId, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) - default: - break - } - if let peerId = peerId { - if transaction.getPeer(peerId) == nil && !missingPeerIds.contains(peerId) { - missingPeerIds.insert(peerId) - missingPeers.append(peer) + |> mapToSignal { result -> Signal<([ChatListFilter], Bool), RequestChatListFiltersError> in + return postbox.transaction { transaction -> ([ChatListFilter], [Api.InputPeer], [Api.InputPeer], Bool) in + switch result { + case let .dialogFilters(flags, apiFilters): + let tagsEnabled = (flags & (1 << 0)) != 0 + + var filters: [ChatListFilter] = [] + var missingPeers: [Api.InputPeer] = [] + var missingChats: [Api.InputPeer] = [] + var missingPeerIds = Set() + var missingChatIds = Set() + for apiFilter in apiFilters { + let filter = ChatListFilter(apiFilter: apiFilter) + filters.append(filter) + switch apiFilter { + case .dialogFilterDefault: + break + case let .dialogFilter(_, _, _, _, _, pinnedPeers, includePeers, excludePeers): + for peer in pinnedPeers + includePeers + excludePeers { + var peerId: PeerId? + switch peer { + case let .inputPeerUser(userId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + case let .inputPeerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) + case let .inputPeerChannel(channelId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + default: + break + } + if let peerId = peerId { + if transaction.getPeer(peerId) == nil && !missingPeerIds.contains(peerId) { + missingPeerIds.insert(peerId) + missingPeers.append(peer) + } } } - } - - for peer in pinnedPeers { - var peerId: PeerId? - switch peer { - case let .inputPeerUser(userId, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) - case let .inputPeerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) - case let .inputPeerChannel(channelId, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) - default: - break - } - if let peerId = peerId, !missingChatIds.contains(peerId) { - if transaction.getPeerChatListIndex(peerId) == nil { - missingChatIds.insert(peerId) - missingChats.append(peer) + + for peer in pinnedPeers { + var peerId: PeerId? + switch peer { + case let .inputPeerUser(userId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + case let .inputPeerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) + case let .inputPeerChannel(channelId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + default: + break + } + if let peerId = peerId, !missingChatIds.contains(peerId) { + if transaction.getPeerChatListIndex(peerId) == nil { + missingChatIds.insert(peerId) + missingChats.append(peer) + } } } - } - case let .dialogFilterChatlist(_, _, _, _, pinnedPeers, includePeers): - for peer in pinnedPeers + includePeers { - var peerId: PeerId? - switch peer { - case let .inputPeerUser(userId, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) - case let .inputPeerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) - case let .inputPeerChannel(channelId, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) - default: - break - } - if let peerId = peerId { - if transaction.getPeer(peerId) == nil && !missingPeerIds.contains(peerId) { - missingPeerIds.insert(peerId) - missingPeers.append(peer) + case let .dialogFilterChatlist(_, _, _, _, _, pinnedPeers, includePeers): + for peer in pinnedPeers + includePeers { + var peerId: PeerId? + switch peer { + case let .inputPeerUser(userId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + case let .inputPeerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) + case let .inputPeerChannel(channelId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + default: + break + } + if let peerId = peerId { + if transaction.getPeer(peerId) == nil && !missingPeerIds.contains(peerId) { + missingPeerIds.insert(peerId) + missingPeers.append(peer) + } } } - } - - for peer in pinnedPeers { - var peerId: PeerId? - switch peer { - case let .inputPeerUser(userId, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) - case let .inputPeerChat(chatId): - peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) - case let .inputPeerChannel(channelId, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) - default: - break - } - if let peerId = peerId, !missingChatIds.contains(peerId) { - if transaction.getPeerChatListIndex(peerId) == nil { - missingChatIds.insert(peerId) - missingChats.append(peer) + + for peer in pinnedPeers { + var peerId: PeerId? + switch peer { + case let .inputPeerUser(userId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)) + case let .inputPeerChat(chatId): + peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)) + case let .inputPeerChannel(channelId, _): + peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId)) + default: + break + } + if let peerId = peerId, !missingChatIds.contains(peerId) { + if transaction.getPeerChatListIndex(peerId) == nil { + missingChatIds.insert(peerId) + missingChats.append(peer) + } } } } } + return (filters, missingPeers, missingChats, tagsEnabled) } - return (filters, missingPeers, missingChats) } |> castError(RequestChatListFiltersError.self) - |> mapToSignal { filtersAndMissingPeers -> Signal<[ChatListFilter], RequestChatListFiltersError> in - let (filters, missingPeers, missingChats) = filtersAndMissingPeers + |> mapToSignal { filtersAndMissingPeers -> Signal<([ChatListFilter], Bool), RequestChatListFiltersError> in + let (filters, missingPeers, missingChats, tagsEnabled) = filtersAndMissingPeers var missingUsers: [Api.InputUser] = [] var missingChannels: [Api.InputChannel] = [] @@ -700,13 +711,10 @@ private func requestChatListFilters(accountPeerId: PeerId, postbox: Postbox, net loadMissingChats ) |> castError(RequestChatListFiltersError.self) - |> mapToSignal { _ -> Signal<[ChatListFilter], RequestChatListFiltersError> in - #if swift(<5.1) - return .complete() - #endif + |> mapToSignal { _ -> Signal<([ChatListFilter], Bool), RequestChatListFiltersError> in } |> then( - .single(filters) + Signal<([ChatListFilter], Bool), RequestChatListFiltersError>.single((filters, tagsEnabled)) ) } } @@ -889,14 +897,16 @@ struct ChatListFiltersState: Codable, Equatable { var updates: [ChatListFilterUpdates] + var remoteDisplayTags: Bool? var displayTags: Bool - static var `default` = ChatListFiltersState(filters: [], remoteFilters: nil, updates: [], displayTags: false) + static var `default` = ChatListFiltersState(filters: [], remoteFilters: nil, updates: [], remoteDisplayTags: nil, displayTags: false) - fileprivate init(filters: [ChatListFilter], remoteFilters: [ChatListFilter]?, updates: [ChatListFilterUpdates], displayTags: Bool) { + fileprivate init(filters: [ChatListFilter], remoteFilters: [ChatListFilter]?, updates: [ChatListFilterUpdates], remoteDisplayTags: Bool?, displayTags: Bool) { self.filters = filters self.remoteFilters = remoteFilters self.updates = updates + self.remoteDisplayTags = remoteDisplayTags self.displayTags = displayTags } @@ -906,6 +916,7 @@ struct ChatListFiltersState: Codable, Equatable { self.filters = try container.decode([ChatListFilter].self, forKey: "filters") self.remoteFilters = try container.decodeIfPresent([ChatListFilter].self, forKey: "remoteFilters") self.updates = try container.decodeIfPresent([ChatListFilterUpdates].self, forKey: "updates") ?? [] + self.remoteDisplayTags = try container.decodeIfPresent(Bool.self, forKey: "remoteDisplayTags") self.displayTags = try container.decodeIfPresent(Bool.self, forKey: "displayTags") ?? false } @@ -915,6 +926,7 @@ struct ChatListFiltersState: Codable, Equatable { try container.encode(self.filters, forKey: "filters") try container.encodeIfPresent(self.remoteFilters, forKey: "remoteFilters") try container.encode(self.updates, forKey: "updates") + try container.encodeIfPresent(self.remoteDisplayTags, forKey: "remoteDisplayTags") try container.encode(self.displayTags, forKey: "displayTags") } @@ -959,6 +971,27 @@ func _internal_updateChatListFiltersInteractively(postbox: Postbox, _ f: @escapi } } +func _internal_updateChatListFiltersDisplayTagsInteractively(postbox: Postbox, displayTags: Bool) -> Signal { + return postbox.transaction { transaction -> Void in + var hasUpdates = false + transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in + var state = entry?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default + if displayTags != state.displayTags { + state.displayTags = displayTags + hasUpdates = true + } + + state.normalize() + + return PreferencesEntry(state) + }) + if hasUpdates { + requestChatListFiltersSync(transaction: transaction) + } + } + |> ignoreValues +} + func _internal_updateChatListFiltersInteractively(transaction: Transaction, _ f: ([ChatListFilter]) -> [ChatListFilter]) { var hasUpdates = false transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in @@ -1366,18 +1399,21 @@ private func synchronizeChatListFilters(transaction: Transaction, accountPeerId: let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters)?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default let localFilters = settings.filters let locallyKnownRemoteFilters = settings.remoteFilters ?? [] + let localDisplayTags = settings.displayTags + let locallyKnownRemoteDisplayTags = settings.remoteDisplayTags ?? false return requestChatListFilters(accountPeerId: accountPeerId, postbox: postbox, network: network) - |> `catch` { _ -> Signal<[ChatListFilter], NoError> in + |> `catch` { _ -> Signal<([ChatListFilter], Bool), NoError> in return .complete() } - |> mapToSignal { remoteFilters -> Signal in - if localFilters == locallyKnownRemoteFilters { + |> mapToSignal { remoteFilters, remoteTagsEnabled -> Signal in + if localFilters == locallyKnownRemoteFilters && localDisplayTags == locallyKnownRemoteDisplayTags { return postbox.transaction { transaction -> Void in let _ = updateChatListFiltersState(transaction: transaction, { state in var state = state state.filters = remoteFilters state.remoteFilters = state.filters + state.displayTags = remoteTagsEnabled return state }) } @@ -1453,6 +1489,17 @@ private func synchronizeChatListFilters(transaction: Transaction, accountPeerId: reorderFilters = .complete() } + let updateTagsEnabled: Signal + if localDisplayTags != remoteTagsEnabled { + updateTagsEnabled = network.request(Api.functions.messages.toggleDialogFilterTags(enabled: localDisplayTags ? .boolTrue : .boolFalse)) + |> ignoreValues + |> `catch` { _ -> Signal in + return .complete() + } + } else { + updateTagsEnabled = .complete() + } + return deleteSignals |> then( addSignals @@ -1460,12 +1507,16 @@ private func synchronizeChatListFilters(transaction: Transaction, accountPeerId: |> then( reorderFilters ) + |> then( + updateTagsEnabled + ) |> then( postbox.transaction { transaction -> Void in let _ = updateChatListFiltersState(transaction: transaction, { state in var state = state state.filters = mergedFilters state.remoteFilters = state.filters + state.remoteDisplayTags = state.displayTags return state }) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 5f625b1e91..d0bc67b725 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -589,13 +589,7 @@ public extension TelegramEngine { } public func updateChatListFiltersDisplayTags(isEnabled: Bool) { - let _ = self.account.postbox.transaction({ transaction in - updateChatListFiltersState(transaction: transaction, { state in - var state = state - state.displayTags = isEnabled - return state - }) - }).start() + let _ = _internal_updateChatListFiltersDisplayTagsInteractively(postbox: self.account.postbox, displayTags: isEnabled).startStandalone() } public func updatedChatListFilters() -> Signal<[ChatListFilter], NoError> { diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift index c262788162..f9dae2ab34 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorItem/Sources/PeerNameColorItem.swift @@ -233,21 +233,27 @@ private final class PeerNameColorIconItemNode : ASDisplayNode { } public final class PeerNameColorItem: ListViewItem, ItemListItem, ListItemComponentAdaptor.ItemGenerator { + public enum Mode { + case name + case profile + case folderTag + } + public var sectionId: ItemListSectionId public let theme: PresentationTheme public let colors: PeerNameColors - public let isProfile: Bool + public let mode: Mode public let displayEmptyColor: Bool public let isLocked: Bool public let currentColor: PeerNameColor? public let updated: (PeerNameColor?) -> Void public let tag: ItemListItemTag? - public init(theme: PresentationTheme, colors: PeerNameColors, isProfile: Bool, displayEmptyColor: Bool = false, currentColor: PeerNameColor?, isLocked: Bool = false, updated: @escaping (PeerNameColor?) -> Void, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId) { + public init(theme: PresentationTheme, colors: PeerNameColors, mode: Mode, displayEmptyColor: Bool = false, currentColor: PeerNameColor?, isLocked: Bool = false, updated: @escaping (PeerNameColor?) -> Void, tag: ItemListItemTag? = nil, sectionId: ItemListSectionId) { self.theme = theme self.colors = colors - self.isProfile = isProfile + self.mode = mode self.displayEmptyColor = displayEmptyColor self.isLocked = isLocked self.currentColor = currentColor @@ -300,7 +306,7 @@ public final class PeerNameColorItem: ListViewItem, ItemListItem, ListItemCompon if lhs.colors != rhs.colors { return false } - if lhs.isProfile != rhs.isProfile { + if lhs.mode != rhs.mode { return false } if lhs.currentColor != rhs.currentColor { @@ -363,13 +369,18 @@ public final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { let itemsPerRow: Int let displayOrder: [Int32] - if item.isProfile { - displayOrder = item.colors.profileDisplayOrder - itemsPerRow = 8 - } else { + switch item.mode { + case .name: displayOrder = item.colors.displayOrder itemsPerRow = 7 + case .profile: + displayOrder = item.colors.profileDisplayOrder + itemsPerRow = 8 + case .folderTag: + displayOrder = item.colors.chatFolderTagDisplayOrder + itemsPerRow = 8 } + var numItems = displayOrder.count if item.displayEmptyColor { numItems += 1 @@ -466,10 +477,13 @@ public final class PeerNameColorItemNode: ListViewItemNode, ItemListItemNode { for index in displayOrder { let color = PeerNameColor(rawValue: index) let colors: PeerNameColors.Colors - if item.isProfile { - colors = item.colors.getProfile(color, dark: item.theme.overallDarkAppearance, subject: .palette) - } else { + switch item.mode { + case .name: colors = item.colors.get(color, dark: item.theme.overallDarkAppearance) + case .profile: + colors = item.colors.getProfile(color, dark: item.theme.overallDarkAppearance, subject: .palette) + case .folderTag: + colors = item.colors.getChatFolderTag(color, dark: item.theme.overallDarkAppearance) } items.append(PeerNameColorIconItem(index: color, colors: colors, isDark: item.theme.overallDarkAppearance, selected: color == item.currentColor, isLocked: item.isLocked, action: action)) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index 7f5576ae36..e805cbd4b7 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -1261,7 +1261,7 @@ final class ChannelAppearanceScreenComponent: Component { itemGenerator: PeerNameColorItem( theme: environment.theme, colors: component.context.peerNameColors, - isProfile: true, + mode: .profile, currentColor: profileColor, updated: { [weak self] value in guard let self, let value else { @@ -1578,7 +1578,7 @@ final class ChannelAppearanceScreenComponent: Component { itemGenerator: PeerNameColorItem( theme: environment.theme, colors: component.context.peerNameColors, - isProfile: false, + mode: .name, currentColor: resolvedState.nameColor, updated: { [weak self] value in guard let self, let value else { diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift index 40fa333d30..72cfb99bc2 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift @@ -212,7 +212,7 @@ private enum PeerNameColorScreenEntry: ItemListNodeEntry { return PeerNameColorItem( theme: presentationData.theme, colors: colors, - isProfile: isProfile, + mode: isProfile ? .profile : .name, currentColor: currentColor, updated: { color in if let color { From 5ce85e7b7599e213ae532f47cddf744025210047 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 29 Feb 2024 01:35:52 +0400 Subject: [PATCH 4/8] Premium and business screens improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 5 + .../AccountContext/Sources/Premium.swift | 1 + .../ChatListFilterPresetListController.swift | 17 +- .../PremiumUI/Sources/PremiumDemoScreen.swift | 28 ++- .../PremiumUI/Sources/PremiumGiftScreen.swift | 1 + .../Sources/PremiumIntroScreen.swift | 235 ++++++++++-------- .../Sources/SharedAccountContext.swift | 4 + 7 files changed, 182 insertions(+), 109 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 42e90545a5..2af89cfaec 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -11316,6 +11316,11 @@ Sorry for the inconvenience."; "ChannelBoost.Header.Giveaway" = "giveaway"; "ChannelBoost.Header.Features" = "features"; +"Premium.FolderTags" = "Folder Tags"; +"Premium.FolderTagsInfo" = "Add colorful labels to chats for faster access with Telegram Premium."; +"Premium.FolderTagsStandaloneInfo" = "Add colorful labels to chats for faster access with Telegram Premium."; +"Premium.FolderTags.Proceed" = "About Telegram Premium"; + "Premium.Business" = "Telegram Business"; "Premium.BusinessInfo" = "Upgrade your account with business features such as location, opening hours and quick replies."; diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index a23c4a9523..c327bc5597 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -38,6 +38,7 @@ public enum PremiumIntroSource { case presence case readTime case messageTags + case folderTags } public enum PremiumGiftSource: Equatable { diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift index 3b496c6b11..8ed12be0da 100644 --- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift +++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift @@ -558,14 +558,15 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch }, updateDisplayTags: { value in context.engine.peers.updateChatListFiltersDisplayTags(isEnabled: value) }, updateDisplayTagsLocked: { - //TODO:localize - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_reorder", scale: 0.05, colors: [:], title: nil, text: "Subscribe to **Telegram Premium** to show folder tags.", customUndoText: presentationData.strings.ChatListFolderSettings_SubscribeToMoveAllAction, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in - if case .undo = action { - pushControllerImpl?(PremiumIntroScreen(context: context, source: .folders)) - } - return false }) - ) + var replaceImpl: ((ViewController) -> Void)? + let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .folderTags, action: { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .folderTags, forceDark: false, dismissed: nil) + replaceImpl?(controller) + }) + replaceImpl = { [weak controller] c in + controller?.replace(with: c) + } + pushControllerImpl?(controller) }) let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState]) diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index 5504cdc4d8..5957a9920f 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -1004,7 +1004,7 @@ private final class DemoSheetContent: CombinedComponent { position: .top, model: .island, videoFile: configuration.videos["last_seen"], - decoration: .tag + decoration: .badgeStars )), title: strings.Premium_LastSeen, text: strings.Premium_LastSeenInfo, @@ -1023,7 +1023,7 @@ private final class DemoSheetContent: CombinedComponent { position: .top, model: .island, videoFile: configuration.videos["message_privacy"], - decoration: .tag + decoration: .swirlStars )), title: strings.Premium_MessagePrivacy, text: strings.Premium_MessagePrivacyInfo, @@ -1033,6 +1033,26 @@ private final class DemoSheetContent: CombinedComponent { ) ) + availableItems[.folderTags] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.folderTags, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + model: .island, + videoFile: configuration.videos["folder_tags"], + decoration: .tag + )), + title: strings.Premium_FolderTags, + text: strings.Premium_FolderTagsStandaloneInfo, + textColor: textColor + ) + ) + ) + ) + let index: Int = 0 var items: [DemoPagerComponent.Item] = [] if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) { @@ -1136,6 +1156,8 @@ private final class DemoSheetContent: CombinedComponent { buttonText = strings.Premium_LastSeen_Proceed case .messagePrivacy: buttonText = strings.Premium_MessagePrivacy_Proceed + case .folderTags: + buttonText = strings.Premium_FolderTags_Proceed default: buttonText = strings.Common_OK } @@ -1177,6 +1199,8 @@ private final class DemoSheetContent: CombinedComponent { text = strings.Premium_LastSeenInfo case .messagePrivacy: text = strings.Premium_MessagePrivacyInfo + case .folderTags: + text = strings.Premium_FolderTagsStandaloneInfo default: text = "" } diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index d2afc9fc9c..2e81b49ed3 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -435,6 +435,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), + UIColor(rgb: 0x676bff), //replace UIColor(rgb: 0x6172ff), UIColor(rgb: 0x5b79ff), UIColor(rgb: 0x4492ff), diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 21d6f91c16..11e56087a1 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -282,6 +282,12 @@ public enum PremiumSource: Equatable { } else { return false } + case .folderTags: + if case .folderTags = rhs { + return true + } else { + return false + } } } @@ -326,6 +332,7 @@ public enum PremiumSource: Equatable { case presence case readTime case messageTags + case folderTags var identifier: String? { switch self { @@ -413,6 +420,8 @@ public enum PremiumSource: Equatable { return "read_time" case .messageTags: return "saved_tags" + case .folderTags: + return "folder_tags" } } } @@ -448,7 +457,6 @@ public enum PremiumPerk: CaseIterable { case businessAwayMessage case businessChatBots - public static var allCases: [PremiumPerk] { return [ .doubleLimits, @@ -471,12 +479,28 @@ public enum PremiumPerk: CaseIterable { .messageTags, .lastSeen, .messagePrivacy, + .folderTags, .business ] } - init?(identifier: String) { - for perk in PremiumPerk.allCases { + public static var allBusinessCases: [PremiumPerk] { + return [ + .businessLocation, + .businessHours, + .businessGreetingMessage, + .businessQuickReplies, + .businessAwayMessage, + .businessChatBots, +// .emojiStatus, +// .folderTags, +// .stories, + ] + } + + + init?(identifier: String, business: Bool) { + for perk in business ? PremiumPerk.allBusinessCases : PremiumPerk.allCases { if perk.identifier == identifier { self = perk return @@ -527,10 +551,22 @@ public enum PremiumPerk: CaseIterable { return "last_seen" case .messagePrivacy: return "message_privacy" + case .folderTags: + return "folder_tags" case .business: return "business" - default: - return "" + case .businessLocation: + return "location" + case .businessHours: + return "opening_hours" + case .businessQuickReplies: + return "quick_replies" + case .businessGreetingMessage: + return "greeting_messages" + case .businessAwayMessage: + return "away_messages" + case .businessChatBots: + return "chatbots" } } @@ -576,10 +612,23 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_LastSeen case .messagePrivacy: return strings.Premium_MessagePrivacy + case .folderTags: + return strings.Premium_FolderTags case .business: return strings.Premium_Business - default: - return "" + + case .businessLocation: + return strings.Business_Location + case .businessHours: + return strings.Business_OpeningHours + case .businessQuickReplies: + return strings.Business_QuickReplies + case .businessGreetingMessage: + return strings.Business_GreetingMessages + case .businessAwayMessage: + return strings.Business_AwayMessages + case .businessChatBots: + return strings.Business_Chatbots } } @@ -625,10 +674,23 @@ public enum PremiumPerk: CaseIterable { return strings.Premium_LastSeenInfo case .messagePrivacy: return strings.Premium_MessagePrivacyInfo + case .folderTags: + return strings.Premium_FolderTagsInfo case .business: return strings.Premium_BusinessInfo - default: - return "" + + case .businessLocation: + return strings.Business_LocationInfo + case .businessHours: + return strings.Business_OpeningHoursInfo + case .businessQuickReplies: + return strings.Business_QuickRepliesInfo + case .businessGreetingMessage: + return strings.Business_GreetingMessagesInfo + case .businessAwayMessage: + return strings.Business_AwayMessagesInfo + case .businessChatBots: + return strings.Business_ChatbotsInfo } } @@ -674,86 +736,22 @@ public enum PremiumPerk: CaseIterable { return "Premium/Perk/LastSeen" case .messagePrivacy: return "Premium/Perk/MessagePrivacy" + case .folderTags: + return "Premium/Perk/MessageTags" case .business: return "Premium/Perk/Business" - default: - return "" - } - } -} - -private enum BusinessPerk: CaseIterable { - case location - case hours - case quickReplies - case greetings - case awayMessages - case chatbots - - var identifier: String { - switch self { - case .location: - return "location" - case .hours: - return "opening_hours" - case .quickReplies: - return "quick_replies" - case .greetings: - return "greeting_messages" - case .awayMessages: - return "away_messages" - case .chatbots: - return "chatbots" - } - } - - func title(strings: PresentationStrings) -> String { - switch self { - case .location: - return strings.Business_Location - case .hours: - return strings.Business_OpeningHours - case .quickReplies: - return strings.Business_QuickReplies - case .greetings: - return strings.Business_GreetingMessages - case .awayMessages: - return strings.Business_AwayMessages - case .chatbots: - return strings.Business_Chatbots - } - } - - func subtitle(strings: PresentationStrings) -> String { - switch self { - case .location: - return strings.Business_LocationInfo - case .hours: - return strings.Business_OpeningHoursInfo - case .quickReplies: - return strings.Business_QuickRepliesInfo - case .greetings: - return strings.Business_GreetingMessagesInfo - case .awayMessages: - return strings.Business_AwayMessagesInfo - case .chatbots: - return strings.Business_ChatbotsInfo - } - } - - var iconName: String { - switch self { - case .location: + + case .businessLocation: return "Premium/BusinessPerk/Location" - case .hours: + case .businessHours: return "Premium/BusinessPerk/Hours" - case .quickReplies: + case .businessQuickReplies: return "Premium/BusinessPerk/Replies" - case .greetings: + case .businessGreetingMessage: return "Premium/BusinessPerk/Greetings" - case .awayMessages: + case .businessAwayMessage: return "Premium/BusinessPerk/Away" - case .chatbots: + case .businessChatBots: return "Premium/BusinessPerk/Chatbots" } } @@ -783,20 +781,32 @@ struct PremiumIntroConfiguration { .animatedUserpics, .premiumStickers, .business + ], businessPerks: [ + .businessGreetingMessage, + .businessAwayMessage, + .businessQuickReplies, + .businessChatBots, + .businessHours, + .businessLocation +// .emojiStatus, +// .folderTags, +// .stories ]) } let perks: [PremiumPerk] + let businessPerks: [PremiumPerk] - fileprivate init(perks: [PremiumPerk]) { + fileprivate init(perks: [PremiumPerk], businessPerks: [PremiumPerk]) { self.perks = perks + self.businessPerks = businessPerks } public static func with(appConfiguration: AppConfiguration) -> PremiumIntroConfiguration { if let data = appConfiguration.data, let values = data["premium_promo_order"] as? [String] { var perks: [PremiumPerk] = [] for value in values { - if let perk = PremiumPerk(identifier: value) { + if let perk = PremiumPerk(identifier: value, business: false) { if !perks.contains(perk) { perks.append(perk) } else { @@ -825,7 +835,29 @@ struct PremiumIntroConfiguration { perks.append(.business) } #endif - return PremiumIntroConfiguration(perks: perks) + + + var businessPerks: [PremiumPerk] = [] + if let values = data["business_promo_order"] as? [String] { + for value in values { + if let perk = PremiumPerk(identifier: value, business: true) { + if !businessPerks.contains(perk) { + businessPerks.append(perk) + } else { + businessPerks = [] + break + } + } else { + businessPerks = [] + break + } + } + } + if businessPerks.count < 4 { + businessPerks = PremiumIntroConfiguration.defaultValue.businessPerks + } + + return PremiumIntroConfiguration(perks: perks, businessPerks: businessPerks) } else { return .defaultValue } @@ -1798,6 +1830,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { UIColor(rgb: 0x9b4fed), UIColor(rgb: 0x8958ff), UIColor(rgb: 0x676bff), + UIColor(rgb: 0x676bff), //replace UIColor(rgb: 0x6172ff), UIColor(rgb: 0x5b79ff), UIColor(rgb: 0x4492ff), @@ -2109,7 +2142,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { var i = 0 var perksItems: [AnyComponentWithIdentity] = [] - for perk in BusinessPerk.allCases { + for perk in state.configuration.businessPerks { perksItems.append(AnyComponentWithIdentity(id: perksItems.count, component: AnyComponent(ListActionItemComponent( theme: environment.theme, title: AnyComponent(VStack([ @@ -2140,7 +2173,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let isPremium = state?.isPremium == true if isPremium { switch perk { - case .location: + case .businessLocation: let _ = (accountContext.engine.data.get( TelegramEngine.EngineData.Item.Peer.BusinessLocation(id: accountContext.account.peerId) ) @@ -2150,7 +2183,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeBusinessLocationSetupScreen(context: accountContext, initialValue: businessLocation, completion: { _ in })) }) - case .hours: + case .businessHours: let _ = (accountContext.engine.data.get( TelegramEngine.EngineData.Item.Peer.BusinessHours(id: accountContext.account.peerId) ) @@ -2160,7 +2193,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeBusinessHoursSetupScreen(context: accountContext, initialValue: businessHours, completion: { _ in })) }) - case .quickReplies: + case .businessQuickReplies: let _ = (accountContext.sharedContext.makeQuickReplySetupScreenInitialData(context: accountContext) |> take(1) |> deliverOnMainQueue).start(next: { [weak accountContext] initialData in @@ -2169,7 +2202,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeQuickReplySetupScreen(context: accountContext, initialData: initialData)) }) - case .greetings: + case .businessGreetingMessage: let _ = (accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreenInitialData(context: accountContext) |> take(1) |> deliverOnMainQueue).start(next: { [weak accountContext] initialData in @@ -2178,7 +2211,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, initialData: initialData, isAwayMode: false)) }) - case .awayMessages: + case .businessAwayMessage: let _ = (accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreenInitialData(context: accountContext) |> take(1) |> deliverOnMainQueue).start(next: { [weak accountContext] initialData in @@ -2187,7 +2220,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeAutomaticBusinessMessageSetupScreen(context: accountContext, initialData: initialData, isAwayMode: true)) }) - case .chatbots: + case .businessChatBots: let _ = (accountContext.sharedContext.makeChatbotSetupScreenInitialData(context: accountContext) |> take(1) |> deliverOnMainQueue).start(next: { [weak accountContext] initialData in @@ -2196,25 +2229,29 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { } push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext, initialData: initialData)) }) + default: + fatalError() } } else { var demoSubject: PremiumDemoScreen.Subject switch perk { - case .location: + case .businessLocation: demoSubject = .businessLocation - case .hours: + case .businessHours: demoSubject = .businessHours - case .quickReplies: + case .businessQuickReplies: demoSubject = .businessQuickReplies - case .greetings: + case .businessGreetingMessage: demoSubject = .businessGreetingMessage - case .awayMessages: + case .businessAwayMessage: demoSubject = .businessAwayMessage - case .chatbots: + case .businessChatBots: demoSubject = .businessChatBots + default: + fatalError() } var dismissImpl: (() -> Void)? - let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: [.businessLocation, .businessHours, .businessQuickReplies, .businessGreetingMessage, .businessAwayMessage, .businessChatBots], buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "—").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium, forceDark: forceDark) + let controller = PremiumLimitsListScreen(context: accountContext, subject: demoSubject, source: .intro(state?.price), order: state?.configuration.businessPerks, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "—").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium, forceDark: forceDark) controller.action = { [weak state] in dismissImpl?() if state?.isPremium == false { diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 72ec09c895..f454f44991 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2000,6 +2000,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .readTime case .messageTags: mappedSource = .messageTags + case .folderTags: + mappedSource = .folderTags } let controller = PremiumIntroScreen(context: context, source: mappedSource, modal: modal, forceDark: forceDark) controller.wasDismissed = dismissed @@ -2049,6 +2051,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSubject = .lastSeen case .messagePrivacy: mappedSubject = .messagePrivacy + case .folderTags: + mappedSubject = .folderTags default: mappedSubject = .doubleLimits } From 64eaaae4fd429931fae21cbb290e238fbc0a39ba Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 29 Feb 2024 01:36:56 +0400 Subject: [PATCH 5/8] Various improvements --- .../Sources/DrawingEntitiesView.swift | 2 +- .../Stickers/TelegramEngineStickers.swift | 6 + .../MetalResources/EditorDual.metal | 22 ++- .../Sources/ImageObjectSeparation.swift | 64 +++++++++ .../MediaEditor/Sources/MediaEditor.swift | 46 +++++++ .../Sources/MediaEditorRenderer.swift | 3 +- .../Sources/UniversalTextureSource.swift | 7 + .../MediaEditor/Sources/VideoFinishPass.swift | 55 ++++++-- .../Components/MediaEditorScreen/BUILD | 1 + .../Sources/MediaCutoutScreen.swift | 95 +++++++++++-- .../Sources/MediaEditorScreen.swift | 97 +++++++++----- .../CutoutUndo.imageset/Contents.json | 12 ++ .../CutoutUndo.imageset/undo2_30.pdf | 126 ++++++++++++++++++ 13 files changed, 474 insertions(+), 62 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/CutoutUndo.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Media Editor/CutoutUndo.imageset/undo2_30.pdf diff --git a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift index 5d6719096e..34253dde77 100644 --- a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift +++ b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift @@ -885,7 +885,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView { } else if self.autoSelectEntities, gestureRecognizer.numberOfTouches == 1, let viewToSelect = self.entity(at: location) { self.selectEntity(viewToSelect.entity, animate: false) self.onInteractionUpdated(true) - } else if gestureRecognizer.numberOfTouches == 2 || self.isStickerEditor, let mediaEntityView = self.subviews.first(where: { $0 is DrawingEntityMediaView }) as? DrawingEntityMediaView { + } else if gestureRecognizer.numberOfTouches == 2 || (self.isStickerEditor && self.autoSelectEntities), let mediaEntityView = self.subviews.first(where: { $0 is DrawingEntityMediaView }) as? DrawingEntityMediaView { mediaEntityView.handlePan(gestureRecognizer) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index 8e607e3dce..acc8702807 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -257,6 +257,12 @@ public extension TelegramEngine { return (items.map(\.file), isFinalResult) } } + + public func addRecentlyUsedSticker(file: TelegramMediaFile) { + let _ = self.account.postbox.transaction({ transaction -> Void in + TelegramCore.addRecentlyUsedSticker(transaction: transaction, fileReference: .standalone(media: file)) + }).start() + } } } diff --git a/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorDual.metal b/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorDual.metal index c45b5ec9ff..a4fa037c62 100644 --- a/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorDual.metal +++ b/submodules/TelegramUI/Components/MediaEditor/MetalResources/EditorDual.metal @@ -3,6 +3,14 @@ using namespace metal; +typedef struct { + float2 dimensions; + float roundness; + float alpha; + float isOpaque; + float empty; +} VideoEncodeParameters; + typedef struct { float4 pos; float2 texCoord; @@ -17,11 +25,10 @@ float sdfRoundedRectangle(float2 uv, float2 position, float2 size, float radius) fragment half4 dualFragmentShader(RasterizerData in [[stage_in]], texture2d texture [[texture(0)]], - constant uint2 &resolution[[buffer(0)]], - constant float &roundness[[buffer(1)]], - constant float &alpha[[buffer(2)]] + texture2d mask [[texture(1)]], + constant VideoEncodeParameters& adjustments [[buffer(0)]] ) { - float2 R = float2(resolution.x, resolution.y); + float2 R = float2(adjustments.dimensions.x, adjustments.dimensions.y); float2 uv = (in.localPos - float2(0.5, 0.5)) * 2.0; if (R.x > R.y) { @@ -33,10 +40,11 @@ fragment half4 dualFragmentShader(RasterizerData in [[stage_in]], constexpr sampler samplr(filter::linear, mag_filter::linear, min_filter::linear); half3 color = texture.sample(samplr, in.texCoord).rgb; + float colorAlpha = min(1.0, adjustments.isOpaque + mask.sample(samplr, in.texCoord).r); - float t = 1.0 / resolution.y; + float t = 1.0 / adjustments.dimensions.y; float side = 1.0 * aspectRatio; - float distance = smoothstep(t, -t, sdfRoundedRectangle(uv, float2(0.0, 0.0), float2(side, mix(1.0, side, roundness)), side * roundness)); + float distance = smoothstep(t, -t, sdfRoundedRectangle(uv, float2(0.0, 0.0), float2(side, mix(1.0, side, adjustments.roundness)), side * adjustments.roundness)); - return mix(half4(color, 0.0), half4(color, 1.0 * alpha), distance); + return mix(half4(color, 0.0), half4(color, colorAlpha * adjustments.alpha), distance); } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift index 9ecd12429b..fe2f1100f0 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/ImageObjectSeparation.swift @@ -55,6 +55,70 @@ public func cutoutStickerImage(from image: UIImage, onlyCheck: Bool = false) -> } } +public enum CutoutResult { + case image(UIImage) + case pixelBuffer(CVPixelBuffer) +} + +public func cutoutImage(from image: UIImage, atPoint point: CGPoint?, asImage: Bool) -> Signal { + if #available(iOS 17.0, *) { + guard let cgImage = image.cgImage else { + return .single(nil) + } + return Signal { subscriber in + let ciContext = CIContext(options: nil) + let inputImage = CIImage(cgImage: cgImage) + let handler = VNImageRequestHandler(cgImage: cgImage, options: [:]) + let request = VNGenerateForegroundInstanceMaskRequest { [weak handler] request, error in + guard let handler, let result = request.results?.first as? VNInstanceMaskObservation else { + subscriber.putNext(nil) + subscriber.putCompletion() + return + } + + let instances = IndexSet(instances(atPoint: point, inObservation: result).prefix(1)) + if let mask = try? result.generateScaledMaskForImage(forInstances: instances, from: handler) { + if asImage { + let filter = CIFilter.blendWithMask() + filter.inputImage = inputImage + filter.backgroundImage = CIImage(color: .clear) + filter.maskImage = CIImage(cvPixelBuffer: mask) + if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) { + let image = UIImage(cgImage: cgImage) + subscriber.putNext(.image(image)) + subscriber.putCompletion() + return + } + } else { + let filter = CIFilter.blendWithMask() + filter.inputImage = CIImage(color: .white) + filter.backgroundImage = CIImage(color: .black) + filter.maskImage = CIImage(cvPixelBuffer: mask) + if let output = filter.outputImage, let cgImage = ciContext.createCGImage(output, from: inputImage.extent) { + let image = UIImage(cgImage: cgImage) + subscriber.putNext(.image(image)) + subscriber.putCompletion() + return + } +// subscriber.putNext(.pixelBuffer(mask)) +// subscriber.putCompletion() + } + } + subscriber.putNext(nil) + subscriber.putCompletion() + } + + try? handler.perform([request]) + return ActionDisposable { + request.cancel() + } + } + |> runOn(queue) + } else { + return .single(nil) + } +} + @available(iOS 17.0, *) private func instances(atPoint maybePoint: CGPoint?, inObservation observation: VNInstanceMaskObservation) -> IndexSet { guard let point = maybePoint else { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index fe03b681b0..183b2405e3 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -190,6 +190,7 @@ public final class MediaEditor { public private(set) var canCutout: Bool = false public var canCutoutUpdated: (Bool) -> Void = { _ in } + public var isCutoutUpdated: (Bool) -> Void = { _ in } private var textureCache: CVMetalTextureCache! @@ -1682,6 +1683,51 @@ public final class MediaEditor { self.renderer.renderFrame() } + public func getSeparatedImage(point: CGPoint?) -> Signal { + guard let textureSource = self.renderer.textureSource as? UniversalTextureSource, let image = textureSource.mainImage else { + return .single(nil) + } + return cutoutImage(from: image, atPoint: point, asImage: true) + |> map { result in + if let result, case let .image(image) = result { + return image + } else { + return nil + } + } + } + + public func removeSeparationMask() { + self.isCutoutUpdated(false) + + self.renderer.currentMainInputMask = nil + if !self.skipRendering { + self.updateRenderChain() + } + } + + public func setSeparationMask(point: CGPoint?) { + guard let renderTarget = self.previewView, let device = renderTarget.mtlDevice else { + return + } + guard let textureSource = self.renderer.textureSource as? UniversalTextureSource, let image = textureSource.mainImage else { + return + } + self.isCutoutUpdated(true) + + let _ = (cutoutImage(from: image, atPoint: point, asImage: false) + |> deliverOnMainQueue).start(next: { [weak self] result in + guard let self, let result, case let .image(image) = result else { + return + } + //TODO:replace with pixelbuffer + self.renderer.currentMainInputMask = loadTexture(image: image, device: device) + if !self.skipRendering { + self.updateRenderChain() + } + }) + } + private func maybeGeneratePersonSegmentation(_ image: UIImage?) { if #available(iOS 15.0, *), let cgImage = image?.cgImage { let faceRequest = VNDetectFaceRectanglesRequest { [weak self] request, _ in diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift index 0242d8dc45..c4bddd4725 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorRenderer.swift @@ -98,6 +98,7 @@ final class MediaEditorRenderer { } private var currentMainInput: Input? + var currentMainInputMask: MTLTexture? private var currentAdditionalInput: Input? private(set) var resultTexture: MTLTexture? @@ -202,7 +203,7 @@ final class MediaEditorRenderer { } if let mainTexture { - return self.videoFinishPass.process(input: mainTexture, secondInput: additionalTexture, timestamp: mainInput.timestamp, device: device, commandBuffer: commandBuffer) + return self.videoFinishPass.process(input: mainTexture, inputMask: self.currentMainInputMask, secondInput: additionalTexture, timestamp: mainInput.timestamp, device: device, commandBuffer: commandBuffer) } else { return nil } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/UniversalTextureSource.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/UniversalTextureSource.swift index 71f84cdfa1..4e7a97f844 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/UniversalTextureSource.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/UniversalTextureSource.swift @@ -42,6 +42,13 @@ final class UniversalTextureSource: TextureSource { ) } + var mainImage: UIImage? { + if let mainInput = self.mainInputContext?.input, case let .image(image) = mainInput { + return image + } + return nil + } + func setMainInput(_ input: Input) { guard let renderTarget = self.renderTarget else { return diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift index bd96115295..d6e3fcbe43 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/VideoFinishPass.swift @@ -144,6 +144,14 @@ private var transitionDuration = 0.5 private var apperanceDuration = 0.2 private var videoRemovalDuration: Double = 0.2 +struct VideoEncodeParameters { + var dimensions: simd_float2 + var roundness: simd_float1 + var alpha: simd_float1 + var isOpaque: simd_float1 + var empty: simd_float1 +} + final class VideoFinishPass: RenderPass { private var cachedTexture: MTLTexture? @@ -195,6 +203,7 @@ final class VideoFinishPass: RenderPass { containerSize: CGSize, texture: MTLTexture, textureRotation: TextureRotation, + maskTexture: MTLTexture?, position: VideoPosition, roundness: Float, alpha: Float, @@ -202,6 +211,11 @@ final class VideoFinishPass: RenderPass { device: MTLDevice ) { encoder.setFragmentTexture(texture, index: 0) + if let maskTexture { + encoder.setFragmentTexture(maskTexture, index: 1) + } else { + encoder.setFragmentTexture(texture, index: 1) + } let center = CGPoint( x: position.position.x - containerSize.width / 2.0, @@ -220,14 +234,25 @@ final class VideoFinishPass: RenderPass { options: []) encoder.setVertexBuffer(buffer, offset: 0, index: 0) - var resolution = simd_uint2(UInt32(size.width), UInt32(size.height)) - encoder.setFragmentBytes(&resolution, length: MemoryLayout.size * 2, index: 0) - - var roundness = roundness - encoder.setFragmentBytes(&roundness, length: MemoryLayout.size, index: 1) - - var alpha = alpha - encoder.setFragmentBytes(&alpha, length: MemoryLayout.size, index: 2) + var parameters = VideoEncodeParameters( + dimensions: simd_float2(Float(size.width), Float(size.height)), + roundness: roundness, + alpha: alpha, + isOpaque: maskTexture == nil ? 1.0 : 0.0, + empty: 0 + ) + encoder.setFragmentBytes(¶meters, length: MemoryLayout.size, index: 0) +// var resolution = simd_uint2(UInt32(size.width), UInt32(size.height)) +// encoder.setFragmentBytes(&resolution, length: MemoryLayout.size * 2, index: 0) +// +// var roundness = roundness +// encoder.setFragmentBytes(&roundness, length: MemoryLayout.size, index: 1) +// +// var alpha = alpha +// encoder.setFragmentBytes(&alpha, length: MemoryLayout.size, index: 2) +// +// var isOpaque = maskTexture == nil ? 1.0 : 0.0 +// encoder.setFragmentBytes(&isOpaque, length: MemoryLayout.size, index: 3) encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4) } @@ -478,7 +503,14 @@ final class VideoFinishPass: RenderPass { return (backgroundVideoState, foregroundVideoState, disappearingVideoState) } - func process(input: MTLTexture, secondInput: MTLTexture?, timestamp: CMTime, device: MTLDevice, commandBuffer: MTLCommandBuffer) -> MTLTexture? { + func process( + input: MTLTexture, + inputMask: MTLTexture?, + secondInput: MTLTexture?, + timestamp: CMTime, + device: MTLDevice, + commandBuffer: MTLCommandBuffer + ) -> MTLTexture? { if !self.isStory { return input } @@ -536,7 +568,6 @@ final class VideoFinishPass: RenderPass { ) if self.gradientColors.topColor.w > 0.0 { - renderCommandEncoder.setRenderPipelineState(self.gradientPipelineState!) self.encodeGradient( using: renderCommandEncoder, containerSize: containerSize, @@ -554,6 +585,7 @@ final class VideoFinishPass: RenderPass { containerSize: containerSize, texture: transitionVideoState.texture, textureRotation: transitionVideoState.textureRotation, + maskTexture: nil, position: transitionVideoState.position, roundness: transitionVideoState.roundness, alpha: transitionVideoState.alpha, @@ -567,6 +599,7 @@ final class VideoFinishPass: RenderPass { containerSize: containerSize, texture: mainVideoState.texture, textureRotation: mainVideoState.textureRotation, + maskTexture: inputMask, position: mainVideoState.position, roundness: mainVideoState.roundness, alpha: mainVideoState.alpha, @@ -580,6 +613,7 @@ final class VideoFinishPass: RenderPass { containerSize: containerSize, texture: additionalVideoState.texture, textureRotation: additionalVideoState.textureRotation, + maskTexture: nil, position: additionalVideoState.position, roundness: additionalVideoState.roundness, alpha: additionalVideoState.alpha, @@ -603,6 +637,7 @@ final class VideoFinishPass: RenderPass { containerSize: CGSize, device: MTLDevice ) { + encoder.setRenderPipelineState(self.gradientPipelineState!) let vertices = verticesDataForRotation(.rotate0Degrees) let buffer = device.makeBuffer( diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD index 728aedc52c..88ab5ba5ef 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/BUILD +++ b/submodules/TelegramUI/Components/MediaEditorScreen/BUILD @@ -51,6 +51,7 @@ swift_library( "//submodules/TelegramUI/Components/ContextReferenceButtonComponent", "//submodules/TelegramUI/Components/MediaScrubberComponent", "//submodules/Components/BlurredBackgroundComponent", + "//submodules/TelegramUI/Components/DustEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift index 2e411f9cf7..e1b3c046cc 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCutoutScreen.swift @@ -15,6 +15,7 @@ import MediaEditor import Photos import LottieAnimationComponent import MessageInputPanelComponent +import DustEffect private final class MediaCutoutScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -40,9 +41,13 @@ private final class MediaCutoutScreenComponent: Component { public final class View: UIView { private let buttonsContainerView = UIView() private let buttonsBackgroundView = UIView() + private let previewContainerView = UIView() private let cancelButton = ComponentView() private let label = ComponentView() private let doneButton = ComponentView() + + private let fadeView = UIView() + private let separatedImageView = UIImageView() private var component: MediaCutoutScreenComponent? private weak var state: State? @@ -51,18 +56,44 @@ private final class MediaCutoutScreenComponent: Component { override init(frame: CGRect) { self.buttonsContainerView.clipsToBounds = true + self.fadeView.alpha = 0.0 + self.fadeView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.6) + + self.separatedImageView.contentMode = .scaleAspectFit + super.init(frame: frame) self.backgroundColor = .clear self.addSubview(self.buttonsContainerView) self.buttonsContainerView.addSubview(self.buttonsBackgroundView) + + self.addSubview(self.fadeView) + self.addSubview(self.separatedImageView) + self.addSubview(self.previewContainerView) + + self.previewContainerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.previewTap(_:)))) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + @objc private func previewTap(_ gestureRecognizer: UITapGestureRecognizer) { + guard let component = self.component else { + return + } + let location = gestureRecognizer.location(in: gestureRecognizer.view) + + let point = CGPoint( + x: location.x / self.previewContainerView.frame.width, + y: location.y / self.previewContainerView.frame.height + ) + component.mediaEditor.setSeparationMask(point: point) + + self.playDissolveAnimation() + } + func animateInFromEditor() { self.buttonsBackgroundView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.label.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -74,6 +105,7 @@ private final class MediaCutoutScreenComponent: Component { self.cancelButton.view?.isHidden = true + self.fadeView.layer.animateAlpha(from: self.fadeView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) self.buttonsBackgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in completion() }) @@ -82,14 +114,35 @@ private final class MediaCutoutScreenComponent: Component { self.state?.updated() } + public func playDissolveAnimation() { + guard let component = self.component, let resultImage = component.mediaEditor.resultImage, let environment = self.environment, let controller = environment.controller() as? MediaCutoutScreen else { + return + } + let previewView = controller.previewView + + let dustEffectLayer = DustEffectLayer() + dustEffectLayer.position = previewView.center + dustEffectLayer.bounds = previewView.bounds + previewView.superview?.layer.insertSublayer(dustEffectLayer, below: previewView.layer) + + dustEffectLayer.animationSpeed = 2.2 + dustEffectLayer.becameEmpty = { [weak dustEffectLayer] in + dustEffectLayer?.removeFromSuperlayer() + } + + dustEffectLayer.addItem(frame: previewView.bounds, image: resultImage) + + controller.requestDismiss(animated: true) + } + func update(component: MediaCutoutScreenComponent, availableSize: CGSize, state: State, environment: Environment, transition: Transition) -> CGSize { let environment = environment[ViewControllerComponentContainer.Environment.self].value self.environment = environment + let isFirstTime = self.component == nil self.component = component self.state = state -// let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } let isTablet: Bool if case .regular = environment.metrics.widthClass { isTablet = true @@ -97,8 +150,6 @@ private final class MediaCutoutScreenComponent: Component { isTablet = false } -// let mediaEditor = (environment.controller() as? MediaCutoutScreen)?.mediaEditor - let buttonSideInset: CGFloat let buttonBottomInset: CGFloat = 8.0 var controlsBottomInset: CGFloat = 0.0 @@ -119,7 +170,7 @@ private final class MediaCutoutScreenComponent: Component { } } -// var previewContainerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: environment.safeInsets.top), size: CGSize(width: previewSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom + controlsBottomInset)) + let previewContainerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: environment.safeInsets.top), size: CGSize(width: previewSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom + controlsBottomInset)) let buttonsContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - environment.safeInsets.bottom + controlsBottomInset), size: CGSize(width: availableSize.width, height: environment.safeInsets.bottom - controlsBottomInset)) let cancelButtonSize = self.cancelButton.update( @@ -140,7 +191,7 @@ private final class MediaCutoutScreenComponent: Component { guard let controller = environment.controller() as? MediaCutoutScreen else { return } - controller.requestDismiss(reset: true, animated: true) + controller.requestDismiss(animated: true) } )), environment: {}, @@ -177,6 +228,30 @@ private final class MediaCutoutScreenComponent: Component { transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame) transition.setFrame(view: self.buttonsBackgroundView, frame: CGRect(origin: .zero, size: buttonsContainerFrame.size)) + transition.setFrame(view: self.previewContainerView, frame: previewContainerFrame) + transition.setFrame(view: self.separatedImageView, frame: previewContainerFrame) + + let frameWidth = floor(previewContainerFrame.width * 0.97) + + self.fadeView.frame = CGRect(x: floorToScreenPixels((previewContainerFrame.width - frameWidth) / 2.0), y: previewContainerFrame.minY + floorToScreenPixels((previewContainerFrame.height - frameWidth) / 2.0), width: frameWidth, height: frameWidth) + self.fadeView.layer.cornerRadius = frameWidth / 8.0 + + if isFirstTime { + let _ = (component.mediaEditor.getSeparatedImage(point: nil) + |> deliverOnMainQueue).start(next: { [weak self] image in + guard let self else { + return + } + self.separatedImageView.image = image + self.state?.updated(transition: .easeInOut(duration: 0.2)) + }) + } else { + if let _ = self.separatedImageView.image { + transition.setAlpha(view: self.fadeView, alpha: 1.0) + } else { + transition.setAlpha(view: self.fadeView, alpha: 0.0) + } + } return availableSize } } @@ -315,14 +390,16 @@ public final class MediaCutoutScreen: ViewController { fileprivate let context: AccountContext fileprivate let mediaEditor: MediaEditor + fileprivate let previewView: MediaEditorPreviewView public var dismissed: () -> Void = {} private var initialValues: MediaEditorValues - public init(context: AccountContext, mediaEditor: MediaEditor) { + public init(context: AccountContext, mediaEditor: MediaEditor, previewView: MediaEditorPreviewView) { self.context = context self.mediaEditor = mediaEditor + self.previewView = previewView self.initialValues = mediaEditor.values.makeCopy() super.init(navigationBarPresentationData: nil) @@ -343,11 +420,7 @@ public final class MediaCutoutScreen: ViewController { super.displayNodeDidLoad() } - func requestDismiss(reset: Bool, animated: Bool) { - if reset { - self.mediaEditor.values = self.initialValues - } - + func requestDismiss(animated: Bool) { self.dismissed() self.node.animateOutToEditor(completion: { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 2f078dad8e..abae6ccac6 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -154,6 +154,7 @@ final class MediaEditorScreenComponent: Component { case tools case done case cutout + case undo } private var cachedImages: [ImageKey: UIImage] = [:] func image(_ key: ImageKey) -> UIImage { @@ -172,6 +173,8 @@ final class MediaEditorScreenComponent: Component { image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Tools"), color: .white)! case .cutout: image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Cutout"), color: .white)! + case .undo: + image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/CutoutUndo"), color: .white)! case .done: image = generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) @@ -981,13 +984,14 @@ final class MediaEditorScreenComponent: Component { } if controller.node.canCutout { + let isCutout = controller.node.isCutout let cutoutButtonSize = self.cutoutButton.update( transition: transition, component: AnyComponent(PlainButtonComponent( content: AnyComponent(CutoutButtonContentComponent( backgroundColor: UIColor(rgb: 0xffffff, alpha: 0.18), - icon: state.image(.cutout), - title: "Cut Out an Object" + icon: state.image(isCutout ? .undo : .cutout), + title: isCutout ? "Undo Cut Out" : "Cut Out an Object" )), effectAlignment: .center, action: { @@ -2161,6 +2165,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate private var isDismissBySwipeSuppressed = false fileprivate var canCutout = false + fileprivate var isCutout = false private (set) var hasAnyChanges = false @@ -2513,7 +2518,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.canCutout = canCutout controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut)) } - + mediaEditor.isCutoutUpdated = { [weak self] isCutout in + guard let self else { + return + } + self.isCutout = isCutout + self.requestLayout(forceUpdate: true, transition: .immediate) + } if case .message = effectiveSubject { } else { @@ -4231,14 +4242,25 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.entitiesView.selectEntity(nil) } - let controller = MediaCutoutScreen(context: self.context, mediaEditor: mediaEditor) - controller.dismissed = { [weak self] in - if let self { - self.animateInFromTool(inPlace: true) + if controller.node.isCutout { + let snapshotView = self.previewView.snapshotView(afterScreenUpdates: false) + if let snapshotView { + self.previewView.superview?.addSubview(snapshotView) } + self.previewView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, completion: { _ in + snapshotView?.removeFromSuperview() + }) + mediaEditor.removeSeparationMask() + } else { + let controller = MediaCutoutScreen(context: self.context, mediaEditor: mediaEditor, previewView: self.previewView) + controller.dismissed = { [weak self] in + if let self { + self.animateInFromTool(inPlace: true) + } + } + self.controller?.present(controller, in: .window(.root)) + self.animateOutToTool(inPlace: true) } - self.controller?.present(controller, in: .window(.root)) - self.animateOutToTool(inPlace: true) } } ) @@ -5084,35 +5106,46 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - let title: String - let save: String - if case .draft = self.node.actualSubject { - title = presentationData.strings.Story_Editor_DraftDiscardDraft - save = presentationData.strings.Story_Editor_DraftKeepDraft - } else { + var title: String + var text: String + var save: String? + switch self.mode { + case .storyEditor: + if case .draft = self.node.actualSubject { + title = presentationData.strings.Story_Editor_DraftDiscardDraft + save = presentationData.strings.Story_Editor_DraftKeepDraft + } else { + title = presentationData.strings.Story_Editor_DraftDiscardMedia + save = presentationData.strings.Story_Editor_DraftKeepMedia + } + text = presentationData.strings.Story_Editor_DraftDiscaedText + case .stickerEditor: title = presentationData.strings.Story_Editor_DraftDiscardMedia - save = presentationData.strings.Story_Editor_DraftKeepMedia + text = presentationData.strings.Story_Editor_DiscardText } + + var actions: [TextAlertAction] = [] + actions.append(TextAlertAction(type: .destructiveAction, title: presentationData.strings.Story_Editor_DraftDiscard, action: { [weak self] in + if let self { + self.requestDismiss(saveDraft: false, animated: true) + } + })) + if let save { + actions.append(TextAlertAction(type: .genericAction, title: save, action: { [weak self] in + if let self { + self.requestDismiss(saveDraft: true, animated: true) + } + })) + } + actions.append(TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { + + })) let controller = textAlertController( context: self.context, forceTheme: defaultDarkPresentationTheme, title: title, - text: presentationData.strings.Story_Editor_DraftDiscaedText, - actions: [ - TextAlertAction(type: .destructiveAction, title: presentationData.strings.Story_Editor_DraftDiscard, action: { [weak self] in - if let self { - self.requestDismiss(saveDraft: false, animated: true) - } - }), - TextAlertAction(type: .genericAction, title: save, action: { [weak self] in - if let self { - self.requestDismiss(saveDraft: true, animated: true) - } - }), - TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { - - }) - ], + text: text, + actions: actions, actionLayout: .vertical ) self.present(controller, in: .window(.root)) diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/CutoutUndo.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Editor/CutoutUndo.imageset/Contents.json new file mode 100644 index 0000000000..4a8e6450d0 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/CutoutUndo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "undo2_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Media Editor/CutoutUndo.imageset/undo2_30.pdf b/submodules/TelegramUI/Images.xcassets/Media Editor/CutoutUndo.imageset/undo2_30.pdf new file mode 100644 index 0000000000..e814c8183d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Media Editor/CutoutUndo.imageset/undo2_30.pdf @@ -0,0 +1,126 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 5.000000 4.258789 cm +0.000000 0.000000 0.000000 scn +1.000000 2.571211 m +0.541604 2.571211 0.170000 2.199607 0.170000 1.741211 c +0.170000 1.282815 0.541604 0.911211 1.000000 0.911211 c +1.000000 2.571211 l +h +1.000000 13.571211 m +0.541604 13.571211 0.170000 13.199608 0.170000 12.741211 c +0.170000 12.282814 0.541604 11.911211 1.000000 11.911211 c +1.000000 13.571211 l +h +3.413101 8.154312 m +3.737236 7.830177 4.262764 7.830177 4.586899 8.154312 c +4.911034 8.478448 4.911034 9.003975 4.586899 9.328110 c +3.413101 8.154312 l +h +0.000000 12.741211 m +-0.586899 13.328110 l +-0.911034 13.003975 -0.911034 12.478447 -0.586899 12.154312 c +0.000000 12.741211 l +h +4.586899 16.154312 m +4.911034 16.478447 4.911034 17.003975 4.586899 17.328110 c +4.262764 17.652245 3.737236 17.652245 3.413101 17.328110 c +4.586899 16.154312 l +h +1.000000 0.911211 m +8.500000 0.911211 l +8.500000 2.571211 l +1.000000 2.571211 l +1.000000 0.911211 l +h +8.500000 13.571211 m +1.000000 13.571211 l +1.000000 11.911211 l +8.500000 11.911211 l +8.500000 13.571211 l +h +4.586899 9.328110 m +0.586899 13.328110 l +-0.586899 12.154312 l +3.413101 8.154312 l +4.586899 9.328110 l +h +0.586899 12.154312 m +4.586899 16.154312 l +3.413101 17.328110 l +-0.586899 13.328110 l +0.586899 12.154312 l +h +14.830000 7.241211 m +14.830000 10.737173 11.995962 13.571211 8.500000 13.571211 c +8.500000 11.911211 l +11.079169 11.911211 13.170000 9.820381 13.170000 7.241211 c +14.830000 7.241211 l +h +8.500000 0.911211 m +11.995962 0.911211 14.830000 3.745249 14.830000 7.241211 c +13.170000 7.241211 l +13.170000 4.662042 11.079169 2.571211 8.500000 2.571211 c +8.500000 0.911211 l +h +f +n +Q + +endstream +endobj + +3 0 obj + 1673 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001763 00000 n +0000001786 00000 n +0000001959 00000 n +0000002033 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2092 +%%EOF \ No newline at end of file From 02a2c2d3592d367c03ebb3e637ea1741805ddc7c Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 29 Feb 2024 02:31:03 +0400 Subject: [PATCH 6/8] Update API --- submodules/TelegramApi/Sources/Api0.swift | 6 ++--- submodules/TelegramApi/Sources/Api2.swift | 26 +++++++++++-------- submodules/TelegramApi/Sources/Api7.swift | 26 +++++++++++-------- .../Messages/QuickReplyMessages.swift | 4 ++- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index d6ea57e18e..1a147326a5 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -102,7 +102,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-944407322] = { return Api.BotMenuButton.parse_botMenuButton($0) } dict[1113113093] = { return Api.BotMenuButton.parse_botMenuButtonCommands($0) } dict[1966318984] = { return Api.BotMenuButton.parse_botMenuButtonDefault($0) } - dict[467254972] = { return Api.BusinessAwayMessage.parse_businessAwayMessage($0) } + dict[-283809188] = { return Api.BusinessAwayMessage.parse_businessAwayMessage($0) } dict[-910564679] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleAlways($0) } dict[-867328308] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleCustom($0) } dict[-1007487743] = { return Api.BusinessAwayMessageSchedule.parse_businessAwayMessageScheduleOutsideWorkHours($0) } @@ -308,7 +308,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-459324] = { return Api.InputBotInlineResult.parse_inputBotInlineResultDocument($0) } dict[1336154098] = { return Api.InputBotInlineResult.parse_inputBotInlineResultGame($0) } dict[-1462213465] = { return Api.InputBotInlineResult.parse_inputBotInlineResultPhoto($0) } - dict[-307493900] = { return Api.InputBusinessAwayMessage.parse_inputBusinessAwayMessage($0) } + dict[-2094959136] = { return Api.InputBusinessAwayMessage.parse_inputBusinessAwayMessage($0) } dict[26528571] = { return Api.InputBusinessGreetingMessage.parse_inputBusinessGreetingMessage($0) } dict[1871393450] = { return Api.InputBusinessRecipients.parse_inputBusinessRecipients($0) } dict[-212145112] = { return Api.InputChannel.parse_inputChannel($0) } @@ -1315,7 +1315,7 @@ public extension Api { return parser(reader) } else { - telegramApiLog("Type constructor \(String(UInt32(bitPattern: signature), radix: 16, uppercase: false)) not found") + telegramApiLog("Type constructor \(String(signature, radix: 16, uppercase: false)) not found") return nil } } diff --git a/submodules/TelegramApi/Sources/Api2.swift b/submodules/TelegramApi/Sources/Api2.swift index aaea1b49c7..9436ea5239 100644 --- a/submodules/TelegramApi/Sources/Api2.swift +++ b/submodules/TelegramApi/Sources/Api2.swift @@ -590,14 +590,15 @@ public extension Api { } public extension Api { enum BusinessAwayMessage: TypeConstructorDescription { - case businessAwayMessage(shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, recipients: Api.BusinessRecipients) + case businessAwayMessage(flags: Int32, shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, recipients: Api.BusinessRecipients) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .businessAwayMessage(let shortcutId, let schedule, let recipients): + case .businessAwayMessage(let flags, let shortcutId, let schedule, let recipients): if boxed { - buffer.appendInt32(467254972) + buffer.appendInt32(-283809188) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(shortcutId, buffer: buffer, boxed: false) schedule.serialize(buffer, true) recipients.serialize(buffer, true) @@ -607,27 +608,30 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .businessAwayMessage(let shortcutId, let schedule, let recipients): - return ("businessAwayMessage", [("shortcutId", shortcutId as Any), ("schedule", schedule as Any), ("recipients", recipients as Any)]) + case .businessAwayMessage(let flags, let shortcutId, let schedule, let recipients): + return ("businessAwayMessage", [("flags", flags as Any), ("shortcutId", shortcutId as Any), ("schedule", schedule as Any), ("recipients", recipients as Any)]) } } public static func parse_businessAwayMessage(_ reader: BufferReader) -> BusinessAwayMessage? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.BusinessAwayMessageSchedule? + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.BusinessAwayMessageSchedule? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule + _3 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule } - var _3: Api.BusinessRecipients? + var _4: Api.BusinessRecipients? if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.BusinessRecipients + _4 = Api.parse(reader, signature: signature) as? Api.BusinessRecipients } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.BusinessAwayMessage.businessAwayMessage(shortcutId: _1!, schedule: _2!, recipients: _3!) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.BusinessAwayMessage.businessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, recipients: _4!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api7.swift b/submodules/TelegramApi/Sources/Api7.swift index 03c3e6512f..cbdf0ebf38 100644 --- a/submodules/TelegramApi/Sources/Api7.swift +++ b/submodules/TelegramApi/Sources/Api7.swift @@ -264,14 +264,15 @@ public extension Api { } public extension Api { enum InputBusinessAwayMessage: TypeConstructorDescription { - case inputBusinessAwayMessage(shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, recipients: Api.InputBusinessRecipients) + case inputBusinessAwayMessage(flags: Int32, shortcutId: Int32, schedule: Api.BusinessAwayMessageSchedule, recipients: Api.InputBusinessRecipients) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .inputBusinessAwayMessage(let shortcutId, let schedule, let recipients): + case .inputBusinessAwayMessage(let flags, let shortcutId, let schedule, let recipients): if boxed { - buffer.appendInt32(-307493900) + buffer.appendInt32(-2094959136) } + serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(shortcutId, buffer: buffer, boxed: false) schedule.serialize(buffer, true) recipients.serialize(buffer, true) @@ -281,27 +282,30 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .inputBusinessAwayMessage(let shortcutId, let schedule, let recipients): - return ("inputBusinessAwayMessage", [("shortcutId", shortcutId as Any), ("schedule", schedule as Any), ("recipients", recipients as Any)]) + case .inputBusinessAwayMessage(let flags, let shortcutId, let schedule, let recipients): + return ("inputBusinessAwayMessage", [("flags", flags as Any), ("shortcutId", shortcutId as Any), ("schedule", schedule as Any), ("recipients", recipients as Any)]) } } public static func parse_inputBusinessAwayMessage(_ reader: BufferReader) -> InputBusinessAwayMessage? { var _1: Int32? _1 = reader.readInt32() - var _2: Api.BusinessAwayMessageSchedule? + var _2: Int32? + _2 = reader.readInt32() + var _3: Api.BusinessAwayMessageSchedule? if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule + _3 = Api.parse(reader, signature: signature) as? Api.BusinessAwayMessageSchedule } - var _3: Api.InputBusinessRecipients? + var _4: Api.InputBusinessRecipients? if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.InputBusinessRecipients + _4 = Api.parse(reader, signature: signature) as? Api.InputBusinessRecipients } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputBusinessAwayMessage.inputBusinessAwayMessage(shortcutId: _1!, schedule: _2!, recipients: _3!) + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputBusinessAwayMessage.inputBusinessAwayMessage(flags: _1!, shortcutId: _2!, schedule: _3!, recipients: _4!) } else { return nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift index 81d67f73ab..5f4a0f96ea 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/QuickReplyMessages.swift @@ -590,7 +590,8 @@ public final class TelegramBusinessAwayMessage: Codable, Equatable { extension TelegramBusinessAwayMessage { convenience init(apiAwayMessage: Api.BusinessAwayMessage) { switch apiAwayMessage { - case let .businessAwayMessage(shortcutId, schedule, recipients): + case let .businessAwayMessage(flags, shortcutId, schedule, recipients): + let _ = flags let mappedSchedule: Schedule switch schedule { case .businessAwayMessageScheduleAlways: @@ -730,6 +731,7 @@ func _internal_updateBusinessAwayMessage(account: Account, awayMessage: Telegram } mappedMessage = .inputBusinessAwayMessage( + flags: 0, shortcutId: awayMessage.shortcutId, schedule: mappedSchedule, recipients: awayMessage.recipients.apiInputValue(additionalPeers: additionalPeers) From 840c80546fe562f53471bbca2af7d054ead7ce5f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 29 Feb 2024 02:31:14 +0400 Subject: [PATCH 7/8] Various fixes --- .../Sources/VideoMessageCameraScreen.swift | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 7f8af9e065..79f6fd787b 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -260,23 +260,25 @@ private final class VideoMessageCameraScreenComponent: CombinedComponent { controller.updateCameraState({ $0.updatedRecording(pressing ? .holding : .handsFree).updatedDuration(initialDuration) }, transition: .spring(duration: 0.4)) controller.node.withReadyCamera(isFirstTime: !controller.node.cameraIsActive) { - self.resultDisposable.set((camera.startRecording() - |> deliverOnMainQueue).start(next: { [weak self] recordingData in - let duration = initialDuration + recordingData.duration - if let self, let controller = self.getController() { - controller.updateCameraState({ $0.updatedDuration(duration) }, transition: .easeInOut(duration: 0.1)) - if isFirstRecording { - controller.node.setupLiveUpload(filePath: recordingData.filePath) + Queue.mainQueue().after(0.15) { + self.resultDisposable.set((camera.startRecording() + |> deliverOnMainQueue).start(next: { [weak self] recordingData in + let duration = initialDuration + recordingData.duration + if let self, let controller = self.getController() { + controller.updateCameraState({ $0.updatedDuration(duration) }, transition: .easeInOut(duration: 0.1)) + if isFirstRecording { + controller.node.setupLiveUpload(filePath: recordingData.filePath) + } + if duration > 59.5 { + controller.onStop() + } } - if duration > 59.5 { - controller.onStop() + }, error: { [weak self] _ in + if let self, let controller = self.getController() { + controller.completion(nil, nil, nil) } - } - }, error: { [weak self] _ in - if let self, let controller = self.getController() { - controller.completion(nil, nil, nil) - } - })) + })) + } } if initialDuration > 0.0 { From 96207f7d175fe045a7a1c892e4428152ca808394 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 29 Feb 2024 03:05:39 +0400 Subject: [PATCH 8/8] Various fixes --- submodules/TelegramUI/Sources/ChatController.swift | 9 +++++++++ submodules/WebUI/Sources/WebAppWebView.swift | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index e40ab50ba2..6eb9e1129f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -6144,6 +6144,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } + var appliedBoosts: Int32? + var boostsToUnrestrict: Int32? + if let cachedChannelData = peerView.cachedData as? CachedChannelData { + appliedBoosts = cachedChannelData.appliedBoosts + boostsToUnrestrict = cachedChannelData.boostsToUnrestrict + } + strongSelf.updateChatPresentationInterfaceState(animated: animated, interactive: false, { return $0.updatedPeer { _ in return renderedPeer @@ -6152,6 +6159,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G .updatedHasSearchTags(hasSearchTags) .updatedIsPremiumRequiredForMessaging(isPremiumRequiredForMessaging) .updatedHasSavedChats(hasSavedChats) + .updatedAppliedBoosts(appliedBoosts) + .updatedBoostsToUnrestrict(boostsToUnrestrict) .updatedInterfaceState { interfaceState in var interfaceState = interfaceState diff --git a/submodules/WebUI/Sources/WebAppWebView.swift b/submodules/WebUI/Sources/WebAppWebView.swift index 169383d6f7..28c66b0361 100644 --- a/submodules/WebUI/Sources/WebAppWebView.swift +++ b/submodules/WebUI/Sources/WebAppWebView.swift @@ -159,7 +159,7 @@ final class WebAppWebView: WKWebView { self.interactiveTransitionGestureRecognizerTest = { point -> Bool in return point.x > 30.0 } - self.allowsBackForwardNavigationGestures = false + self.allowsBackForwardNavigationGestures = true if #available(iOS 16.4, *) { self.isInspectable = true }