From 330fcfa5cd65db237a37c4a92a7d55216979208a Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 31 Mar 2023 23:44:19 +0400 Subject: [PATCH] Initial peer custom wallpapers implementation --- .../Telegram-iOS/en.lproj/Localizable.strings | 4 + .../Sources/GalleryControllerNode.swift | 4 + .../Sources/MediaPickerGridItem.swift | 21 +- .../Sources/MediaPickerScreen.swift | 60 ++-- submodules/SettingsUI/BUILD | 2 + .../Themes/CustomWallpaperPicker.swift | 129 ++++++++ .../Sources/Themes/ThemeGridController.swift | 137 ++++---- .../Themes/ThemeGridControllerNode.swift | 13 +- .../Themes/WallpaperGalleryController.swift | 33 +- .../Sources/Themes/WallpaperGalleryItem.swift | 6 +- .../Themes/WallpaperGalleryToolbarNode.swift | 5 +- .../Themes/WallpaperOptionButtonNode.swift | 2 +- submodules/TelegramApi/Sources/Api0.swift | 3 +- submodules/TelegramApi/Sources/Api12.swift | 22 ++ submodules/TelegramApi/Sources/Api22.swift | 20 +- submodules/TelegramApi/Sources/Api30.swift | 32 ++ .../ApiUtils/StoreMessage_Telegram.swift | 2 +- .../ApiUtils/TelegramMediaAction.swift | 2 + .../Sources/ApiUtils/Wallpaper.swift | 2 +- .../SyncCore/SyncCore_CachedUserData.swift | 57 ++-- .../SyncCore_TelegramMediaAction.swift | 10 + .../Peers/UpdateCachedPeerData.swift | 7 +- .../TelegramEngine/Themes/ChatThemes.swift | 45 ++- .../Themes/TelegramEngineThemes.swift | 10 +- .../Sources/ServiceMessageStrings.swift | 7 + .../TelegramUI/Sources/ChatController.swift | 60 +++- .../ChatInterfaceStateInputPanels.swift | 2 +- .../Sources/ChatMessageBubbleItemNode.swift | 2 + ...hatMessageWallpaperBubbleContentNode.swift | 312 ++++++++++++++++++ .../Sources/PeerInfo/PeerInfoScreen.swift | 4 +- .../Sources/WallpaperPreviewMedia.swift | 15 + 31 files changed, 863 insertions(+), 167 deletions(-) create mode 100644 submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index ced71d78bd..8c6e3076bc 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9118,3 +9118,7 @@ Sorry for the inconvenience."; "Premium.MaxSharedFolderLinksFinalText" = "Sorry, you can only create **%1$@** invite links"; "Premium.GiftedTitle.Someone" = "Someone"; + +"Notification.ChangedWallpaper" = "%1$@ set a new background for this chat"; +"Notification.YouChangedWallpaper" = "You set a new background for this chat"; +"Notification.Wallpaper.View" = "View Background"; diff --git a/submodules/GalleryUI/Sources/GalleryControllerNode.swift b/submodules/GalleryUI/Sources/GalleryControllerNode.swift index 20409a6720..e943566b24 100644 --- a/submodules/GalleryUI/Sources/GalleryControllerNode.swift +++ b/submodules/GalleryUI/Sources/GalleryControllerNode.swift @@ -87,6 +87,10 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture self.pager.dismiss = { [weak self] in if let strongSelf = self { + if let galleryController = strongSelf.galleryController(), galleryController.navigationController != nil { + galleryController.dismiss(animated: true) + return + } var interfaceAnimationCompleted = false var contentAnimationCompleted = true diff --git a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift index 7f98602068..dac85ccde4 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift @@ -25,14 +25,16 @@ final class MediaPickerGridItem: GridItem { let content: MediaPickerGridItemContent let interaction: MediaPickerInteraction let theme: PresentationTheme + let selectable: Bool let enableAnimations: Bool let section: GridSection? = nil - init(content: MediaPickerGridItemContent, interaction: MediaPickerInteraction, theme: PresentationTheme, enableAnimations: Bool) { + init(content: MediaPickerGridItemContent, interaction: MediaPickerInteraction, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool) { self.content = content self.interaction = interaction self.theme = theme + self.selectable = selectable self.enableAnimations = enableAnimations } @@ -40,11 +42,11 @@ final class MediaPickerGridItem: GridItem { switch self.content { case let .asset(fetchResult, index): let node = MediaPickerGridItemNode() - node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme, enableAnimations: self.enableAnimations) + node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations) return node case let .media(media, index): let node = MediaPickerGridItemNode() - node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, enableAnimations: self.enableAnimations) + node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations) return node } } @@ -56,13 +58,13 @@ final class MediaPickerGridItem: GridItem { assertionFailure() return } - node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme, enableAnimations: self.enableAnimations) + node.setup(interaction: self.interaction, fetchResult: fetchResult, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations) case let .media(media, index): guard let node = node as? MediaPickerGridItemNode else { assertionFailure() return } - node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, enableAnimations: self.enableAnimations) + node.setup(interaction: self.interaction, media: media, index: index, theme: self.theme, selectable: self.selectable, enableAnimations: self.enableAnimations) } } } @@ -84,6 +86,7 @@ final class MediaPickerGridItemNode: GridItemNode { var currentMediaState: (TGMediaSelectableItem, Int)? var currentState: (PHFetchResult, Int)? var enableAnimations: Bool = true + private var selectable: Bool = false private let imageNode: ImageNode private var checkNode: InteractiveCheckNode? @@ -171,7 +174,7 @@ final class MediaPickerGridItemNode: GridItemNode { } func updateSelectionState(animated: Bool = false) { - if self.checkNode == nil, let _ = self.interaction?.selectionState, let theme = self.theme { + if self.checkNode == nil, let _ = self.interaction?.selectionState, self.selectable, let theme = self.theme { let checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: theme, style: .overlay)) checkNode.valueChanged = { [weak self] value in if let strongSelf = self, let interaction = strongSelf.interaction, let selectableItem = strongSelf.selectableItem { @@ -223,9 +226,10 @@ final class MediaPickerGridItemNode: GridItemNode { self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:)))) } - func setup(interaction: MediaPickerInteraction, media: MediaPickerScreen.Subject.Media, index: Int, theme: PresentationTheme, enableAnimations: Bool) { + func setup(interaction: MediaPickerInteraction, media: MediaPickerScreen.Subject.Media, index: Int, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool) { self.interaction = interaction self.theme = theme + self.selectable = selectable self.enableAnimations = enableAnimations self.backgroundColor = theme.list.mediaPlaceholderColor @@ -239,9 +243,10 @@ final class MediaPickerGridItemNode: GridItemNode { self.updateHiddenMedia() } - func setup(interaction: MediaPickerInteraction, fetchResult: PHFetchResult, index: Int, theme: PresentationTheme, enableAnimations: Bool) { + func setup(interaction: MediaPickerInteraction, fetchResult: PHFetchResult, index: Int, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool) { self.interaction = interaction self.theme = theme + self.selectable = selectable self.enableAnimations = enableAnimations self.backgroundColor = theme.list.mediaPlaceholderColor diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 41b97f65b3..c18e9897a3 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -48,13 +48,14 @@ final class MediaPickerInteraction { private struct MediaPickerGridEntry: Comparable, Identifiable { let stableId: Int let content: MediaPickerGridItemContent + let selectable: Bool static func <(lhs: MediaPickerGridEntry, rhs: MediaPickerGridEntry) -> Bool { return lhs.stableId < rhs.stableId } func item(context: AccountContext, interaction: MediaPickerInteraction, theme: PresentationTheme) -> MediaPickerGridItem { - return MediaPickerGridItem(content: self.content, interaction: interaction, theme: theme, enableAnimations: context.sharedContext.energyUsageSettings.fullTranslucency) + return MediaPickerGridItem(content: self.content, interaction: interaction, theme: theme, selectable: self.selectable, enableAnimations: context.sharedContext.energyUsageSettings.fullTranslucency) } } @@ -127,7 +128,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } } - case assets(PHAssetCollection?) + case assets(PHAssetCollection?, Bool) case media([Media]) } @@ -157,6 +158,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var presentWebSearch: (MediaGroupsScreen, Bool) -> Void = { _, _ in } public var getCaptionPanelView: () -> TGCaptionPanelView? = { return nil } + public var customSelection: ((PHAsset) -> Void)? = nil + private var completed = false public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _ in } @@ -250,7 +253,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { super.init() - if case .assets(nil) = controller.subject { + if case .assets(nil, false) = controller.subject { } else { self.preloadPromise.set(false) } @@ -263,7 +266,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { let preloadPromise = self.preloadPromise let updatedState: Signal switch controller.subject { - case let .assets(collection): + case let .assets(collection, _): updatedState = combineLatest(mediaAssetsContext.mediaAccess(), mediaAssetsContext.cameraAccess()) |> mapToSignal { mediaAccess, cameraAccess -> Signal in if case .notDetermined = mediaAccess { @@ -400,7 +403,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.gridNode.view.addGestureRecognizer(selectionGesture) self.selectionGesture = selectionGesture - if let controller = self.controller, case let .assets(collection) = controller.subject, collection != nil { + if let controller = self.controller, case let .assets(collection, _) = controller.subject, collection != nil { self.gridNode.view.interactiveTransitionGestureRecognizerTest = { point -> Bool in return point.x > 44.0 } @@ -451,7 +454,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { } }) - if let controller = self.controller, case .assets(nil) = controller.subject { + if let controller = self.controller, case .assets(nil, false) = controller.subject { let enableAnimations = self.controller?.context.sharedContext.energyUsageSettings.fullTranslucency ?? true let cameraView = TGAttachmentCameraView(forSelfPortrait: false, videoModeByDefault: controller.bannedSendPhotos != nil && controller.bannedSendVideos == nil)! @@ -552,6 +555,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { var updateLayout = false + var selectable = true + if case let .assets(_, isStandalone) = controller.subject, isStandalone { + selectable = false + } + switch state { case let .noAccess(cameraAccess): if case .assets = previousState { @@ -570,12 +578,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { for i in 0 ..< count { let index: Int - if case let .assets(collection) = controller.subject, let _ = collection { + if case let .assets(collection, _) = controller.subject, let _ = collection { index = i } else { index = totalCount - i - 1 } - entries.append(MediaPickerGridEntry(stableId: stableId, content: .asset(fetchResult, index))) + entries.append(MediaPickerGridEntry(stableId: stableId, content: .asset(fetchResult, index), selectable: selectable)) stableId += 1 } @@ -594,7 +602,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { case let .media(media): let count = media.count for i in 0 ..< count { - entries.append(MediaPickerGridEntry(stableId: stableId, content: .media(media[i], i))) + entries.append(MediaPickerGridEntry(stableId: stableId, content: .media(media[i], i), selectable: true)) stableId += 1 } } @@ -603,7 +611,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.currentEntries = entries var scrollToItem: GridNodeScrollToItem? - if case let .assets(collection) = controller.subject, let _ = collection, previousEntries.isEmpty && !entries.isEmpty { + if case let .assets(collection, _) = controller.subject, let _ = collection, previousEntries.isEmpty && !entries.isEmpty { scrollToItem = GridNodeScrollToItem(index: entries.count - 1, position: .bottom(0.0), transition: .immediate, directionHint: .down, adjustForSection: false) } @@ -722,6 +730,19 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { Queue.mainQueue().justDispatch { self.dismissInput() } + + if let customSelection = controller.customSelection { + customSelection(fetchResult[index]) + return + } + + let reversed: Bool + if case .assets(nil, _) = controller.subject { + reversed = true + } else { + reversed = false + } + let index = reversed ? fetchResult.count - index - 1 : index var hasTimer = false if controller.chatLocation?.peerId != controller.context.account.peerId && controller.chatLocation?.peerId?.namespace == Namespaces.Peer.CloudUser { @@ -732,13 +753,6 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.openingMedia = true - let reversed: Bool - if case .assets(nil) = controller.subject { - reversed = true - } else { - reversed = false - } - let index = reversed ? fetchResult.count - index - 1 : index self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, threadTitle: controller.threadTitle, chatLocation: controller.chatLocation, presentationData: self.presentationData, source: .fetchResult(fetchResult: fetchResult, index: index, reversed: reversed), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: hasSchedule, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in self?.hiddenMediaId.set(.single(id)) }, initialLayout: layout, transitionHostView: { [weak self] in @@ -1233,7 +1247,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) - if case let .assets(collection) = subject, let collection = collection { + if case let .assets(collection, _) = subject, let collection = collection { self.titleView.title = collection.localizedTitle ?? presentationData.strings.Attachment_Gallery } else { self.titleView.title = presentationData.strings.Attachment_Gallery @@ -1302,8 +1316,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.navigationItem.titleView = self.titleView - if case let .assets(collection) = self.subject, collection != nil { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) + if case let .assets(collection, isStandalone) = self.subject { + if !isStandalone { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) + } else if collection != nil { + self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) + } } else { self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) @@ -1658,7 +1676,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.requestAttachmentMenuExpansion() self.presentWebSearch(MediaGroupsScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mediaAssetsContext: self.controllerNode.mediaAssetsContext, openGroup: { [weak self] collection in if let strongSelf = self { - let mediaPicker = MediaPickerScreen(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: strongSelf.peer, threadTitle: strongSelf.threadTitle, chatLocation: strongSelf.chatLocation, bannedSendPhotos: strongSelf.bannedSendPhotos, bannedSendVideos: strongSelf.bannedSendVideos, subject: .assets(collection), editingContext: strongSelf.interaction?.editingState, selectionContext: strongSelf.interaction?.selectionState) + let mediaPicker = MediaPickerScreen(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: strongSelf.peer, threadTitle: strongSelf.threadTitle, chatLocation: strongSelf.chatLocation, bannedSendPhotos: strongSelf.bannedSendPhotos, bannedSendVideos: strongSelf.bannedSendVideos, subject: .assets(collection, false), editingContext: strongSelf.interaction?.editingState, selectionContext: strongSelf.interaction?.selectionState) mediaPicker.presentSchedulePicker = strongSelf.presentSchedulePicker mediaPicker.presentTimerPicker = strongSelf.presentTimerPicker diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index f5a4524847..678a8fd26f 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -110,6 +110,8 @@ swift_library( "//submodules/AnimatedAvatarSetNode", "//submodules/TelegramUI/Components/StorageUsageScreen", "//submodules/FeaturedStickersScreen:FeaturedStickersScreen", + "//submodules/MediaPickerUI:MediaPickerUI", + "//submodules/ImageBlur:ImageBlur", ], visibility = [ "//visibility:public", diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index b5b24430b3..53fe225ab9 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -11,6 +11,7 @@ import AccountContext import LegacyUI import LegacyMediaPickerUI import LocalMediaResources +import ImageBlur func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } @@ -194,3 +195,131 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE return croppedImage }).start() } + +func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, peerId: PeerId, completion: @escaping () -> Void) { + let imageSignal: Signal + switch wallpaper { + case let .wallpaper(wallpaper, _): + switch wallpaper { + case let .file(file): + if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data) + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() + } + case let .image(representations, _): + for representation in representations { + let resource = representation.resource + if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() + } + } + default: + break + } + imageSignal = .complete() + completion() + case let .asset(asset): + imageSignal = fetchPhotoLibraryImage(localIdentifier: asset.localIdentifier, thumbnail: false) + |> filter { value in + return !(value?.1 ?? true) + } + |> mapToSignal { result -> Signal in + if let result = result { + return .single(result.0) + } else { + return .complete() + } + } + case let .contextResult(result): + var imageResource: TelegramMediaResource? + switch result { + case let .externalReference(externalReference): + if let content = externalReference.content { + imageResource = content.resource + } + case let .internalReference(internalReference): + if let image = internalReference.image { + if let imageRepresentation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 1000, height: 800)) { + imageResource = imageRepresentation.resource + } + } + } + + if let imageResource = imageResource { + imageSignal = .single(context.account.postbox.mediaBox.completedResourcePath(imageResource)) + |> mapToSignal { path -> Signal in + if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]), let image = UIImage(data: data) { + return .single(image) + } else { + return .complete() + } + } + } else { + imageSignal = .complete() + } + } + + let _ = (imageSignal + |> map { image -> UIImage in + var croppedImage = UIImage() + + let finalCropRect: CGRect + if let cropRect = cropRect { + finalCropRect = cropRect + } else { + let screenSize = TGScreenSize() + let fittedSize = TGScaleToFit(screenSize, image.size) + finalCropRect = CGRect(x: (image.size.width - fittedSize.width) / 2.0, y: (image.size.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height) + } + croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true) + + if mode.contains(.blur) { + croppedImage = blurredImage(croppedImage, radius: 45.0)! + } + + let thumbnailDimensions = finalCropRect.size.fitted(CGSize(width: 320.0, height: 320.0)) + let thumbnailImage = generateScaledImage(image: croppedImage, size: thumbnailDimensions, scale: 1.0) + + if let data = croppedImage.jpegData(compressionQuality: 0.8), let thumbnailImage = thumbnailImage, let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) { + let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) + context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData) + + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) + + let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: nil) + let temporaryWallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), TelegramMediaImageRepresentation(dimensions: PixelDimensions(croppedImage.size), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], settings) + + let _ = context.account.postbox.transaction({ transaction in + transaction.updatePeerCachedData(peerIds: Set([peerId])) { _, cachedData in + if let cachedData = cachedData as? CachedUserData { + return cachedData.withUpdatedWallpaper(temporaryWallpaper) + } else { + return cachedData + } + } + }).start() + + Queue.mainQueue().async { + completion() + } + + let _ = uploadWallpaper(account: context.account, resource: resource, settings: WallpaperSettings(blur: false, motion: mode.contains(.motion), colors: [], intensity: nil)).start(next: { status in + if case let .complete(wallpaper) = status { + if case let .file(file) = wallpaper { + context.account.postbox.mediaBox.copyResourceData(from: resource.id, to: file.file.resource.id, synchronous: true) + for representation in file.file.previewRepresentations { + context.account.postbox.mediaBox.copyResourceData(from: resource.id, to: representation.resource.id, synchronous: true) + } + } + let _ = context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: wallpaper).start() + } + }) + } + return croppedImage + }).start() +} diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift index 8ad2740952..c297370792 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift @@ -14,8 +14,23 @@ import ShareController import SearchUI import HexColor import PresentationDataUtils +import MediaPickerUI public final class ThemeGridController: ViewController { + public enum Mode: Equatable { + case `default` + case peer(EnginePeer.Id, TelegramWallpaper?) + + var galleryMode: WallpaperGalleryController.Mode { + switch self { + case .default: + return .default + case let .peer(peerId, _): + return .peer(peerId) + } + } + } + private var controllerNode: ThemeGridControllerNode { return self.displayNode as! ThemeGridControllerNode } @@ -26,6 +41,7 @@ public final class ThemeGridController: ViewController { } private let context: AccountContext + private let mode: Mode private var presentationData: PresentationData private let presentationDataPromise = Promise() @@ -44,8 +60,9 @@ public final class ThemeGridController: ViewController { private var previousContentOffset: GridNodeVisibleContentOffset? - public init(context: AccountContext) { + public init(context: AccountContext, mode: Mode = .default) { self.context = context + self.mode = mode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationDataPromise.set(.single(self.presentationData)) @@ -116,83 +133,81 @@ public final class ThemeGridController: ViewController { } public override func loadDisplayNode() { - self.displayNode = ThemeGridControllerNode(context: self.context, presentationData: self.presentationData, presentPreviewController: { [weak self] source in + let dismissControllers = { [weak self] in + if let self, let navigationController = self.navigationController as? NavigationController { + let controllers = navigationController.viewControllers.filter({ controller in + if controller is ThemeGridController || controller is WallpaperGalleryController || controller is MediaPickerScreen { + return false + } + return true + }) + navigationController.setViewControllers(controllers, animated: true) + } + } + self.displayNode = ThemeGridControllerNode(context: self.context, presentationData: self.presentationData, mode: self.mode, presentPreviewController: { [weak self] source in if let strongSelf = self { - let controller = WallpaperGalleryController(context: strongSelf.context, source: source) - controller.apply = { [weak self, weak controller] wallpaper, mode, cropRect in - if let strongSelf = self { - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: mode, cropRect: cropRect, completion: { [weak self, weak controller] in - if let strongSelf = self { - strongSelf.deactivateSearch(animated: false) - strongSelf.controllerNode.scrollToTop(animated: false) - } - if let controller = controller { - switch wallpaper { + let controller = WallpaperGalleryController(context: strongSelf.context, source: source, mode: strongSelf.mode.galleryMode) + controller.apply = { [weak self, weak controller] wallpaper, options, cropRect in + if let strongSelf = self, case let .wallpaper(wallpaperValue, _) = wallpaper { + switch strongSelf.mode { + case .default: + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak self, weak controller] in + if let strongSelf = self { + strongSelf.deactivateSearch(animated: false) + strongSelf.controllerNode.scrollToTop(animated: false) + } + if let controller = controller { + switch wallpaper { case .asset, .contextResult: - controller.dismiss(forceAway: true) + controller.dismiss(animated: true) default: break + } } - } - }) + }) + case let .peer(peerId, _): + let _ = (strongSelf.context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: wallpaperValue) + |> deliverOnMainQueue).start(completed: { + dismissControllers() + }) + } } } self?.push(controller) } }, presentGallery: { [weak self] in if let strongSelf = self { - presentCustomWallpaperPicker(context: strongSelf.context, present: { [weak self] controller in - self?.present(controller, in: .window(.root), with: nil, blockInteraction: true) - }, push: { [weak self] controller in - self?.push(controller) - }) + let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, true)) + controller.customSelection = { [weak self] asset in + guard let strongSelf = self else { + return + } + let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: strongSelf.mode.galleryMode) + controller.apply = { [weak self, weak controller] wallpaper, options, cropRect in + if let strongSelf = self, let controller = controller { + switch strongSelf.mode { + case .default: + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak controller] in + if let controller = controller { + controller.dismiss(forceAway: true) + } + }) + case let .peer(peerId, _): + uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, peerId: peerId, completion: { + dismissControllers() + }) + } + } + } + strongSelf.push(controller) + } + self?.push(controller) } }, presentColors: { [weak self] in if let strongSelf = self { let controller = ThemeColorsGridController(context: strongSelf.context) (strongSelf.navigationController as? NavigationController)?.pushViewController(controller) } - - /*if let strongSelf = self { - let _ = (strongSelf.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) - |> take(1) - |> deliverOnMainQueue).start(next: { [weak self] sharedData in - guard let strongSelf = self else { - return - } - let settings = (sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings] as? PresentationThemeSettings) ?? PresentationThemeSettings.defaultSettings - - let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered - let themeReference: PresentationThemeReference - if autoNightModeTriggered { - themeReference = settings.automaticThemeSwitchSetting.theme - } else { - themeReference = settings.theme - } - - let controller = ThemeAccentColorController(context: strongSelf.context, mode: .background(themeReference: themeReference)) - controller.completion = { [weak self] in - if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { - var controllers = navigationController.viewControllers - controllers = controllers.filter { controller in - if controller is ThemeColorsGridController { - return false - } - return true - } - navigationController.setViewControllers(controllers, animated: false) - controllers = controllers.filter { controller in - if controller is ThemeAccentColorController { - return false - } - return true - } - navigationController.setViewControllers(controllers, animated: true) - } - } - strongSelf.push(controller) - }) - }*/ }, emptyStateUpdated: { [weak self] empty in if let strongSelf = self { if empty != strongSelf.isEmpty { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift index 279e96d0f5..46fe2a32ea 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift @@ -141,6 +141,7 @@ final class ThemeGridControllerNode: ASDisplayNode { private let context: AccountContext private var presentationData: PresentationData + private let mode: ThemeGridController.Mode private var controllerInteraction: ThemeGridControllerInteraction? private let presentPreviewController: (WallpaperListSource) -> Void @@ -192,8 +193,9 @@ final class ThemeGridControllerNode: ASDisplayNode { private var disposable: Disposable? - init(context: AccountContext, presentationData: PresentationData, presentPreviewController: @escaping (WallpaperListSource) -> Void, presentGallery: @escaping () -> Void, presentColors: @escaping () -> Void, emptyStateUpdated: @escaping (Bool) -> Void, deleteWallpapers: @escaping ([TelegramWallpaper], @escaping () -> Void) -> Void, shareWallpapers: @escaping ([TelegramWallpaper]) -> Void, resetWallpapers: @escaping () -> Void, popViewController: @escaping () -> Void) { + init(context: AccountContext, presentationData: PresentationData, mode: ThemeGridController.Mode, presentPreviewController: @escaping (WallpaperListSource) -> Void, presentGallery: @escaping () -> Void, presentColors: @escaping () -> Void, emptyStateUpdated: @escaping (Bool) -> Void, deleteWallpapers: @escaping ([TelegramWallpaper], @escaping () -> Void) -> Void, shareWallpapers: @escaping ([TelegramWallpaper]) -> Void, resetWallpapers: @escaping () -> Void, popViewController: @escaping () -> Void) { self.context = context + self.mode = mode self.presentationData = presentationData self.presentPreviewController = presentPreviewController self.presentGallery = presentGallery @@ -338,12 +340,19 @@ final class ThemeGridControllerNode: ASDisplayNode { }) self.controllerInteraction = interaction + var selectFirst = true + if case .peer = self.mode { + selectFirst = false + } + let transition = combineLatest(self.wallpapersPromise.get(), deletedWallpaperIdsPromise.get(), context.sharedContext.presentationData) |> map { wallpapers, deletedWallpaperIds, presentationData -> (ThemeGridEntryTransition, Bool) in var entries: [ThemeGridControllerEntry] = [] var index = 1 - entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, isEditable: false, isSelected: true), at: 0) + if selectFirst { + entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, isEditable: false, isSelected: true), at: 0) + } var defaultWallpaper: TelegramWallpaper? if !presentationData.chatWallpaper.isBasicallyEqual(to: presentationData.theme.chat.defaultWallpaper) { diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift index e966bba179..7f19484096 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift @@ -165,12 +165,17 @@ private func updatedFileWallpaper(id: Int64? = nil, accessHash: Int64? = nil, sl } public class WallpaperGalleryController: ViewController { + public enum Mode { + case `default` + case peer(EnginePeer.Id) + } private var galleryNode: GalleryControllerNode { return self.displayNode as! GalleryControllerNode } private let context: AccountContext private let source: WallpaperListSource + private let mode: Mode public var apply: ((WallpaperGalleryEntry, WallpaperPresentationOptions, CGRect?) -> Void)? private let _ready = Promise() @@ -211,9 +216,10 @@ public class WallpaperGalleryController: ViewController { private var savedPatternWallpaper: TelegramWallpaper? private var savedPatternIntensity: Int32? - public init(context: AccountContext, source: WallpaperListSource) { + public init(context: AccountContext, source: WallpaperListSource, mode: Mode = .default) { self.context = context self.source = source + self.mode = mode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } super.init(navigationBarPresentationData: nil) @@ -378,6 +384,9 @@ public class WallpaperGalleryController: ViewController { (self.displayNode as? WallpaperGalleryControllerNode)?.nativeStatusBar = self.statusBar + self.galleryNode.galleryController = { [weak self] in + return self + } self.galleryNode.navigationBar = self.navigationBar self.galleryNode.dismiss = { [weak self] in self?.presentingViewController?.dismiss(animated: false, completion: nil) @@ -423,6 +432,9 @@ public class WallpaperGalleryController: ViewController { default: break } + if case .peer = self.mode { + doneButtonType = .setPeer + } let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings, doneButtonType: doneButtonType) self.toolbarNode = toolbarNode @@ -439,6 +451,13 @@ public class WallpaperGalleryController: ViewController { let options = centralItemNode.options if !strongSelf.entries.isEmpty { let entry = strongSelf.entries[centralItemNode.index] + + if case .peer = strongSelf.mode { + strongSelf.apply?(entry, options, centralItemNode.cropRect) + return + } + + switch entry { case let .wallpaper(wallpaper, _): var resource: MediaResource? @@ -1044,15 +1063,3 @@ public class WallpaperGalleryController: ViewController { } } } - -private extension GalleryControllerNode { - func modalAnimateIn(completion: (() -> Void)? = nil) { - self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) - } - - func modalAnimateOut(completion: (() -> Void)? = nil) { - self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in - completion?() - }) - } -} diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 7a5ce2bd9e..f40e7d5747 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -658,9 +658,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { strongSelf.colorsButtonNode.buttonColor = color if color == UIColor(rgb: 0x000000, alpha: 0.3) { - strongSelf.playButtonBackgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.45), transition: .immediate) - strongSelf.cancelButtonBackgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.45), transition: .immediate) - strongSelf.shareButtonBackgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.45), transition: .immediate) + strongSelf.playButtonBackgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.55), transition: .immediate) + strongSelf.cancelButtonBackgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.55), transition: .immediate) + strongSelf.shareButtonBackgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.55), transition: .immediate) } else { strongSelf.playButtonBackgroundNode.updateColor(color: color, transition: .immediate) strongSelf.cancelButtonBackgroundNode.updateColor(color: color, transition: .immediate) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift index 8bc23fb21c..887b400e5f 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift @@ -11,6 +11,7 @@ enum WallpaperGalleryToolbarCancelButtonType { enum WallpaperGalleryToolbarDoneButtonType { case set + case setPeer case proceed case apply case none @@ -48,7 +49,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { self.cancelButtonType = cancelButtonType self.doneButtonType = doneButtonType - self.doneButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0xf2f2f2, alpha: 0.45)) + self.doneButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0xf2f2f2, alpha: 0.55)) self.doneButtonBackgroundNode.cornerRadius = 14.0 let blurEffect: UIBlurEffect @@ -152,6 +153,8 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode { switch self.doneButtonType { case .set: doneTitle = strings.Wallpaper_ApplyForAll + case .setPeer: + doneTitle = strings.Wallpaper_ApplyForChat case .proceed: doneTitle = strings.Theme_Colors_Proceed case .apply: diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index 7e193bb9f4..f7a458ec55 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -141,7 +141,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode { var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { didSet { if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) { - self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.45), transition: .immediate) + self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.55), transition: .immediate) } else { self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate) } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 056930ae34..0fcf2120c3 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -476,6 +476,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-648257196] = { return Api.MessageAction.parse_messageActionSecureValuesSent($0) } dict[455635795] = { return Api.MessageAction.parse_messageActionSecureValuesSentMe($0) } dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) } + dict[-1136350937] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) } dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } @@ -913,7 +914,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) } dict[-1885878744] = { return Api.User.parse_user($0) } dict[-742634630] = { return Api.User.parse_userEmpty($0) } - dict[-120378643] = { return Api.UserFull.parse_userFull($0) } + dict[-1813324973] = { return Api.UserFull.parse_userFull($0) } dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) } diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift index 52955dbb5a..f4db8076a9 100644 --- a/submodules/TelegramApi/Sources/Api12.swift +++ b/submodules/TelegramApi/Sources/Api12.swift @@ -261,6 +261,7 @@ public extension Api { case messageActionSecureValuesSent(types: [Api.SecureValueType]) case messageActionSecureValuesSentMe(values: [Api.SecureValue], credentials: Api.SecureCredentialsEncrypted) case messageActionSetChatTheme(emoticon: String) + case messageActionSetChatWallPaper(wallpaper: Api.WallPaper) case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?) case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) @@ -506,6 +507,12 @@ public extension Api { } serializeString(emoticon, buffer: buffer, boxed: false) break + case .messageActionSetChatWallPaper(let wallpaper): + if boxed { + buffer.appendInt32(-1136350937) + } + wallpaper.serialize(buffer, true) + break case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): if boxed { buffer.appendInt32(1007897979) @@ -619,6 +626,8 @@ public extension Api { return ("messageActionSecureValuesSentMe", [("values", values as Any), ("credentials", credentials as Any)]) case .messageActionSetChatTheme(let emoticon): return ("messageActionSetChatTheme", [("emoticon", emoticon as Any)]) + case .messageActionSetChatWallPaper(let wallpaper): + return ("messageActionSetChatWallPaper", [("wallpaper", wallpaper as Any)]) case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom): return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)]) case .messageActionSuggestProfilePhoto(let photo): @@ -1043,6 +1052,19 @@ public extension Api { return nil } } + public static func parse_messageActionSetChatWallPaper(_ reader: BufferReader) -> MessageAction? { + var _1: Api.WallPaper? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.WallPaper + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSetChatWallPaper(wallpaper: _1!) + } + else { + return nil + } + } public static func parse_messageActionSetMessagesTTL(_ reader: BufferReader) -> MessageAction? { var _1: Int32? _1 = reader.readInt32() diff --git a/submodules/TelegramApi/Sources/Api22.swift b/submodules/TelegramApi/Sources/Api22.swift index b7d59d97dc..321570b40f 100644 --- a/submodules/TelegramApi/Sources/Api22.swift +++ b/submodules/TelegramApi/Sources/Api22.swift @@ -586,13 +586,13 @@ public extension Api { } public extension Api { enum UserFull: TypeConstructorDescription { - case userFull(flags: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?) + case userFull(flags: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?, wallpaper: Api.WallPaper?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .userFull(let flags, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts): + case .userFull(let flags, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper): if boxed { - buffer.appendInt32(-120378643) + buffer.appendInt32(-1813324973) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -616,14 +616,15 @@ public extension Api { for item in premiumGifts! { item.serialize(buffer, true) }} + if Int(flags) & Int(1 << 24) != 0 {wallpaper!.serialize(buffer, true)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .userFull(let flags, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts): - return ("userFull", [("flags", flags as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("premiumGifts", premiumGifts as Any)]) + case .userFull(let flags, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts, let wallpaper): + return ("userFull", [("flags", flags as Any), ("id", id as Any), ("about", about as Any), ("settings", settings as Any), ("personalPhoto", personalPhoto as Any), ("profilePhoto", profilePhoto as Any), ("fallbackPhoto", fallbackPhoto as Any), ("notifySettings", notifySettings as Any), ("botInfo", botInfo as Any), ("pinnedMsgId", pinnedMsgId as Any), ("commonChatsCount", commonChatsCount as Any), ("folderId", folderId as Any), ("ttlPeriod", ttlPeriod as Any), ("themeEmoticon", themeEmoticon as Any), ("privateForwardName", privateForwardName as Any), ("botGroupAdminRights", botGroupAdminRights as Any), ("botBroadcastAdminRights", botBroadcastAdminRights as Any), ("premiumGifts", premiumGifts as Any), ("wallpaper", wallpaper as Any)]) } } @@ -682,6 +683,10 @@ public extension Api { if Int(_1!) & Int(1 << 19) != 0 {if let _ = reader.readInt32() { _18 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumGiftOption.self) } } + var _19: Api.WallPaper? + if Int(_1!) & Int(1 << 24) != 0 {if let signature = reader.readInt32() { + _19 = Api.parse(reader, signature: signature) as? Api.WallPaper + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil @@ -700,8 +705,9 @@ public extension Api { let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil let _c17 = (Int(_1!) & Int(1 << 18) == 0) || _17 != nil let _c18 = (Int(_1!) & Int(1 << 19) == 0) || _18 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 { - return Api.UserFull.userFull(flags: _1!, id: _2!, about: _3, settings: _4!, personalPhoto: _5, profilePhoto: _6, fallbackPhoto: _7, notifySettings: _8!, botInfo: _9, pinnedMsgId: _10, commonChatsCount: _11!, folderId: _12, ttlPeriod: _13, themeEmoticon: _14, privateForwardName: _15, botGroupAdminRights: _16, botBroadcastAdminRights: _17, premiumGifts: _18) + let _c19 = (Int(_1!) & Int(1 << 24) == 0) || _19 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 { + return Api.UserFull.userFull(flags: _1!, id: _2!, about: _3, settings: _4!, personalPhoto: _5, profilePhoto: _6, fallbackPhoto: _7, notifySettings: _8!, botInfo: _9, pinnedMsgId: _10, commonChatsCount: _11!, folderId: _12, ttlPeriod: _13, themeEmoticon: _14, privateForwardName: _15, botGroupAdminRights: _16, botBroadcastAdminRights: _17, premiumGifts: _18, wallpaper: _19) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift index 89d78efab9..55bf9b4f03 100644 --- a/submodules/TelegramApi/Sources/Api30.swift +++ b/submodules/TelegramApi/Sources/Api30.swift @@ -3057,6 +3057,21 @@ public extension Api.functions.communities { }) } } +public extension Api.functions.communities { + static func getLeaveCommunitySuggestions(community: Api.InputCommunity) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.Peer]>) { + let buffer = Buffer() + buffer.appendInt32(-296838430) + community.serialize(buffer, true) + return (FunctionDescription(name: "communities.getLeaveCommunitySuggestions", parameters: [("community", String(describing: community))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.Peer]? in + let reader = BufferReader(buffer) + var result: [Api.Peer]? + if let _ = reader.readInt32() { + result = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self) + } + return result + }) + } +} public extension Api.functions.communities { static func hideCommunityUpdates(community: Api.InputCommunity) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -6865,6 +6880,23 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func setChatWallPaper(peer: Api.InputPeer, wallpaper: Api.InputWallPaper, settings: Api.WallPaperSettings) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1109557590) + peer.serialize(buffer, true) + wallpaper.serialize(buffer, true) + settings.serialize(buffer, true) + return (FunctionDescription(name: "messages.setChatWallPaper", parameters: [("peer", String(describing: peer)), ("wallpaper", String(describing: wallpaper)), ("settings", String(describing: settings))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } +} public extension Api.functions.messages { static func setDefaultHistoryTTL(period: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index cbea11f5a5..3897b88063 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -205,7 +205,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } switch action { - case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 7cd909d651..bf73937574 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -108,6 +108,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .suggestedProfilePhoto(image: telegramMediaImageFromApiPhoto(photo))) case let .messageActionRequestedPeer(buttonId, peer): return TelegramMediaAction(action: .requestedPeer(buttonId: buttonId, peerId: peer.peerId)) + case let .messageActionSetChatWallPaper(wallpaper): + return TelegramMediaAction(action: .setChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper))) } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift b/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift index 91badb59b0..09f80f1882 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift @@ -95,7 +95,7 @@ extension TelegramWallpaper { } } - var apiInputWallpaperAndSettings: (Api.InputWallPaper?, Api.WallPaperSettings)? { + var apiInputWallpaperAndSettings: (Api.InputWallPaper, Api.WallPaperSettings)? { switch self { case .builtin: return nil diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index 2dc5cc3813..d52b385713 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -199,6 +199,7 @@ public final class CachedUserData: CachedPeerData { public let fallbackPhoto: CachedPeerProfilePhoto public let premiumGiftOptions: [CachedPremiumGiftOption] public let voiceMessagesAvailable: Bool + public let wallpaper: TelegramWallpaper? public let flags: CachedUserFlags public let peerIds: Set @@ -224,12 +225,13 @@ public final class CachedUserData: CachedPeerData { self.fallbackPhoto = .unknown self.premiumGiftOptions = [] self.voiceMessagesAvailable = true + self.wallpaper = nil self.flags = CachedUserFlags() self.peerIds = Set() self.messageIds = Set() } - public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: CachedPeerProfilePhoto, personalPhoto: CachedPeerProfilePhoto, fallbackPhoto: CachedPeerProfilePhoto, premiumGiftOptions: [CachedPremiumGiftOption], voiceMessagesAvailable: Bool, flags: CachedUserFlags) { + public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: CachedPeerProfilePhoto, personalPhoto: CachedPeerProfilePhoto, fallbackPhoto: CachedPeerProfilePhoto, premiumGiftOptions: [CachedPremiumGiftOption], voiceMessagesAvailable: Bool, wallpaper: TelegramWallpaper?, flags: CachedUserFlags) { self.about = about self.botInfo = botInfo self.peerStatusSettings = peerStatusSettings @@ -248,6 +250,7 @@ public final class CachedUserData: CachedPeerData { self.fallbackPhoto = fallbackPhoto self.premiumGiftOptions = premiumGiftOptions self.voiceMessagesAvailable = voiceMessagesAvailable + self.wallpaper = wallpaper self.flags = flags self.peerIds = Set() @@ -290,6 +293,7 @@ public final class CachedUserData: CachedPeerData { self.premiumGiftOptions = decoder.decodeObjectArrayWithDecoderForKey("pgo") as [CachedPremiumGiftOption] self.voiceMessagesAvailable = decoder.decodeInt32ForKey("vma", orElse: 0) != 0 + self.wallpaper = decoder.decode(TelegramWallpaperNativeCodable.self, forKey: "wp")?.value self.flags = CachedUserFlags(rawValue: decoder.decodeInt32ForKey("fl", orElse: 0)) self.peerIds = Set() @@ -346,6 +350,13 @@ public final class CachedUserData: CachedPeerData { encoder.encodeObjectArray(self.premiumGiftOptions, forKey: "pgo") encoder.encodeInt32(self.voiceMessagesAvailable ? 1 : 0, forKey: "vma") + + if let wallpaper = self.wallpaper { + encoder.encode(TelegramWallpaperNativeCodable(wallpaper), forKey: "wp") + } else { + encoder.encodeNil(forKey: "wp") + } + encoder.encodeInt32(self.flags.rawValue, forKey: "fl") } @@ -361,82 +372,86 @@ public final class CachedUserData: CachedPeerData { return false } - return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.themeEmoticon == other.themeEmoticon && self.photo == other.photo && self.personalPhoto == other.personalPhoto && self.fallbackPhoto == other.fallbackPhoto && self.premiumGiftOptions == other.premiumGiftOptions && self.voiceMessagesAvailable == other.voiceMessagesAvailable && self.flags == other.flags + return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.themeEmoticon == other.themeEmoticon && self.photo == other.photo && self.personalPhoto == other.personalPhoto && self.fallbackPhoto == other.fallbackPhoto && self.premiumGiftOptions == other.premiumGiftOptions && self.voiceMessagesAvailable == other.voiceMessagesAvailable && self.flags == other.flags && self.wallpaper == other.wallpaper } public func withUpdatedAbout(_ about: String?) -> CachedUserData { - return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedPhoto(_ photo: CachedPeerProfilePhoto) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedPersonalPhoto(_ personalPhoto: CachedPeerProfilePhoto) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedFallbackPhoto(_ fallbackPhoto: CachedPeerProfilePhoto) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedPremiumGiftOptions(_ premiumGiftOptions: [CachedPremiumGiftOption]) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) } public func withUpdatedVoiceMessagesAvailable(_ voiceMessagesAvailable: Bool) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: voiceMessagesAvailable, flags: self.flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: voiceMessagesAvailable, wallpaper: self.wallpaper, flags: self.flags) + } + + public func withUpdatedWallpaper(_ wallpaper: TelegramWallpaper?) -> CachedUserData { + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: wallpaper, flags: self.flags) } public func withUpdatedFlags(_ flags: CachedUserFlags) -> CachedUserData { - return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, flags: flags) + return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable, wallpaper: self.wallpaper, flags: flags) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index b62144a7d2..2a3205fb47 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -101,6 +101,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case suggestedProfilePhoto(image: TelegramMediaImage?) case attachMenuBotAllowed case requestedPeer(buttonId: Int32, peerId: PeerId) + case setChatWallpaper(wallpaper: TelegramWallpaper) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -181,6 +182,12 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .attachMenuBotAllowed case 32: self = .requestedPeer(buttonId: decoder.decodeInt32ForKey("b", orElse: 0), peerId: PeerId(decoder.decodeInt64ForKey("pi", orElse: 0))) + case 33: + if let wallpaper = decoder.decode(TelegramWallpaperNativeCodable.self, forKey: "wallpaper")?.value { + self = .setChatWallpaper(wallpaper: wallpaper) + } else { + self = .unknown + } default: self = .unknown } @@ -342,6 +349,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeInt32(32, forKey: "_rawValue") encoder.encodeInt32(buttonId, forKey: "b") encoder.encodeInt64(peerId.toInt64(), forKey: "pi") + case let .setChatWallpaper(wallpaper): + encoder.encodeInt32(33, forKey: "_rawValue") + encoder.encode(TelegramWallpaperNativeCodable(wallpaper), forKey: "wallpaper") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 6b49891b55..a3b5f457b6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -237,7 +237,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee } switch fullUser { - case let .userFull(_, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _): + case let .userFull(_, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _, _): updatePeers(transaction: transaction, peers: peers, update: { previous, updated -> Peer in if previous?.id == accountPeerId, let accountUser = accountUser, let user = TelegramUser.merge(previous as? TelegramUser, rhs: accountUser) { return user @@ -255,7 +255,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee previous = CachedUserData() } switch fullUser { - case let .userFull(userFullFlags, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, userPremiumGiftOptions): + case let .userFull(userFullFlags, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, userPremiumGiftOptions, userWallpaper): let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:)) let isBlocked = (userFullFlags & (1 << 0)) != 0 let voiceCallsAvailable = (userFullFlags & (1 << 4)) != 0 @@ -290,6 +290,8 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee premiumGiftOptions = [] } + let wallpaper = userWallpaper.flatMap { TelegramWallpaper(apiWallpaper: $0) } + return previous.withUpdatedAbout(userFullAbout) .withUpdatedBotInfo(botInfo) .withUpdatedCommonGroupCount(userFullCommonChatsCount) @@ -308,6 +310,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee .withUpdatedFallbackPhoto(.known(fallbackPhoto)) .withUpdatedPremiumGiftOptions(premiumGiftOptions) .withUpdatedVoiceMessagesAvailable(voiceMessagesAvailable) + .withUpdatedWallpaper(wallpaper) } }) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index 15d1507a65..25ecba8171 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -78,14 +78,14 @@ func _internal_getChatThemes(accountManager: AccountManager Signal { - return postbox.loadedPeerWithId(peerId) +func _internal_setChatTheme(account: Account, peerId: PeerId, emoticon: String?) -> Signal { + return account.postbox.loadedPeerWithId(peerId) |> mapToSignal { peer in guard let inputPeer = apiInputPeer(peer) else { return .complete() } - return postbox.transaction { transaction -> Signal in + return account.postbox.transaction { transaction -> Signal in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in if let current = current as? CachedUserData { return current.withUpdatedThemeEmoticon(emoticon) @@ -98,12 +98,12 @@ func _internal_setChatTheme(postbox: Postbox, network: Network, stateManager: Ac } }) - return network.request(Api.functions.messages.setChatTheme(peer: inputPeer, emoticon: emoticon ?? "")) + return account.network.request(Api.functions.messages.setChatTheme(peer: inputPeer, emoticon: emoticon ?? "")) |> `catch` { error in return .complete() } |> mapToSignal { updates -> Signal in - stateManager.addUpdates(updates) + account.stateManager.addUpdates(updates) return .complete() } } |> switchToLatest @@ -117,3 +117,38 @@ func managedChatThemesUpdates(accountManager: AccountManager then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart } + +func _internal_setChatWallpaper(account: Account, peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal { + return account.postbox.loadedPeerWithId(peerId) + |> mapToSignal { peer in + guard let inputPeer = apiInputPeer(peer) else { + return .complete() + } + + return account.postbox.transaction { transaction -> Signal in + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in + if let current = current as? CachedUserData { + return current.withUpdatedWallpaper(wallpaper) + } else { + return current + } + }) + + var inputWallpaper: Api.InputWallPaper? + var inputSettings: Api.WallPaperSettings? + if let inputWallpaperAndInputSettings = wallpaper?.apiInputWallpaperAndSettings { + inputWallpaper = inputWallpaperAndInputSettings.0 + inputSettings = inputWallpaperAndInputSettings.1 + } + + return account.network.request(Api.functions.messages.setChatWallPaper(peer: inputPeer, wallpaper: inputWallpaper ?? .inputWallPaperNoFile(id: 0), settings: inputSettings ?? apiWallpaperSettings(WallpaperSettings()))) + |> `catch` { error in + return .complete() + } + |> mapToSignal { updates -> Signal in + account.stateManager.addUpdates(updates) + return .complete() + } + } |> switchToLatest + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift index ca6283d39a..96db92da0e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift @@ -9,16 +9,16 @@ public extension TelegramEngine { self.account = account } -// public func getThemes(accountManager: AccountManager) -> Signal<[TelegramTheme], NoError> { -// return _internal_getThemes(accountManager: accountManager, postbox: self.account.postbox, network: self.account.network) -// } - public func getChatThemes(accountManager: AccountManager, forceUpdate: Bool = false, onlyCached: Bool = false) -> Signal<[TelegramTheme], NoError> { return _internal_getChatThemes(accountManager: accountManager, network: self.account.network, forceUpdate: forceUpdate, onlyCached: onlyCached) } public func setChatTheme(peerId: PeerId, emoticon: String?) -> Signal { - return _internal_setChatTheme(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, emoticon: emoticon) + return _internal_setChatTheme(account: self.account, peerId: peerId, emoticon: emoticon) + } + + public func setChatWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal { + return _internal_setChatWallpaper(account: self.account, peerId: peerId, wallpaper: wallpaper) } } } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 78e54ac440..42260d5ff4 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -873,6 +873,13 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, let botName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? "" let peerName = message.peers[peerId].flatMap(EnginePeer.init)?.displayTitle(strings: strings, displayOrder: nameDisplayOrder) ?? "" attributedString = addAttributesToStringWithRanges(strings.Notification_RequestedPeer(peerName, botName)._tuple, body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, peerId), (1, message.id.peerId)])) + case .setChatWallpaper: + if message.author?.id == accountPeerId { + attributedString = NSAttributedString(string: strings.Notification_YouChangedWallpaper, font: titleFont, textColor: primaryTextColor) + } else { + let resultTitleString = strings.Notification_ChangedWallpaper(authorName) + attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) + } case .unknown: attributedString = nil } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c64bdf6ed3..b15c8e39db 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -262,7 +262,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return self.presentationInterfaceState.interfaceState.selectionState?.selectedIds } - private var themeEmoticonPromise = Promise() + private let chatThemeEmoticonPromise = Promise() + private let chatWallpaperPromise = Promise() private var chatTitleView: ChatTitleView? private var leftNavigationButton: ChatNavigationButton? @@ -837,7 +838,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .setChatTheme: strongSelf.presentThemeSelection() return true + case let .setChatWallpaper(wallpaper): + strongSelf.chatDisplayNode.dismissInput() + let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil)) + strongSelf.push(wallpaperPreviewController) + return true case let .giftPremium(_, _, duration, _, _): + strongSelf.chatDisplayNode.dismissInput() let fromPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? strongSelf.context.account.peerId : message.id.peerId let toPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? message.id.peerId : strongSelf.context.account.peerId let controller = PremiumIntroScreen(context: strongSelf.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration)) @@ -5785,7 +5792,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }) } - let themeEmoticon: Signal = self.themeEmoticonPromise.get() + let themeEmoticon: Signal = self.chatThemeEmoticonPromise.get() + |> distinctUntilChanged + + let chatWallpaper: Signal = self.chatWallpaperPromise.get() |> distinctUntilChanged let themeSettings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings]) @@ -5801,7 +5811,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let accountManager = context.sharedContext.accountManager let currentThemeEmoticon = Atomic<(String?, Bool)?>(value: nil) - self.presentationDataDisposable = combineLatest(queue: Queue.mainQueue(), context.sharedContext.presentationData, themeSettings, context.engine.themes.getChatThemes(accountManager: accountManager, onlyCached: true), themeEmoticon, self.themeEmoticonAndDarkAppearancePreviewPromise.get()).start(next: { [weak self] presentationData, themeSettings, chatThemes, themeEmoticon, themeEmoticonAndDarkAppearance in + self.presentationDataDisposable = combineLatest( + queue: Queue.mainQueue(), + context.sharedContext.presentationData, + themeSettings, + context.engine.themes.getChatThemes(accountManager: accountManager, onlyCached: true), + themeEmoticon, + self.themeEmoticonAndDarkAppearancePreviewPromise.get(), + chatWallpaper + ).start(next: { [weak self] presentationData, themeSettings, chatThemes, themeEmoticon, themeEmoticonAndDarkAppearance, chatWallpaper in if let strongSelf = self { let (themeEmoticonPreview, darkAppearancePreview) = themeEmoticonAndDarkAppearance @@ -5905,6 +5923,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G presentationData = presentationData.withUpdated(theme: lightTheme).withUpdated(chatWallpaper: lightWallpaper) } } + + if let chatWallpaper { + presentationData = presentationData.withUpdated(chatWallpaper: chatWallpaper) + } + + let isFirstTime = !strongSelf.didSetPresentationData strongSelf.presentationData = presentationData strongSelf.didSetPresentationData = true @@ -6820,9 +6844,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if let peerId = self.chatLocation.peerId { - self.themeEmoticonPromise.set(self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ThemeEmoticon(id: peerId))) + self.chatThemeEmoticonPromise.set(self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ThemeEmoticon(id: peerId))) + let chatWallpaper = self.context.account.viewTracker.peerView(peerId) + |> take(1) + |> map { view -> TelegramWallpaper? in + if let cachedUserData = view.cachedData as? CachedUserData { + return cachedUserData.wallpaper + } else { + return nil + } + } + self.chatWallpaperPromise.set(chatWallpaper) } else { - self.themeEmoticonPromise.set(.single(nil)) + self.chatThemeEmoticonPromise.set(.single(nil)) + self.chatWallpaperPromise.set(.single(nil)) } if let peerId = self.chatLocation.peerId { @@ -6961,15 +6996,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if cachedData != nil { var themeEmoticon: String? = nil + var chatWallpaper: TelegramWallpaper? if let cachedData = cachedData as? CachedUserData { themeEmoticon = cachedData.themeEmoticon + chatWallpaper = cachedData.wallpaper } else if let cachedData = cachedData as? CachedGroupData { themeEmoticon = cachedData.themeEmoticon } else if let cachedData = cachedData as? CachedChannelData { themeEmoticon = cachedData.themeEmoticon } - strongSelf.themeEmoticonPromise.set(.single(themeEmoticon)) + strongSelf.chatThemeEmoticonPromise.set(.single(themeEmoticon)) + strongSelf.chatWallpaperPromise.set(.single(chatWallpaper)) } var pinnedMessageId: MessageId? @@ -13771,7 +13809,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.present(actionSheet, in: .window(.root)) } - private func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { + private func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, false), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { return } @@ -18424,7 +18462,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return animatedEmojiStickers } - let _ = (combineLatest(queue: Queue.mainQueue(), self.themeEmoticonPromise.get(), animatedEmojiStickers) + let _ = (combineLatest(queue: Queue.mainQueue(), self.chatThemeEmoticonPromise.get(), animatedEmojiStickers) |> take(1)).start(next: { [weak self] themeEmoticon, animatedEmojiStickers in guard let strongSelf = self else { return @@ -18445,19 +18483,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, changeWallpaper: { - guard let strongSelf = self else { + guard let strongSelf = self, let peerId else { return } if let themeController = strongSelf.themeScreen { strongSelf.themeScreen = nil themeController.dimTapped() } - let controller = ThemeGridController(context: strongSelf.context) + let controller = ThemeGridController(context: strongSelf.context, mode: .peer(peerId, strongSelf.presentationData.chatWallpaper)) controller.navigationPresentation = .modal strongSelf.push(controller) }, completion: { [weak self] emoticon in - guard let strongSelf = self, let peerId = peerId else { + guard let strongSelf = self, let peerId else { return } strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon ?? "", nil))) diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift index 71999c44c0..13d0bec58b 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateInputPanels.swift @@ -182,7 +182,7 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState } } } - } else if let isGeneralThreadClosed = chatPresentationInterfaceState.isGeneralThreadClosed, isGeneralThreadClosed { + } else if let isGeneralThreadClosed = chatPresentationInterfaceState.isGeneralThreadClosed, isGeneralThreadClosed && chatPresentationInterfaceState.interfaceState.replyMessageId == nil { if !canManage { if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) { return (currentPanel, nil) diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 1e6cddcfb6..8aed6eda5b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -157,6 +157,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ result.append((message, ChatMessageGiftBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default))) } else if case .suggestedProfilePhoto = action.action { result.append((message, ChatMessageProfilePhotoSuggestionContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default))) + } else if case .setChatWallpaper = action.action { + result.append((message, ChatMessageWallpaperBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default))) } else { result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default))) } diff --git a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift new file mode 100644 index 0000000000..80a24f3387 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -0,0 +1,312 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import AccountContext +import TelegramPresentationData +import TelegramUIPreferences +import TextFormat +import LocalizedPeerData +import TelegramStringFormatting +import WallpaperBackgroundNode +import PhotoResources +import WallpaperResources +import Markdown + +class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { + private var mediaBackgroundContent: WallpaperBubbleBackgroundNode? + private let mediaBackgroundNode: NavigationBackgroundNode + private let subtitleNode: TextNode + private let imageNode: TransformImageNode + + private let buttonNode: HighlightTrackingButtonNode + private let buttonTitleNode: TextNode + + private var absoluteRect: (CGRect, CGSize)? + + private let fetchDisposable = MetaDisposable() + + required init() { + self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear) + self.mediaBackgroundNode.clipsToBounds = true + self.mediaBackgroundNode.cornerRadius = 24.0 + + self.subtitleNode = TextNode() + self.subtitleNode.isUserInteractionEnabled = false + self.subtitleNode.displaysAsynchronously = false + + self.imageNode = TransformImageNode() + + self.buttonNode = HighlightTrackingButtonNode() + self.buttonNode.clipsToBounds = true + self.buttonNode.cornerRadius = 17.0 + + self.buttonTitleNode = TextNode() + self.buttonTitleNode.isUserInteractionEnabled = false + self.buttonTitleNode.displaysAsynchronously = false + + super.init() + + self.addSubnode(self.mediaBackgroundNode) + self.addSubnode(self.subtitleNode) + self.addSubnode(self.imageNode) + + self.addSubnode(self.buttonNode) + self.addSubnode(self.buttonTitleNode) + + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.buttonNode.layer.removeAnimation(forKey: "opacity") + strongSelf.buttonNode.alpha = 0.4 + strongSelf.buttonTitleNode.layer.removeAnimation(forKey: "opacity") + strongSelf.buttonTitleNode.alpha = 0.4 + } else { + strongSelf.buttonNode.alpha = 1.0 + strongSelf.buttonNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.buttonTitleNode.alpha = 1.0 + strongSelf.buttonTitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.fetchDisposable.dispose() + } + + override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? { + if self.item?.message.id == messageId { + return (self.imageNode, self.imageNode.bounds, { [weak self] in + guard let strongSelf = self else { + return (nil, nil) + } + + let resultView = strongSelf.imageNode.view.snapshotContentTree(unhide: true) + return (resultView, nil) + }) + } else { + return nil + } + } + + override func updateHiddenMedia(_ media: [Media]?) -> Bool { + var mediaHidden = false + var currentMedia: Media? + if let item = item { + mediaLoop: for media in item.message.media { + if let media = media as? TelegramMediaAction { + switch media.action { + case let .suggestedProfilePhoto(image): + currentMedia = image + break mediaLoop + default: + break + } + } + } + } + if let currentMedia = currentMedia, let media = media { + for item in media { + if item.isSemanticallyEqual(to: currentMedia) { + mediaHidden = true + break + } + } + } + + self.imageNode.isHidden = mediaHidden + return mediaHidden + } + + @objc private func buttonPressed() { + guard let item = self.item else { + return + } + let _ = item.controllerInteraction.openMessage(item.message, .default) + } + + override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { + let makeImageLayout = self.imageNode.asyncLayout() + let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode) + let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode) + + let currentItem = self.item + + return { item, layoutConstants, _, _, _, _ in + let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center) + + return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in + let width: CGFloat = 220.0 + let imageSize = CGSize(width: 100.0, height: 100.0) + + let primaryTextColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText + + var wallpaper: TelegramWallpaper? + if let media = item.message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .setChatWallpaper(wallpaperValue) = media.action { + wallpaper = wallpaperValue + } + + var mediaUpdated = true + if let wallpaper = wallpaper, let media = currentItem?.message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .setChatWallpaper(currentWallpaper) = media.action { + mediaUpdated = wallpaper != currentWallpaper + } + + var media: WallpaperPreviewMedia? + if let wallpaper { + media = WallpaperPreviewMedia(wallpaper: wallpaper) + } + + let fromYou = item.message.author?.id == item.context.account.peerId + + let peerName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0).compactDisplayTitle } ?? "" + let text: String + if fromYou { + text = item.presentationData.strings.Notification_YouChangedWallpaper + } else { + text = item.presentationData.strings.Notification_ChangedWallpaper(peerName).string + } + + let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor) + let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor) + + let subtitle = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in + return nil + }), textAlignment: .center) + + let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_Wallpaper_View, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 145.0 + (fromYou ? 0.0 : 37.0)) + + return (backgroundSize.width, { boundingWidth in + return (backgroundSize, { [weak self] animation, synchronousLoads, _ in + if let strongSelf = self { + strongSelf.item = item + + strongSelf.buttonNode.isHidden = fromYou + strongSelf.buttonTitleNode.isHidden = fromYou + + let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: 13.0), size: imageSize) + if let media { + if mediaUpdated { +// strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(context: item.context, userLocation: .peer(item.message.id.peerId), photoReference: .message(message: MessageReference(item.message), media: photo), displayAtSize: nil, storeToDownloadsPeerId: nil).start()) + } + + let boundingSize = imageSize + var imageSize = boundingSize + let updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError> + switch media.content { + case let .file(file, _, _, _, _, _): + var representations: [ImageRepresentationWithReference] = file.previewRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: AnyMediaReference.message(message: MessageReference(item.message), media: file).resourceReference($0.resource)) }) + if file.mimeType == "image/svg+xml" || file.mimeType == "application/x-tgwallpattern" { + representations.append(ImageRepresentationWithReference(representation: .init(dimensions: PixelDimensions(width: 1440, height: 2960), resource: file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), reference: AnyMediaReference.message(message: MessageReference(item.message), media: file).resourceReference(file.resource))) + } + if ["image/png", "image/svg+xml", "application/x-tgwallpattern"].contains(file.mimeType) { + updateImageSignal = patternWallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, representations: representations, mode: .screen) + |> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in + if let value { + return .single(value) + } else { + return .complete() + } + } + } else { + if let dimensions = file.dimensions?.cgSize { + imageSize = dimensions.aspectFilled(boundingSize) + } + updateImageSignal = wallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file), representations: representations, alwaysShowThumbnailFirst: true, thumbnail: true, autoFetchFullSize: true) + } + case let .color(color): + updateImageSignal = solidColorImage(color) + case let .gradient(colors, rotation): + updateImageSignal = gradientImage(colors.map(UIColor.init(rgb:)), rotation: rotation ?? 0) + case .themeSettings: + updateImageSignal = .complete() + } + + strongSelf.imageNode.setSignal(updateImageSignal, attemptSynchronously: synchronousLoads) + + let arguments = TransformImageArguments(corners: ImageCorners(radius: boundingSize.width / 2.0), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()) + let apply = makeImageLayout(arguments) + apply() + + strongSelf.imageNode.frame = imageFrame + } + + let mediaBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - width) / 2.0), y: 0.0), size: backgroundSize) + strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame + + strongSelf.mediaBackgroundNode.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate) + strongSelf.mediaBackgroundNode.update(size: mediaBackgroundFrame.size, transition: .immediate) + strongSelf.buttonNode.backgroundColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12) + + let _ = subtitleApply() + let _ = buttonTitleApply() + + let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 127.0), size: subtitleLayout.size) + strongSelf.subtitleNode.frame = subtitleFrame + + let buttonTitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonTitleLayout.size.width) / 2.0), y: subtitleFrame.maxY + 18.0), size: buttonTitleLayout.size) + strongSelf.buttonTitleNode.frame = buttonTitleFrame + + let buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0) + strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: subtitleFrame.maxY + 10.0), size: buttonSize) + + if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true { + if strongSelf.mediaBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) { + strongSelf.mediaBackgroundNode.isHidden = true + backgroundContent.clipsToBounds = true + backgroundContent.allowsGroupOpacity = true + backgroundContent.cornerRadius = 24.0 + + strongSelf.mediaBackgroundContent = backgroundContent + strongSelf.insertSubnode(backgroundContent, at: 0) + } + + strongSelf.mediaBackgroundContent?.frame = mediaBackgroundFrame + } else { + strongSelf.mediaBackgroundNode.isHidden = false + strongSelf.mediaBackgroundContent?.removeFromSupernode() + strongSelf.mediaBackgroundContent = nil + } + + if let (rect, size) = strongSelf.absoluteRect { + strongSelf.updateAbsoluteRect(rect, within: size) + } + } + }) + }) + }) + } + } + + override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absoluteRect = (rect, containerSize) + + if let mediaBackgroundContent = self.mediaBackgroundContent { + var backgroundFrame = mediaBackgroundContent.frame + backgroundFrame.origin.x += rect.minX + backgroundFrame.origin.y += rect.minY + mediaBackgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate) + } + } + + override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction { + if self.mediaBackgroundNode.frame.contains(point) { + return .openMessage + } else { + return .none + } + } +} diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index abc2b0868a..67000f1a1e 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1406,7 +1406,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL })) items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemIntro, text: "Edit Intro", icon: UIImage(bundleImageName: "Peer Info/BotIntro"), action: { - interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: user.addressName ?? "", behavior: .interactive))) + interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-intro", behavior: .interactive))) })) items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemCommands, text: "Edit Commands", icon: UIImage(bundleImageName: "Peer Info/BotCommands"), action: { interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-commands", behavior: .interactive))) @@ -4132,7 +4132,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } } case let .withBotStartPayload(startPayload): - strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botStart: startPayload)) + strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), botStart: startPayload, keepStack: .always)) case let .withAttachBot(attachBotStart): strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer), attachBotStart: attachBotStart)) case let .withBotApp(botAppStart): diff --git a/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift b/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift index c1ec05a476..04ce3d01be 100644 --- a/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift +++ b/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift @@ -45,3 +45,18 @@ final class WallpaperPreviewMedia: Media { return self.isEqual(to: other) } } + +extension WallpaperPreviewMedia { + convenience init?(wallpaper: TelegramWallpaper) { + switch wallpaper { + case let .color(color): + self.init(content: .color(UIColor(rgb: color))) + case let .gradient(gradient): + self.init(content: .gradient(gradient.colors, gradient.settings.rotation)) + case let .file(file): + self.init(content: .file(file: file.file, colors: file.settings.colors, rotation: file.settings.rotation, intensity: file.settings.intensity, false, false)) + default: + return nil + } + } +}