diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index dc27f99853..99c62a222e 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10768,3 +10768,6 @@ Sorry for the inconvenience."; "Wallpaper.ChannelCustomBackgroundInfo" = "Upload your own background image for the channel."; "Wallpaper.ChannelRemoveBackground" = "Remove Wallpaper"; "Wallpaper.NoWallpaper" = "No\nWallpaper"; + +"ChatList.PremiumXmasGiftTitle" = "Send gifts to **your friends**! 🎄"; +"ChatList.PremiumXmasGiftText" = "Gift Telegram Premium for Christmas."; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index c1e128e786..824f23cb8b 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1388,7 +1388,8 @@ public class PeerNameColors: Equatable { profilePaletteDarkColors: [:], profileStoryColors: [:], profileStoryDarkColors: [:], - profileDisplayOrder: [] + profileDisplayOrder: [], + nameColorsChannelMinRequiredBoostLevel: [:] ) } @@ -1404,6 +1405,8 @@ public class PeerNameColors: Equatable { public let profileStoryDarkColors: [Int32: Colors] public let profileDisplayOrder: [Int32] + public let nameColorsChannelMinRequiredBoostLevel: [Int32: Int32] + public func get(_ color: PeerNameColor, dark: Bool = false) -> Colors { if dark, let colors = self.darkColors[color.rawValue] { return colors @@ -1453,7 +1456,8 @@ public class PeerNameColors: Equatable { profilePaletteDarkColors: [Int32: Colors], profileStoryColors: [Int32: Colors], profileStoryDarkColors: [Int32: Colors], - profileDisplayOrder: [Int32] + profileDisplayOrder: [Int32], + nameColorsChannelMinRequiredBoostLevel: [Int32: Int32] ) { self.colors = colors self.darkColors = darkColors @@ -1465,6 +1469,7 @@ public class PeerNameColors: Equatable { self.profileStoryColors = profileStoryColors self.profileStoryDarkColors = profileStoryDarkColors self.profileDisplayOrder = profileDisplayOrder + self.nameColorsChannelMinRequiredBoostLevel = nameColorsChannelMinRequiredBoostLevel } public static func with(availableReplyColors: EngineAvailableColorOptions, availableProfileColors: EngineAvailableColorOptions) -> PeerNameColors { @@ -1479,8 +1484,14 @@ public class PeerNameColors: Equatable { var profileStoryDarkColors: [Int32: Colors] = [:] var profileDisplayOrder: [Int32] = [] + var nameColorsChannelMinRequiredBoostLevel: [Int32: Int32] = [:] + if !availableReplyColors.options.isEmpty { for option in availableReplyColors.options { + if let requiredChannelMinBoostLevel = option.value.requiredChannelMinBoostLevel { + nameColorsChannelMinRequiredBoostLevel[option.key] = requiredChannelMinBoostLevel + } + if let parsedLight = PeerNameColors.Colors(colors: option.value.light.background) { colors[option.key] = parsedLight } @@ -1539,7 +1550,8 @@ public class PeerNameColors: Equatable { profilePaletteDarkColors: profilePaletteDarkColors, profileStoryColors: profileStoryColors, profileStoryDarkColors: profileStoryDarkColors, - profileDisplayOrder: profileDisplayOrder + profileDisplayOrder: profileDisplayOrder, + nameColorsChannelMinRequiredBoostLevel: nameColorsChannelMinRequiredBoostLevel ) } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index ff248d5245..9efaf4cd31 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -2236,6 +2236,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, openStorageManagement: { }, openPasswordSetup: { }, openPremiumIntro: { + }, openPremiumGift: { }, openActiveSessions: { }, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: { @@ -3556,7 +3557,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode { let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() - }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: { + }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: { }, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in diff --git a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift index 5db4d9194e..f9a989c274 100644 --- a/submodules/ChatListUI/Sources/ChatListShimmerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListShimmerNode.swift @@ -156,7 +156,7 @@ final class ChatListShimmerNode: ASDisplayNode { let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() - }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }) + }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: {}, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: {}, openStories: { _, _ in }) interaction.isInlineMode = isInlineMode let items = (0 ..< 2).map { _ -> ChatListItem in diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 41cce2a269..cdbecdd604 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -100,6 +100,7 @@ public final class ChatListNodeInteraction { let openStorageManagement: () -> Void let openPasswordSetup: () -> Void let openPremiumIntro: () -> Void + let openPremiumGift: () -> Void let openActiveSessions: () -> Void let performActiveSessionAction: (NewSessionReview, Bool) -> Void let openChatFolderUpdates: () -> Void @@ -150,6 +151,7 @@ public final class ChatListNodeInteraction { openStorageManagement: @escaping () -> Void, openPasswordSetup: @escaping () -> Void, openPremiumIntro: @escaping () -> Void, + openPremiumGift: @escaping () -> Void, openActiveSessions: @escaping () -> Void, performActiveSessionAction: @escaping (NewSessionReview, Bool) -> Void, openChatFolderUpdates: @escaping () -> Void, @@ -187,6 +189,7 @@ public final class ChatListNodeInteraction { self.openStorageManagement = openStorageManagement self.openPasswordSetup = openPasswordSetup self.openPremiumIntro = openPremiumIntro + self.openPremiumGift = openPremiumGift self.openActiveSessions = openActiveSessions self.performActiveSessionAction = performActiveSessionAction self.openChatFolderUpdates = openChatFolderUpdates @@ -708,6 +711,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction?.openPasswordSetup() case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore: nodeInteraction?.openPremiumIntro() + case .xmasPremiumGift: + nodeInteraction?.openPremiumGift() case .reviewLogin: break } @@ -1028,6 +1033,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL nodeInteraction?.openPasswordSetup() case .premiumUpgrade, .premiumAnnualDiscount, .premiumRestore: nodeInteraction?.openPremiumIntro() + case .xmasPremiumGift: + nodeInteraction?.openPremiumGift() case .reviewLogin: break } @@ -1604,6 +1611,17 @@ public final class ChatListNode: ListView { } let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads, forceDark: false, dismissed: nil) self.push?(controller) + }, openPremiumGift: { [weak self] in + guard let self else { + return + } + Queue.mainQueue().after(0.6) { [weak self] in + if let self { + let _ = dismissServerProvidedSuggestion(account: self.context.account, suggestion: .xmasPremiumGift).startStandalone() + } + } + let controller = self.context.sharedContext.makePremiumGiftController(context: self.context) + self.push?(controller) }, openActiveSessions: { [weak self] in guard let self else { return @@ -1793,7 +1811,9 @@ public final class ChatListNode: ListView { return .single(.setupPassword) } } - if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager { + if suggestions.contains(.xmasPremiumGift) { + return .single(.xmasPremiumGift) + } else if suggestions.contains(.annualPremium) || suggestions.contains(.upgradePremium) || suggestions.contains(.restorePremium), let inAppPurchaseManager = context.inAppPurchaseManager { return inAppPurchaseManager.availableProducts |> map { products -> ChatListNotice? in if products.count > 1 { diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift index 8c0f2bd8de..bd2ee5f620 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift @@ -85,6 +85,7 @@ enum ChatListNotice: Equatable { case premiumUpgrade(discount: Int32) case premiumAnnualDiscount(discount: Int32) case premiumRestore(discount: Int32) + case xmasPremiumGift case reviewLogin(newSessionReview: NewSessionReview, totalCount: Int) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift index 7b2947a44e..246ac15a9c 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift @@ -7,6 +7,7 @@ import TelegramPresentationData import ListSectionHeaderNode import AppBundle import ItemListUI +import Markdown class ChatListStorageInfoItem: ListViewItem { enum Action { @@ -203,6 +204,9 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode { titleString = titleStringValue textString = NSAttributedString(string: item.strings.ChatList_PremiumRestoreDiscountText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) + case .xmasPremiumGift: + titleString = parseMarkdownIntoAttributedString(item.strings.ChatList_PremiumXmasGiftTitle, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), bold: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.accentTextColor), link: MarkdownAttributeSet(font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor), linkAttribute: { _ in return nil })) + textString = NSAttributedString(string: item.strings.ChatList_PremiumXmasGiftText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor) case let .reviewLogin(newSessionReview, totalCount): spacing = 2.0 alignment = .center diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index 73111b4f6d..ce2f60b0d0 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -4739,7 +4739,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if self.experimentalSnapScrollToItem { self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.visible, animated: animated, curve: ListViewAnimationCurve.Default(duration: nil), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } else { - if node.frame.minY < self.insets.top { + if node.frame.minY < self.insets.top + overflow { if !allowIntersection || node.frame.maxY < self.insets.top { let position: ListViewScrollPosition if allowIntersection { @@ -4749,7 +4749,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: position, animated: animated, curve: curve, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } - } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { + } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom - overflow { if !allowIntersection || node.frame.minY > self.visibleSize.height - self.insets.bottom { let position: ListViewScrollPosition if allowIntersection { diff --git a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift index 26ece05e2e..78d220a911 100644 --- a/submodules/DrawingUI/Sources/DrawingEntitiesView.swift +++ b/submodules/DrawingUI/Sources/DrawingEntitiesView.swift @@ -36,6 +36,7 @@ private func makeEntityView(context: AccountContext, entity: DrawingEntity) -> D private func prepareForRendering(entityView: DrawingEntityView) { if let entityView = entityView as? DrawingStickerEntityView { entityView.entity.renderImage = entityView.getRenderImage() + entityView.entity.renderSubEntities = entityView.getRenderSubEntities() } if let entityView = entityView as? DrawingBubbleEntityView { entityView.entity.renderImage = entityView.getRenderImage() diff --git a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift index a2ce71b0a3..71bd36285f 100644 --- a/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift +++ b/submodules/DrawingUI/Sources/DrawingStickerEntityView.swift @@ -154,7 +154,7 @@ public class DrawingStickerEntityView: DrawingEntityView { return file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) case .dualVideoReference: return CGSize(width: 512.0, height: 512.0) - case let .message(_, size): + case let .message(_, _, size): return size } } @@ -267,45 +267,7 @@ public class DrawingStickerEntityView: DrawingEntityView { }), attemptSynchronously: synchronous) self.setNeedsLayout() } else if case let .video(file) = self.stickerEntity.content { - let videoNode = UniversalVideoNode( - postbox: self.context.account.postbox, - audioSession: self.context.sharedContext.mediaManager.audioSession, - manager: self.context.sharedContext.mediaManager.universalVideoManager, - decoration: StickerVideoDecoration(), - content: NativeVideoContent( - id: .contextResult(0, "\(UInt64.random(in: 0 ... UInt64.max))"), - userLocation: .other, - fileReference: .standalone(media: file), - imageReference: nil, - streamVideo: .story, - loopVideo: true, - enableSound: false, - soundMuted: true, - beginWithAmbientSound: false, - mixWithOthers: true, - useLargeThumbnail: false, - autoFetchFullSizeThumbnail: false, - tempFilePath: nil, - captureProtected: false, - hintDimensions: file.dimensions?.cgSize, - storeAfterDownload: nil, - displayImage: false, - hasSentFramesToDisplay: { [weak self] in - guard let self else { - return - } - self.videoNode?.isHidden = false - } - ), - priority: .gallery - ) - videoNode.canAttachContent = true - videoNode.isUserInteractionEnabled = false - videoNode.clipsToBounds = true - self.addSubnode(videoNode) - self.videoNode = videoNode - self.setNeedsLayout() - videoNode.play() + self.setupWithVideo(file) } else if case let .animatedImage(data, thumbnailImage) = self.stickerEntity.content { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit @@ -314,16 +276,63 @@ public class DrawingStickerEntityView: DrawingEntityView { self.animatedImageView = imageView self.addSubview(imageView) self.setNeedsLayout() - } else if case .message = self.stickerEntity.content { + } else if case let .message(_, innerFile, _) = self.stickerEntity.content { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit imageView.image = self.stickerEntity.renderImage self.animatedImageView = imageView self.addSubview(imageView) self.setNeedsLayout() + + let _ = innerFile +// if let innerFile, innerFile.isAnimated { +// self.setupWithVideo(innerFile) +// } } } + private func setupWithVideo(_ file: TelegramMediaFile) { + let videoNode = UniversalVideoNode( + postbox: self.context.account.postbox, + audioSession: self.context.sharedContext.mediaManager.audioSession, + manager: self.context.sharedContext.mediaManager.universalVideoManager, + decoration: StickerVideoDecoration(), + content: NativeVideoContent( + id: .contextResult(0, "\(UInt64.random(in: 0 ... UInt64.max))"), + userLocation: .other, + fileReference: .standalone(media: file), + imageReference: nil, + streamVideo: .story, + loopVideo: true, + enableSound: false, + soundMuted: true, + beginWithAmbientSound: false, + mixWithOthers: true, + useLargeThumbnail: false, + autoFetchFullSizeThumbnail: false, + tempFilePath: nil, + captureProtected: false, + hintDimensions: file.dimensions?.cgSize, + storeAfterDownload: nil, + displayImage: false, + hasSentFramesToDisplay: { [weak self] in + guard let self else { + return + } + self.videoNode?.isHidden = false + } + ), + priority: .gallery + ) + videoNode.canAttachContent = true + videoNode.isUserInteractionEnabled = false + videoNode.clipsToBounds = true + self.addSubnode(videoNode) + self.videoNode = videoNode + self.setNeedsLayout() + videoNode.play() + } + public override func play() { self.isVisible = true self.applyVisibility() @@ -494,6 +503,13 @@ public class DrawingStickerEntityView: DrawingEntityView { guard let containerView = self.containerView, case let .image(image, _) = self.stickerEntity.content else { return } + + let scaledSize = image.size.aspectFitted(CGSize(width: 180.0, height: 180.0)) + guard let scaledImage = generateScaledImage(image: image, size: scaledSize) else { + self.isHidden = true + completion() + return + } let dustEffectLayer = DustEffectLayer() dustEffectLayer.position = containerView.bounds.center @@ -506,7 +522,7 @@ public class DrawingStickerEntityView: DrawingEntityView { containerView.layer.insertSublayer(dustEffectLayer, below: self.layer) let itemFrame = self.layer.convert(self.bounds, to: dustEffectLayer) - dustEffectLayer.addItem(frame: itemFrame, image: image) + dustEffectLayer.addItem(frame: itemFrame, image: scaledImage) self.isHidden = true } @@ -515,79 +531,6 @@ public class DrawingStickerEntityView: DrawingEntityView { let values = [self.entity.scale, self.entity.scale * 1.1, self.entity.scale] let keyTimes = [0.0, 0.67, 1.0] self.layer.animateKeyframes(values: values as [NSNumber], keyTimes: keyTimes as [NSNumber], duration: 0.35, keyPath: "transform.scale") -// func blob(pointsCount: Int, randomness: CGFloat) -> [CGPoint] { -// let angle = (CGFloat.pi * 2) / CGFloat(pointsCount) -// -// let rgen = { () -> CGFloat in -// let accuracy: UInt32 = 1000 -// let random = arc4random_uniform(accuracy) -// return CGFloat(random) / CGFloat(accuracy) -// } -// let rangeStart: CGFloat = 1 / (1 + randomness / 10) -// -// let startAngle = angle * CGFloat(arc4random_uniform(100)) / CGFloat(100) -// let points = (0 ..< pointsCount).map { i -> CGPoint in -// let randPointOffset = (rangeStart + CGFloat(rgen()) * (1 - rangeStart)) / 2 -// let angleRandomness: CGFloat = angle * 0.1 -// let randAngle = angle + angle * ((angleRandomness * CGFloat(arc4random_uniform(100)) / CGFloat(100)) - angleRandomness * 0.5) -// let pointX = sin(startAngle + CGFloat(i) * randAngle) -// let pointY = cos(startAngle + CGFloat(i) * randAngle) -// return CGPoint( -// x: pointX * randPointOffset, -// y: pointY * randPointOffset -// ) -// } -// return points -// } -// -// func generateNextBlob(for size: CGSize) -> [CGPoint] { -// let pointsCount = 8 -// let minRandomness = 1.0 -// let maxRandomness = 1.0 -// let speedLevel = 0.8 -// let randomness = minRandomness + (maxRandomness - minRandomness) * speedLevel -// return blob(pointsCount: pointsCount, randomness: randomness) -// .map { -// return CGPoint( -// x: $0.x * CGFloat(size.width), -// y: $0.y * CGFloat(size.height) -// ) -// } -// } -// -// guard case let .image(image, _) = self.stickerEntity.content else { -// return -// } -// let maskView = UIImageView() -// maskView.frame = self.bounds -// maskView.image = image -// self.mask = maskView -// -// let blobLayer = CAShapeLayer() -// blobLayer.strokeColor = UIColor.red.cgColor -// blobLayer.fillColor = UIColor.clear.cgColor -// blobLayer.lineWidth = 2.0 -// blobLayer.shadowRadius = 3.0 -// blobLayer.shadowOpacity = 0.8 -// blobLayer.shadowColor = UIColor.white.cgColor -// blobLayer.position = CGPoint( -// x: CGFloat.random(in: self.bounds.width * 0.33 ..< self.bounds.width * 0.5), -// y: self.bounds.height * 0.5 -// ) -// -// -// let minSide = min(self.bounds.width, self.bounds.height) -// let size = CGSize(width: minSide * 0.5, height: minSide * 0.5) -// blobLayer.bounds = CGRect(origin: .zero, size: size) -// -// let points = generateNextBlob(for: size) -// blobLayer.path = UIBezierPath.smoothCurve(through: points, length: size.width).cgPath -// self.layer.addSublayer(blobLayer) -// -// blobLayer.animateScale(from: 0.01, to: 3.0, duration: 1.0, removeOnCompletion: false, completion: { _ in -// blobLayer.removeFromSuperlayer() -// self.mask = nil -// }) } private var didApplyVisibility = false @@ -621,7 +564,14 @@ public class DrawingStickerEntityView: DrawingEntityView { } if let videoNode = self.videoNode { - videoNode.cornerRadius = floor(imageSize.width * 0.03) + var imageSize = imageSize + if case let .message(_, file, _) = self.stickerEntity.content, let dimensions = file?.dimensions { + let fittedDimensions = dimensions.cgSize.aspectFitted(boundingSize) + imageSize = fittedDimensions + videoNode.cornerRadius = 0.0 + } else { + videoNode.cornerRadius = floor(imageSize.width * 0.03) + } videoNode.frame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) * 0.5), y: floor((size.height - imageSize.height) * 0.5)), size: imageSize) videoNode.updateLayout(size: imageSize, transition: .immediate) } @@ -743,6 +693,15 @@ public class DrawingStickerEntityView: DrawingEntityView { selectionView.entityView = self return selectionView } + + func getRenderSubEntities() -> [DrawingEntity] { + guard case let .message(_, file, _) = self.stickerEntity.content else { + return [] + } + + let _ = file + return [] + } } final class DrawingStickerEntititySelectionView: DrawingEntitySelectionView { @@ -1277,3 +1236,48 @@ extension UIImageView { self.animationRepeatCount = 0 } } + +//private func prerenderEntityTransformations(entity: DrawingEntity, image: UIImage, colorSpace: CGColorSpace) -> UIImage { +// let imageSize = image.size +// +// let angle: CGFloat +// var scale: CGFloat +// let position: CGPoint +// +// if let entity = entity as? DrawingStickerEntity { +// angle = -entity.rotation +// scale = entity.scale +// position = entity.position +// } else { +// fatalError() +// } +// +// let rotatedSize = CGSize( +// width: abs(imageSize.width * cos(angle)) + abs(imageSize.height * sin(angle)), +// height: abs(imageSize.width * sin(angle)) + abs(imageSize.height * cos(angle)) +// ) +// let newSize = CGSize(width: rotatedSize.width * scale, height: rotatedSize.height * scale) +// +// let newImage = generateImage(newSize, contextGenerator: { size, context in +// context.setAllowsAntialiasing(true) +// context.setShouldAntialias(true) +// context.interpolationQuality = .high +// context.clear(CGRect(origin: .zero, size: size)) +// context.translateBy(x: newSize.width * 0.5, y: newSize.height * 0.5) +// context.rotate(by: angle) +// context.scaleBy(x: scale, y: scale) +// let drawRect = CGRect( +// x: -imageSize.width * 0.5, +// y: -imageSize.height * 0.5, +// width: imageSize.width, +// height: imageSize.height +// ) +// if let cgImage = image.cgImage { +// context.draw(cgImage, in: drawRect) +// } +// }, scale: 1.0)! +// +// let _ = position +// +// return newImage +//} diff --git a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift index ca46f6d356..2b60d8e325 100644 --- a/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift +++ b/submodules/HashtagSearchUI/Sources/HashtagSearchController.swift @@ -95,6 +95,7 @@ public final class HashtagSearchController: TelegramBaseController { }, openStorageManagement: { }, openPasswordSetup: { }, openPremiumIntro: { + }, openPremiumGift: { }, openActiveSessions: { }, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: { diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index cd331d7da2..fadc5de53d 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -182,17 +182,17 @@ private enum CreateGiveawayEntry: ItemListNodeEntry { return 202 case .prizeDescriptionInfo: return 203 - case .timeHeader: - return 204 - case .timeExpiryDate: - return 205 - case .timeCustomPicker: - return 206 - case .timeInfo: - return 207 case .winners: - return 208 + return 204 case .winnersInfo: + return 205 + case .timeHeader: + return 206 + case .timeExpiryDate: + return 207 + case .timeCustomPicker: + return 208 + case .timeInfo: return 209 } } @@ -777,12 +777,12 @@ private func createGiveawayControllerEntries( } entries.append(.prizeDescriptionInfo(presentationData.theme, prizeDescriptionInfoText)) + entries.append(.winners(presentationData.theme, presentationData.strings.BoostGift_Winners, state.showWinners)) + entries.append(.winnersInfo(presentationData.theme, presentationData.strings.BoostGift_WinnersInfo)) + entries.append(.timeHeader(presentationData.theme, presentationData.strings.BoostGift_DateTitle.uppercased())) entries.append(.timeCustomPicker(presentationData.theme, presentationData.dateTimeFormat, state.time, minDate, maxDate, state.pickingExpiryDate, state.pickingExpiryTime)) entries.append(.timeInfo(presentationData.theme, presentationData.strings.BoostGift_DateInfo(presentationData.strings.BoostGift_DateInfoSubscribers(Int32(state.subscriptions))).string)) - - entries.append(.winners(presentationData.theme, presentationData.strings.BoostGift_Winners, state.showWinners)) - entries.append(.winnersInfo(presentationData.theme, presentationData.strings.BoostGift_WinnersInfo)) case .gift: appendDurationEntries() } @@ -803,7 +803,7 @@ private struct CreateGiveawayControllerState: Equatable { var selectedMonths: Int32? var countries: [String] = [] var onlyNewEligible: Bool = false - var showWinners: Bool = false + var showWinners: Bool = true var showPrizeDescription: Bool = false var prizeDescription: String = "" var time: Int32 diff --git a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift index 392fd6991b..87b098b81a 100644 --- a/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumBoostLevelsScreen.swift @@ -20,10 +20,37 @@ import SolidRoundedButtonComponent import BlurredBackgroundComponent import UndoUI +func requiredBoostSubjectLevel(subject: BoostSubject, context: AccountContext, configuration: PremiumConfiguration) -> Int32 { + switch subject { + case .stories: + return 1 + case let .channelReactions(reactionCount): + return reactionCount + case let .nameColors(colors): + if let value = context.peerNameColors.nameColorsChannelMinRequiredBoostLevel[colors.rawValue] { + return value + } else { + return 1 + } + case .nameIcon: + return configuration.minChannelNameIconLevel + case .profileColors: + return configuration.minChannelProfileColorLevel + case .profileIcon: + return configuration.minChannelProfileIconLevel + case .emojiStatus: + return configuration.minChannelEmojiStatusLevel + case .wallpaper: + return configuration.minChannelWallpaperLevel + case .customWallpaper: + return configuration.minChannelCustomWallpaperLevel + } +} + public enum BoostSubject: Equatable { case stories case channelReactions(reactionCount: Int32) - case nameColors + case nameColors(colors: PeerNameColor) case nameIcon case profileColors case profileIcon @@ -31,27 +58,8 @@ public enum BoostSubject: Equatable { case wallpaper case customWallpaper - public func requiredLevel(_ configuration: PremiumConfiguration) -> Int32 { - switch self { - case .stories: - return 1 - case let .channelReactions(reactionCount): - return reactionCount - case .nameColors: - return configuration.minChannelNameColorLevel - case .nameIcon: - return configuration.minChannelNameIconLevel - case .profileColors: - return configuration.minChannelProfileColorLevel - case .profileIcon: - return configuration.minChannelProfileIconLevel - case .emojiStatus: - return configuration.minChannelEmojiStatusLevel - case .wallpaper: - return configuration.minChannelWallpaperLevel - case .customWallpaper: - return configuration.minChannelCustomWallpaperLevel - } + public func requiredLevel(context: AccountContext, configuration: PremiumConfiguration) -> Int32 { + return requiredBoostSubjectLevel(subject: self, context: context, configuration: configuration) } } @@ -479,7 +487,9 @@ private final class LimitSheetContent: CombinedComponent { textString = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string needsSecondParagraph = false case .nameColors: - textString = strings.ChannelBoost_EnableNameColorLevelText("\(premiumConfiguration.minChannelNameColorLevel)").string + let colorLevel = component.subject.requiredLevel(context: context.component.context, configuration: premiumConfiguration) + + textString = strings.ChannelBoost_EnableNameColorLevelText("\(colorLevel)").string case .nameIcon: textString = strings.ChannelBoost_EnableNameIconLevelText("\(premiumConfiguration.minChannelNameIconLevel)").string case .profileColors: diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index 29573caeb3..29c1e1dfbc 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -1180,6 +1180,7 @@ private final class LimitSheetContent: CombinedComponent { if let remaining { let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1) let valueString = strings.ChannelBoost_MoreBoosts(remaining) + switch boostSubject { case .stories: if level == 0 { @@ -1189,9 +1190,12 @@ private final class LimitSheetContent: CombinedComponent { titleText = strings.ChannelBoost_IncreaseLimit string = strings.ChannelBoost_IncreaseLimitText(valueString, storiesString).string } - case .nameColors: + case let .nameColors(colors): titleText = strings.ChannelBoost_EnableColors - string = strings.ChannelBoost_EnableColorsLevelText("\(premiumConfiguration.minChannelNameColorLevel)").string + + let colorLevel = requiredBoostSubjectLevel(subject: .nameColors(colors: colors), context: component.context, configuration: premiumConfiguration) + + string = strings.ChannelBoost_EnableColorsLevelText("\(colorLevel)").string case let .channelReactions(reactionCount): titleText = strings.ChannelBoost_CustomReactions string = strings.ChannelBoost_CustomReactionsText("\(reactionCount)", "\(reactionCount)").string @@ -1778,7 +1782,7 @@ public class PremiumLimitScreen: ViewControllerComponentContainer { public enum BoostSubject: Equatable { case stories - case nameColors + case nameColors(colors: PeerNameColor) case channelReactions(reactionCount: Int) } diff --git a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift index a0591b67c4..4c4560408d 100644 --- a/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift +++ b/submodules/SettingsUI/Sources/Text Size/TextSizeSelectionController.swift @@ -222,7 +222,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView }, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() - }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: { + }, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: { }, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in diff --git a/submodules/SettingsUI/Sources/ThemePickerGridItem.swift b/submodules/SettingsUI/Sources/ThemePickerGridItem.swift index a1dec15db2..ef7c648cb7 100644 --- a/submodules/SettingsUI/Sources/ThemePickerGridItem.swift +++ b/submodules/SettingsUI/Sources/ThemePickerGridItem.swift @@ -193,10 +193,14 @@ private final class ThemeGridThemeItemIconNode : ASDisplayNode { } let string: String? - if let _ = item.themeReference.emoticon { - string = nil + if let themeReference = item.themeReference { + if let _ = themeReference.emoticon { + string = nil + } else { + string = themeDisplayName(strings: item.strings, reference: themeReference) + } } else { - string = themeDisplayName(strings: item.strings, reference: item.themeReference) + string = nil } let text = NSAttributedString(string: string ?? item.strings.Conversation_Theme_NoTheme, font: Font.bold(14.0), textColor: .white) @@ -207,16 +211,17 @@ private final class ThemeGridThemeItemIconNode : ASDisplayNode { let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) if updatedThemeReference || updatedWallpaper || updatedNightMode || updatedSize { - var themeReference = item.themeReference - if case .builtin = themeReference, item.nightMode { - themeReference = .builtin(.night) + if var themeReference = item.themeReference { + if case .builtin = themeReference, item.nightMode { + themeReference = .builtin(.night) + } + + let color = item.themeSpecificAccentColors[themeReference.index] + let wallpaper = item.themeSpecificChatWallpapers[themeReference.index] + + self.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: color, wallpaper: wallpaper ?? item.wallpaper, nightMode: item.nightMode, emoticon: true, large: true)) + self.imageNode.backgroundColor = nil } - - let color = item.themeSpecificAccentColors[themeReference.index] - let wallpaper = item.themeSpecificChatWallpapers[themeReference.index] - - self.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: color, wallpaper: wallpaper ?? item.wallpaper, nightMode: item.nightMode, emoticon: true, large: true)) - self.imageNode.backgroundColor = nil } if updatedTheme || updatedSelected { @@ -501,7 +506,9 @@ class ThemeGridThemeItemNode: ListViewItemNode, ItemListItemNode { let selected = item.currentTheme.index == theme.index let iconItem = ThemeCarouselThemeIconItem(context: item.context, emojiFile: theme.emoticon.flatMap { item.animatedEmojiStickers[$0]?.first?.file }, themeReference: theme, nightMode: item.nightMode, channelMode: false, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: selected, theme: item.theme, strings: item.strings, wallpaper: nil, action: { theme in - item.updatedTheme(theme) + if let theme { + item.updatedTheme(theme) + } }, contextAction: nil) validIds.append(theme.index) diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift index f1a854eb17..d2f4e8a75e 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift @@ -371,7 +371,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate { }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in - }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: { + }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: { }, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index e21f3b4f7b..22cd47d571 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -285,8 +285,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry { case let .chatPreview(theme, wallpaper, fontSize, chatBubbleCorners, strings, dateTimeFormat, nameDisplayOrder, items): return ThemeSettingsChatPreviewItem(context: arguments.context, theme: theme, componentTheme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, messageItems: items) case let .themes(theme, strings, chatThemes, currentTheme, nightMode, animatedEmojiStickers, themeSpecificAccentColors, themeSpecificChatWallpapers): - return ThemeCarouselThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, nightMode: nightMode, currentTheme: currentTheme, updatedTheme: { theme in - arguments.selectTheme(theme) + return ThemeCarouselThemeItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, themes: chatThemes, hasNoTheme: false, animatedEmojiStickers: animatedEmojiStickers, themeSpecificAccentColors: themeSpecificAccentColors, themeSpecificChatWallpapers: themeSpecificChatWallpapers, nightMode: nightMode, currentTheme: currentTheme, updatedTheme: { theme in + if let theme { + arguments.selectTheme(theme) + } }, contextAction: { theme, node, gesture in arguments.themeContextAction(false, theme, node, gesture) }, tag: ThemeSettingsEntryTag.theme) diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 418388a82c..89db221498 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -628,6 +628,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP if abs(panGestureState.offsetFraction) > 0.6 || abs(velocity.y) >= 100.0 { self.panGestureState = PanGestureState(offsetFraction: panGestureState.offsetFraction < 0.0 ? -1.0 : 1.0) self.notifyDismissedInteractivelyOnPanGestureApply = true + self.callScreen.beginPictureInPictureIfPossible() } self.update(transition: .animated(duration: 0.4, curve: .spring)) diff --git a/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift b/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift index 4872e91bf2..9bb1a7ae3f 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift @@ -80,7 +80,11 @@ extension TelegramWallpaper { self = .color(0xffffff) } case let .wallPaperNoFile(id, _, settings): - if let settings = settings, case let .wallPaperSettings(_, backgroundColor, secondBackgroundColor, thirdBackgroundColor, fourthBackgroundColor, _, rotation, _) = settings { + if let settings = settings, case let .wallPaperSettings(_, backgroundColor, secondBackgroundColor, thirdBackgroundColor, fourthBackgroundColor, _, rotation, emoticon) = settings { + if id == 0, let emoticon = emoticon { + self = .emoticon(emoticon) + return + } let colors: [UInt32] = ([backgroundColor, secondBackgroundColor, thirdBackgroundColor, fourthBackgroundColor] as [Int32?]).compactMap({ color -> UInt32? in return color.flatMap(UInt32.init(bitPattern:)) }) @@ -108,12 +112,10 @@ extension TelegramWallpaper { return (.inputWallPaperNoFile(id: 0), apiWallpaperSettings(WallpaperSettings(colors: [color]))) case let .gradient(gradient): return (.inputWallPaperNoFile(id: gradient.id ?? 0), apiWallpaperSettings(WallpaperSettings(colors: gradient.colors, rotation: gradient.settings.rotation))) + case let .emoticon(emoticon): + return (.inputWallPaperNoFile(id: 0), apiWallpaperSettings(WallpaperSettings(emoticon: emoticon))) default: return nil } } - - func apiInputWallpaper(emoticon: String) -> (Api.InputWallPaper, Api.WallPaperSettings) { - return (.inputWallPaperNoFile(id: 0), apiWallpaperSettings(WallpaperSettings(emoticon: emoticon))) - } } diff --git a/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift index 2918736048..76c5dc6fcc 100644 --- a/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift +++ b/submodules/TelegramCore/Sources/State/ManagedPeerColorUpdates.swift @@ -94,16 +94,19 @@ public final class EngineAvailableColorOptions: Codable, Equatable { case light = "l" case dark = "d" case isHidden = "h" + case requiredChannelMinBoostLevel = "rcmb" } public let light: ColorOption public let dark: ColorOption? public let isHidden: Bool + public let requiredChannelMinBoostLevel: Int32? - public init(light: ColorOption, dark: ColorOption?, isHidden: Bool) { + public init(light: ColorOption, dark: ColorOption?, isHidden: Bool, requiredChannelMinBoostLevel: Int32?) { self.light = light self.dark = dark self.isHidden = isHidden + self.requiredChannelMinBoostLevel = requiredChannelMinBoostLevel } public init(from decoder: Decoder) throws { @@ -112,6 +115,7 @@ public final class EngineAvailableColorOptions: Codable, Equatable { self.light = try container.decode(ColorOption.self, forKey: .light) self.dark = try container.decodeIfPresent(ColorOption.self, forKey: .dark) self.isHidden = try container.decode(Bool.self, forKey: .isHidden) + self.requiredChannelMinBoostLevel = try container.decodeIfPresent(Int32.self, forKey: .requiredChannelMinBoostLevel) } public func encode(to encoder: Encoder) throws { @@ -120,6 +124,7 @@ public final class EngineAvailableColorOptions: Codable, Equatable { try container.encode(self.light, forKey: .light) try container.encodeIfPresent(self.dark, forKey: .dark) try container.encodeIfPresent(self.isHidden, forKey: .isHidden) + try container.encodeIfPresent(self.requiredChannelMinBoostLevel, forKey: .requiredChannelMinBoostLevel) } public static func ==(lhs: ColorOptionPack, rhs: ColorOptionPack) -> Bool { @@ -135,6 +140,9 @@ public final class EngineAvailableColorOptions: Codable, Equatable { if lhs.isHidden != rhs.isHidden { return false } + if lhs.requiredChannelMinBoostLevel != rhs.requiredChannelMinBoostLevel { + return false + } return true } } @@ -262,14 +270,14 @@ private extension EngineAvailableColorOptions { var mappedOptions: [Option] = [] for apiColor in apiColors { switch apiColor { - case let .peerColorOption(flags, colorId, colors, darkColors, _): + case let .peerColorOption(flags, colorId, colors, darkColors, requiredChannelMinBoostLevel): let isHidden = (flags & (1 << 0)) != 0 let mappedColors = colors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:)) let mappedDarkColors = darkColors.flatMap(EngineAvailableColorOptions.ColorOption.init(apiColors:)) if let mappedColors = mappedColors { - mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: mappedColors, dark: mappedDarkColors, isHidden: isHidden))) + mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: mappedColors, dark: mappedDarkColors, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel))) } else if colorId >= 0 && colorId <= 6 { let staticMap: [UInt32] = [ 0xcc5049, @@ -282,7 +290,7 @@ private extension EngineAvailableColorOptions { ] let colorPack = MultiColorPack(colors: [staticMap[Int(colorId)]]) let defaultColors = EngineAvailableColorOptions.ColorOption(palette: colorPack, background: colorPack, stories: nil) - mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: defaultColors, dark: nil, isHidden: isHidden))) + mappedOptions.append(Option(key: colorId, value: ColorOptionPack(light: defaultColors, dark: nil, isHidden: isHidden, requiredChannelMinBoostLevel: requiredChannelMinBoostLevel))) } } } diff --git a/submodules/TelegramCore/Sources/Suggestions.swift b/submodules/TelegramCore/Sources/Suggestions.swift index 792f97e781..d8ad886658 100644 --- a/submodules/TelegramCore/Sources/Suggestions.swift +++ b/submodules/TelegramCore/Sources/Suggestions.swift @@ -12,6 +12,7 @@ public enum ServerProvidedSuggestion: String { case upgradePremium = "PREMIUM_UPGRADE" case annualPremium = "PREMIUM_ANNUAL" case restorePremium = "PREMIUM_RESTORE" + case xmasPremiumGift = "PREMIUM_GIFT_XMAS" } private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set]>([:]) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramWallpaper.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramWallpaper.swift index b39e0f5850..5c0518226e 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramWallpaper.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramWallpaper.swift @@ -80,7 +80,7 @@ public struct TelegramWallpaperNativeCodable: Codable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: StringCodingKey.self) - + switch try container.decode(Int32.self, forKey: "v") { case 0: let settings = try container.decode(WallpaperSettings.self, forKey: "settings") @@ -113,9 +113,9 @@ public struct TelegramWallpaperNativeCodable: Codable { } case 4: let settings = try container.decode(WallpaperSettings.self, forKey: "settings") - + var colors: [UInt32] = [] - + if let topColor = (try container.decodeIfPresent(Int32.self, forKey: "c1")).flatMap(UInt32.init(bitPattern:)) { colors.append(topColor) if let bottomColor = (try container.decodeIfPresent(Int32.self, forKey: "c2")).flatMap(UInt32.init(bitPattern:)) { @@ -124,12 +124,14 @@ public struct TelegramWallpaperNativeCodable: Codable { } else { colors = (try container.decode([Int32].self, forKey: "colors")).map(UInt32.init(bitPattern:)) } - + self.value = .gradient(TelegramWallpaper.Gradient( id: try container.decodeIfPresent(Int64.self, forKey: "id"), colors: colors, settings: settings )) + case 5: + self.value = .emoticon(try container.decode(String.self, forKey: "e")) default: assertionFailure() self.value = .color(0xffffff) @@ -138,41 +140,48 @@ public struct TelegramWallpaperNativeCodable: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: StringCodingKey.self) - + switch self.value { - case let .builtin(settings): - try container.encode(0 as Int32, forKey: "v") - try container.encode(settings, forKey: "settings") - case let .color(color): - try container.encode(1 as Int32, forKey: "v") - try container.encode(Int32(bitPattern: color), forKey: "c") - case let .gradient(gradient): - try container.encode(4 as Int32, forKey: "v") - try container.encodeIfPresent(gradient.id, forKey: "id") - try container.encode(gradient.colors.map(Int32.init(bitPattern:)), forKey: "colors") - try container.encode(gradient.settings, forKey: "settings") - case let .image(representations, settings): - try container.encode(2 as Int32, forKey: "v") - try container.encode(representations.map { item in - return PostboxEncoder().encodeObjectToRawData(item) - }, forKey: "i") - try container.encode(settings, forKey: "settings") - case let .file(file): - try container.encode(3 as Int32, forKey: "v") - try container.encode(file.id, forKey: "id") - try container.encode(file.accessHash, forKey: "accessHash") - try container.encode((file.isCreator ? 1 : 0) as Int32, forKey: "isCreator") - try container.encode((file.isDefault ? 1 : 0) as Int32, forKey: "isDefault") - try container.encode((file.isPattern ? 1 : 0) as Int32, forKey: "isPattern") - try container.encode((file.isDark ? 1 : 0) as Int32, forKey: "isDark") - try container.encode(file.slug, forKey: "slug") - try container.encode(PostboxEncoder().encodeObjectToRawData(file.file), forKey: "file") - try container.encode(file.settings, forKey: "settings") + case let .builtin(settings): + try container.encode(0 as Int32, forKey: "v") + try container.encode(settings, forKey: "settings") + case let .color(color): + try container.encode(1 as Int32, forKey: "v") + try container.encode(Int32(bitPattern: color), forKey: "c") + case let .gradient(gradient): + try container.encode(4 as Int32, forKey: "v") + try container.encodeIfPresent(gradient.id, forKey: "id") + try container.encode(gradient.colors.map(Int32.init(bitPattern:)), forKey: "colors") + try container.encode(gradient.settings, forKey: "settings") + case let .image(representations, settings): + try container.encode(2 as Int32, forKey: "v") + try container.encode(representations.map { item in + return PostboxEncoder().encodeObjectToRawData(item) + }, forKey: "i") + try container.encode(settings, forKey: "settings") + case let .file(file): + try container.encode(3 as Int32, forKey: "v") + try container.encode(file.id, forKey: "id") + try container.encode(file.accessHash, forKey: "accessHash") + try container.encode((file.isCreator ? 1 : 0) as Int32, forKey: "isCreator") + try container.encode((file.isDefault ? 1 : 0) as Int32, forKey: "isDefault") + try container.encode((file.isPattern ? 1 : 0) as Int32, forKey: "isPattern") + try container.encode((file.isDark ? 1 : 0) as Int32, forKey: "isDark") + try container.encode(file.slug, forKey: "slug") + try container.encode(PostboxEncoder().encodeObjectToRawData(file.file), forKey: "file") + try container.encode(file.settings, forKey: "settings") + case let .emoticon(emoticon): + try container.encode(5 as Int32, forKey: "v") + try container.encode(emoticon, forKey: "e") } } } public enum TelegramWallpaper: Equatable { + public static func emoticonWallpaper(emoticon: String) -> TelegramWallpaper { + return .file(File(id: -1, accessHash: -1, isCreator: false, isDefault: false, isPattern: false, isDark: false, slug: "", file: TelegramMediaFile(fileId: MediaId(namespace: 0, id: 0), partialReference: nil, resource: EmptyMediaResource(), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "", size: nil, attributes: []), settings: WallpaperSettings(emoticon: emoticon))) + } + public struct Gradient: Equatable { public var id: Int64? public var colors: [UInt32] @@ -228,6 +237,7 @@ public enum TelegramWallpaper: Equatable { case gradient(Gradient) case image([TelegramMediaImageRepresentation], WallpaperSettings) case file(File) + case emoticon(String) public var hasWallpaper: Bool { switch self { @@ -270,6 +280,12 @@ public enum TelegramWallpaper: Equatable { } else { return false } + case let .emoticon(emoticon): + if case .emoticon(emoticon) = rhs { + return true + } else { + return false + } } } @@ -305,6 +321,12 @@ public enum TelegramWallpaper: Equatable { } else { return false } + case let .emoticon(emoticon): + if case .emoticon(emoticon) = wallpaper { + return true + } else { + return false + } } } @@ -335,6 +357,8 @@ public enum TelegramWallpaper: Equatable { case var .file(file): file.settings = settings return .file(file) + case .emoticon: + return self } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index be6cbd94fb..698db7547d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -134,6 +134,8 @@ func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in if let current = current as? CachedUserData { return current.withUpdatedWallpaper(wallpaper) + } else if let current = current as? CachedChannelData { + return current.withUpdatedWallpaper(wallpaper) } else { return current } diff --git a/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift b/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift index 884cae8f3a..8cee0d4f08 100644 --- a/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift +++ b/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift @@ -34,7 +34,7 @@ public func chatControllerBackgroundImage(theme: PresentationTheme?, wallpaper i } else { var succeed = true switch wallpaper { - case .builtin: + case .builtin, .emoticon: if let filePath = getAppBundle().path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg") { backgroundImage = UIImage(contentsOfFile: filePath)?.precomposed() } @@ -114,7 +114,7 @@ public func chatControllerBackgroundImageSignal(wallpaper: TelegramWallpaper, me } switch wallpaper { - case .builtin: + case .builtin, .emoticon: if let filePath = getAppBundle().path(forResource: "ChatWallpaperBuiltin0", ofType: "jpg") { return .single((UIImage(contentsOfFile: filePath)?.precomposed(), true)) |> afterNext { image in diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift index bcbb763eee..11f2f15c3b 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationData.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift @@ -624,6 +624,8 @@ public func serviceColor(for wallpaper: (TelegramWallpaper, UIImage?)) -> UIColo } else { return UIColor(rgb: 0x000000, alpha: 0.3) } + case .emoticon: + return UIColor(rgb: 0x000000, alpha: 0.3) } } @@ -708,6 +710,8 @@ public func chatServiceBackgroundColor(wallpaper: TelegramWallpaper, mediaBox: M serviceBackgroundColorForWallpaper = (wallpaper, color) } } + case .emoticon: + return .single(UIColor(rgb: 0x000000, alpha: 0.3)) } } } diff --git a/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift b/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift index e077993a22..3a9c6ba5d6 100644 --- a/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift +++ b/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift @@ -7,6 +7,8 @@ public extension TelegramWallpaper { switch self { case .image: return false + case .emoticon: + return false case let .file(file): if self.isPattern, file.settings.colors.count == 1 && (file.settings.colors[0] == 0xffffff || file.settings.colors[0] == 0xffffffff) { return true diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift index a7ddc0bbb6..d82d93c7b1 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/CodableDrawingEntity.swift @@ -123,7 +123,7 @@ public enum CodableDrawingEntity: Equatable { reaction: reaction, flags: flags ) - } else if case let .message(messageIds, _) = entity.content, let messageId = messageIds.first { + } else if case let .message(messageIds, _, _) = entity.content, let messageId = messageIds.first { return .channelMessage(coordinates: coordinates, messageId: messageId) } else { return nil diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift index 35ccd2a079..9ff189c911 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/Drawing/DrawingStickerEntity.swift @@ -33,7 +33,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { case animatedImage(Data, UIImage) case video(TelegramMediaFile) case dualVideoReference(Bool) - case message([MessageId], CGSize) + case message([MessageId], TelegramMediaFile?, CGSize) public static func == (lhs: Content, rhs: Content) -> Bool { switch lhs { @@ -67,8 +67,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { } else { return false } - case let .message(messageIds, size): - if case .message(messageIds, size) = rhs { + case let .message(messageIds, innerFile, size): + if case .message(messageIds, innerFile, size) = rhs { return true } else { return false @@ -108,6 +108,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { didSet { if case let .file(_, type) = self.content, case .reaction = type { self.scale = max(0.59, min(1.77, self.scale)) + } else if case .message = self.content { + self.scale = max(1.0, min(4.0, self.scale)) } } } @@ -144,7 +146,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0) case .dualVideoReference: dimensions = CGSize(width: 512.0, height: 512.0) - case let .message(_, size): + case let .message(_, _, size): dimensions = size } @@ -174,7 +176,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { case .dualVideoReference: return true case .message: - return false + return !(self.renderSubEntities ?? []).isEmpty } } @@ -216,7 +218,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { self.uuid = try container.decode(UUID.self, forKey: .uuid) if let messageIds = try container.decodeIfPresent([MessageId].self, forKey: .messageIds) { let size = try container.decodeIfPresent(CGSize.self, forKey: .explicitSize) ?? .zero - self.content = .message(messageIds, size) + self.content = .message(messageIds, nil, size) } else if let _ = try container.decodeIfPresent(Bool.self, forKey: .dualVideo) { let isAdditional = try container.decodeIfPresent(Bool.self, forKey: .isAdditionalVideo) ?? false self.content = .dualVideoReference(isAdditional) @@ -311,8 +313,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable { case let .dualVideoReference(isAdditional): try container.encode(true, forKey: .dualVideo) try container.encode(isAdditional, forKey: .isAdditionalVideo) - case let .message(messageIds, size): + case let .message(messageIds, innerFile, size): try container.encode(messageIds, forKey: .messageIds) + let _ = innerFile try container.encode(size, forKey: .explicitSize) } try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift index fc1418cb77..cd3b903b46 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift @@ -34,17 +34,17 @@ public final class DrawingWallpaperRenderer { self.darkWallpaperBackgroundNode.update(wallpaper: darkWallpaper, animated: false) } - public func render(completion: @escaping (CGSize, UIImage?, UIImage?) -> Void) { + public func render(completion: @escaping (CGSize, UIImage?, UIImage?, CGRect?) -> Void) { self.updateLayout(size: CGSize(width: 360.0, height: 640.0)) let resultSize = CGSize(width: 1080, height: 1920) self.generate(view: self.wallpaperBackgroundNode.view) { dayImage in if self.customWallpaper != nil { - completion(resultSize, dayImage, nil) + completion(resultSize, dayImage, nil, nil) } else { Queue.mainQueue().justDispatch { self.generate(view: self.darkWallpaperBackgroundNode.view) { nightImage in - completion(resultSize, dayImage, nightImage) + completion(resultSize, dayImage, nightImage, nil) } } } diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift index 16c325eb82..9251a6a872 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorUtils.swift @@ -140,7 +140,7 @@ public func getChatWallpaperImage(context: AccountContext, messageId: EngineMess return Signal { subscriber in Queue.mainQueue().async { let wallpaperRenderer = DrawingWallpaperRenderer(context: context, customWallpaper: customWallpaper) - wallpaperRenderer.render { size, image, darkImage in + wallpaperRenderer.render { size, image, darkImage, mediaRect in subscriber.putNext((size, image, darkImage)) subscriber.putCompletion() } diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index b3da601579..17a41f61e8 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -2427,9 +2427,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } } + let maybeFile = messages.first?.media.first(where: { $0 is TelegramMediaFile }) as? TelegramMediaFile + let renderer = DrawingMessageRenderer(context: self.context, messages: messages) renderer.render(completion: { size, dayImage, nightImage in - let messageEntity = DrawingStickerEntity(content: .message(messageIds, size)) + let messageEntity = DrawingStickerEntity(content: .message(messageIds, maybeFile?.isVideo == true ? maybeFile : nil, size)) messageEntity.renderImage = dayImage messageEntity.secondaryRenderImage = nightImage messageEntity.referenceDrawingSize = storyDimensions diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index 0ba287783d..af67f19b8b 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -247,6 +247,7 @@ final class ChannelAppearanceScreenComponent: Component { private var updatedPeerProfileEmoji: Int64?? private var updatedPeerStatus: PeerEmojiStatus?? private var updatedPeerWallpaper: WallpaperSelectionResult? + private var temporaryPeerWallpaper: TelegramWallpaper? private var requiredBoostSubject: BoostSubject? @@ -396,14 +397,10 @@ final class ChannelAppearanceScreenComponent: Component { wallpaper = nil case let .emoticon(emoticon): wallpaper = contentsData.availableThemes.first(where: { $0.emoticon == emoticon })?.settings?.first?.wallpaper - case let .custom(wallpaperEntry, options, editedImage, cropRect, brightness): - let _ = wallpaperEntry - let _ = options - let _ = editedImage - let _ = cropRect - let _ = brightness - wallpaper = nil + case .custom: + wallpaper = self.temporaryPeerWallpaper } + hasChanges = true } else { wallpaper = contentsData.wallpaper } @@ -427,7 +424,7 @@ final class ChannelAppearanceScreenComponent: Component { return } - let requiredLevel = requiredBoostSubject.requiredLevel(premiumConfiguration) + let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration) if let boostLevel = self.boostLevel, requiredLevel > boostLevel { self.displayBoostLevels(subject: requiredBoostSubject) return @@ -449,6 +446,17 @@ final class ChannelAppearanceScreenComponent: Component { case generic } + if let updatedPeerWallpaper { + switch updatedPeerWallpaper { + case .remove: + let _ = component.context.engine.themes.setChatWallpaper(peerId: component.peerId, wallpaper: nil, forBoth: false).start() + case let .emoticon(emoticon): + let _ = component.context.engine.themes.setChatWallpaper(peerId: component.peerId, wallpaper: .emoticon(emoticon), forBoth: false).start() + case let .custom(wallpaperEntry, options, editedImage, cropRect, brightness): + uploadCustomPeerWallpaper(context: component.context, wallpaper: wallpaperEntry, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: component.peerId, forBoth: false, completion: {}) + } + } + self.applyDisposable = (combineLatest([ component.context.engine.peers.updatePeerNameColorAndEmoji(peerId: component.peerId, nameColor: resolvedState.nameColor, backgroundEmojiId: resolvedState.replyFileId, profileColor: resolvedState.profileColor, profileBackgroundEmojiId: resolvedState.backgroundFileId) |> ignoreValues @@ -526,8 +534,8 @@ final class ChannelAppearanceScreenComponent: Component { return } let level = boostStatus.level - let requiredWallpaperLevel = Int(BoostSubject.wallpaper.requiredLevel(premiumConfiguration)) - let requiredCustomWallpaperLevel = Int(BoostSubject.customWallpaper.requiredLevel(premiumConfiguration)) + let requiredWallpaperLevel = Int(BoostSubject.wallpaper.requiredLevel(context: component.context, configuration: premiumConfiguration)) + let requiredCustomWallpaperLevel = Int(BoostSubject.customWallpaper.requiredLevel(context: component.context, configuration: premiumConfiguration)) let selectedWallpaper = resolvedState.wallpaper @@ -536,7 +544,7 @@ final class ChannelAppearanceScreenComponent: Component { mode: .peer(peer, contentsData.availableThemes, selectedWallpaper, level < requiredWallpaperLevel ? requiredWallpaperLevel : nil, level < requiredCustomWallpaperLevel ? requiredCustomWallpaperLevel : nil) ) controller.completion = { [weak self] result in - guard let self, let contentsData = self.contentsData else { + guard let self, let component = self.component, let contentsData = self.contentsData else { return } self.updatedPeerWallpaper = result @@ -545,12 +553,19 @@ final class ChannelAppearanceScreenComponent: Component { if let selectedTheme = contentsData.availableThemes.first(where: { $0.emoticon == emoticon }) { self.currentTheme = .cloud(PresentationCloudTheme(theme: selectedTheme, resolvedWallpaper: nil, creatorAccountId: nil)) } + self.temporaryPeerWallpaper = nil case .remove: - self.currentTheme = .builtin(.dayClassic) - case .custom: - self.currentTheme = .builtin(.dayClassic) + self.currentTheme = nil + self.temporaryPeerWallpaper = nil + case let .custom(wallpaperEntry, options, editedImage, cropRect, brightness): + let _ = (getTemporaryCustomPeerWallpaper(context: component.context, wallpaper: wallpaperEntry, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness) + |> deliverOnMainQueue).startStandalone(next: { [weak self] wallpaper in + self?.temporaryPeerWallpaper = wallpaper + self?.state?.updated(transition: .immediate) + }) + self.currentTheme = nil } - self.state?.updated(transition: .spring(duration: 0.4)) + self.state?.updated(transition: .immediate) } self.environment?.controller()?.push(controller) } @@ -716,7 +731,7 @@ final class ChannelAppearanceScreenComponent: Component { return availableSize } - var requiredBoostSubject: BoostSubject = .nameColors + var requiredBoostSubjects: [BoostSubject] = [.nameColors(colors: resolvedState.nameColor)] let replyIconLevel = 5 var profileIconLevel = 7 @@ -734,35 +749,28 @@ final class ChannelAppearanceScreenComponent: Component { let replyFileId = resolvedState.replyFileId if replyFileId != nil { - requiredBoostSubject = .nameIcon + requiredBoostSubjects.append(.nameIcon) } let profileColor = resolvedState.profileColor if profileColor != nil { - requiredBoostSubject = .profileColors + requiredBoostSubjects.append(.profileColors) } let backgroundFileId = resolvedState.backgroundFileId if backgroundFileId != nil { - requiredBoostSubject = .profileIcon + requiredBoostSubjects.append(.profileIcon) } let emojiStatus = resolvedState.emojiStatus if emojiStatus != nil { - requiredBoostSubject = .emojiStatus + requiredBoostSubjects.append(.emojiStatus) } let statusFileId = emojiStatus?.fileId let cloudThemes: [PresentationThemeReference] = contentsData.availableThemes.map { .cloud(PresentationCloudTheme(theme: $0, resolvedWallpaper: nil, creatorAccountId: $0.isCreator ? component.context.account.id : nil)) } - var chatThemes = cloudThemes.filter { $0.emoticon != nil } - chatThemes.insert(.builtin(.dayClassic), at: 0) - - if !chatThemes.isEmpty { - if self.currentTheme == nil { - self.currentTheme = chatThemes[0] - } - } - + let chatThemes = cloudThemes.filter { $0.emoticon != nil } + if let currentTheme = self.currentTheme, (self.resolvedCurrentTheme?.reference != currentTheme || self.resolvedCurrentTheme?.isDark != environment.theme.overallDarkAppearance), (self.resolvingCurrentTheme?.reference != currentTheme || self.resolvingCurrentTheme?.isDark != environment.theme.overallDarkAppearance) { self.resolvingCurrentTheme?.disposable.dispose() @@ -780,7 +788,9 @@ final class ChannelAppearanceScreenComponent: Component { } if let presentationTheme { let resolvedWallpaper: Signal - if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { + if let temporaryPeerWallpaper = self.temporaryPeerWallpaper { + resolvedWallpaper = .single(temporaryPeerWallpaper) + } else if case let .file(file) = presentationTheme.chat.defaultWallpaper, file.id == 0 { resolvedWallpaper = cachedWallpaper(account: component.context.account, slug: file.slug, settings: file.settings) |> map { wallpaper -> TelegramWallpaper? in return wallpaper?.wallpaper @@ -799,13 +809,17 @@ final class ChannelAppearanceScreenComponent: Component { } })) } + } else if self.currentTheme == nil { + self.resolvedCurrentTheme = nil + self.resolvingCurrentTheme?.disposable.dispose() + self.resolvingCurrentTheme = nil } - if resolvedState.wallpaper != nil { - if self.currentTheme != chatThemes.first { - requiredBoostSubject = .wallpaper + if let wallpaper = resolvedState.wallpaper { + if wallpaper.isPattern { + requiredBoostSubjects.append(.wallpaper) } else { - requiredBoostSubject = .customWallpaper + requiredBoostSubjects.append(.customWallpaper) } } @@ -827,6 +841,12 @@ final class ChannelAppearanceScreenComponent: Component { ) } + let requiredBoostSubject: BoostSubject + if let maxBoostSubject = requiredBoostSubjects.max(by: { $0.requiredLevel(context: component.context, configuration: premiumConfiguration) < $1.requiredLevel(context: component.context, configuration: premiumConfiguration) }) { + requiredBoostSubject = maxBoostSubject + } else { + requiredBoostSubject = .nameColors(colors: resolvedState.nameColor) + } self.requiredBoostSubject = requiredBoostSubject let topInset: CGFloat = 24.0 @@ -874,7 +894,9 @@ final class ChannelAppearanceScreenComponent: Component { var chatPreviewTheme: PresentationTheme = environment.theme var chatPreviewWallpaper: TelegramWallpaper = presentationData.chatWallpaper - if let resolvedCurrentTheme = self.resolvedCurrentTheme { + if let temporaryPeerWallpaper = self.temporaryPeerWallpaper { + chatPreviewWallpaper = temporaryPeerWallpaper + } else if let resolvedCurrentTheme = self.resolvedCurrentTheme { chatPreviewTheme = resolvedCurrentTheme.theme if let wallpaper = resolvedCurrentTheme.wallpaper { chatPreviewWallpaper = wallpaper @@ -961,7 +983,7 @@ final class ChannelAppearanceScreenComponent: Component { contentHeight += sectionSpacing - if !chatThemes.isEmpty, let currentTheme { + if !chatThemes.isEmpty { var wallpaperLogoContents: [AnyComponentWithIdentity] = [] wallpaperLogoContents.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( @@ -978,6 +1000,14 @@ final class ChannelAppearanceScreenComponent: Component { )))) } + var currentTheme = self.currentTheme + var selectedWallpaper: TelegramWallpaper? + if currentTheme == nil, let wallpaper = resolvedState.wallpaper, !wallpaper.isPattern { + let theme: PresentationThemeReference = .builtin(.day) + currentTheme = theme + selectedWallpaper = wallpaper + } + let wallpaperSectionSize = self.wallpaperSection.update( transition: transition, component: AnyComponent(ListSectionComponent( @@ -999,22 +1029,24 @@ final class ChannelAppearanceScreenComponent: Component { strings: environment.strings, sectionId: 0, themes: chatThemes, - firstIsNone: true, + hasNoTheme: true, animatedEmojiStickers: component.context.animatedEmojiStickers, themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], nightMode: environment.theme.overallDarkAppearance, channelMode: true, + selectedWallpaper: selectedWallpaper, currentTheme: currentTheme, updatedTheme: { [weak self] value in guard let self else { return } self.currentTheme = value - if case .builtin = value { - self.updatedPeerWallpaper = .remove - } else { + self.temporaryPeerWallpaper = nil + if let value { self.updatedPeerWallpaper = .emoticon(value.emoticon ?? "") + } else { + self.updatedPeerWallpaper = .remove } self.state?.updated(transition: .spring(duration: 0.4)) }, @@ -1281,7 +1313,7 @@ final class ChannelAppearanceScreenComponent: Component { Text(text: "Apply Changes", font: Font.semibold(17.0), color: environment.theme.list.itemCheckColors.foregroundColor) ))) - let requiredLevel = requiredBoostSubject.requiredLevel(premiumConfiguration) + let requiredLevel = requiredBoostSubject.requiredLevel(context: component.context, configuration: premiumConfiguration) if let boostLevel = self.boostLevel, requiredLevel > boostLevel { buttonContents.append(AnyComponentWithIdentity(id: AnyHashable(1 as Int), component: AnyComponent(PremiumLockButtonSubtitleComponent( count: Int(requiredLevel), diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift index 8d10567b47..2c7cba50d8 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorItem.swift @@ -416,7 +416,7 @@ private func ensureColorVisible(listNode: ListView, color: PeerNameColor, animat } } if let resultNode = resultNode { - listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 24.0) + listNode.ensureItemNodeVisible(resultNode, animated: animated, overflow: 76.0) return true } else { return false diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift index e5f66fbd3c..798bcdc873 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/PeerNameColorScreen.swift @@ -891,7 +891,7 @@ public func PeerNameColorScreen( } let link = status.url - let controller = PremiumLimitScreen(context: context, subject: .storiesChannelBoost(peer: peer, boostSubject: .nameColors, isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), action: { + let controller = PremiumLimitScreen(context: context, subject: .storiesChannelBoost(peer: peer, boostSubject: .nameColors(colors: .blue), isCurrent: true, level: Int32(status.level), currentLevelBoosts: Int32(status.currentLevelBoosts), nextLevelBoosts: status.nextLevelBoosts.flatMap(Int32.init), link: link, myBoostCount: 0, canBoostAgain: false), count: Int32(status.boosts), action: { UIPasteboard.general.string = link let presentationData = context.sharedContext.currentPresentationData.with { $0 } presentImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.ChannelBoost_BoostLinkCopied), elevatedLayout: false, position: .bottom, animateInAsReplacement: false, action: { _ in return false })) diff --git a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift index 31beb5d74b..74b0f895a3 100644 --- a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift +++ b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift @@ -327,7 +327,7 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { } } else if let wallpaper = self.wallpaper { switch wallpaper { - case .builtin, .color, .gradient: + case .builtin, .color, .gradient, .emoticon: let apply = self.imageNode.asyncLayout()(TransformImageArguments(corners: corners, imageSize: CGSize(), boundingSize: size, intrinsicInsets: UIEdgeInsets())) apply() case let .image(representations, _): diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift index a0f2bb1b76..e1028d3402 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift @@ -865,7 +865,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate }, activateChatPreview: { _, _, _, gesture, _ in gesture?.cancel() }, present: { _ in - }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openActiveSessions: { + }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openPremiumGift: {}, openActiveSessions: { }, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: {}, hideChatFolderUpdates: { }, openStories: { _, _ in diff --git a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift index f32f429e33..95ebbd920b 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift @@ -24,10 +24,9 @@ import HexColor private struct ThemeCarouselThemeEntry: Comparable, Identifiable { let index: Int let emojiFile: TelegramMediaFile? - let themeReference: PresentationThemeReference + let themeReference: PresentationThemeReference? let nightMode: Bool let channelMode: Bool - let firstIsNone: Bool let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] let themeSpecificChatWallpapers: [Int64: TelegramWallpaper] var selected: Bool @@ -46,7 +45,7 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable { if lhs.emojiFile?.fileId != rhs.emojiFile?.fileId { return false } - if lhs.themeReference.index != rhs.themeReference.index { + if lhs.themeReference?.index != rhs.themeReference?.index { return false } if lhs.nightMode != rhs.nightMode { @@ -55,9 +54,6 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable { if lhs.channelMode != rhs.channelMode { return false } - if lhs.firstIsNone != rhs.firstIsNone { - return false - } if lhs.themeSpecificAccentColors != rhs.themeSpecificAccentColors { return false } @@ -83,12 +79,8 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable { return lhs.index < rhs.index } - func item(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem { - var isNone = false - if self.index == 0 && self.firstIsNone { - isNone = true - } - return ThemeCarouselThemeIconItem(context: context, emojiFile: self.emojiFile, themeReference: self.themeReference, nightMode: self.nightMode, channelMode: self.channelMode, isNone: isNone, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action, contextAction: contextAction) + func item(context: AccountContext, action: @escaping (PresentationThemeReference?) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) -> ListViewItem { + return ThemeCarouselThemeIconItem(context: context, emojiFile: self.emojiFile, themeReference: self.themeReference, nightMode: self.nightMode, channelMode: self.channelMode, themeSpecificAccentColors: self.themeSpecificAccentColors, themeSpecificChatWallpapers: self.themeSpecificChatWallpapers, selected: self.selected, theme: self.theme, strings: self.strings, wallpaper: self.wallpaper, action: action, contextAction: contextAction) } } @@ -96,8 +88,7 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable { public class ThemeCarouselThemeIconItem: ListViewItem { public let context: AccountContext public let emojiFile: TelegramMediaFile? - public let themeReference: PresentationThemeReference - public let isNone: Bool + public let themeReference: PresentationThemeReference? public let nightMode: Bool public let channelMode: Bool public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] @@ -106,16 +97,15 @@ public class ThemeCarouselThemeIconItem: ListViewItem { public let theme: PresentationTheme public let strings: PresentationStrings public let wallpaper: TelegramWallpaper? - public let action: (PresentationThemeReference) -> Void + public let action: (PresentationThemeReference?) -> Void public let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)? - public init(context: AccountContext, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference, nightMode: Bool, channelMode: Bool, isNone: Bool = false, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) { + public init(context: AccountContext, emojiFile: TelegramMediaFile?, themeReference: PresentationThemeReference?, nightMode: Bool, channelMode: Bool, themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], selected: Bool, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper?, action: @escaping (PresentationThemeReference?) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?) { self.context = context self.emojiFile = emojiFile self.themeReference = themeReference self.nightMode = nightMode self.channelMode = channelMode - self.isNone = isNone self.themeSpecificAccentColors = themeSpecificAccentColors self.themeSpecificChatWallpapers = themeSpecificChatWallpapers self.selected = selected @@ -319,14 +309,10 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode { } override func selected() { - guard let item = self.item else { - return - } - - let wasSelected = item.selected + let wasSelected = self.item?.selected ?? false super.selected() - if let animatedStickerNode = self.animatedStickerNode, !item.isNone { + if let animatedStickerNode = self.animatedStickerNode { Queue.mainQueue().after(0.1) { if !wasSelected { animatedStickerNode.seekTo(.frameIndex(0)) @@ -383,15 +369,18 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode { updatedSelected = true } + let text = NSAttributedString(string: item.strings.Wallpaper_NoWallpaper, font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor) + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) var string: String? - if let _ = item.themeReference.emoticon { + if item.themeReference == nil { + string = "❌" + self?.imageNode.backgroundColor = item.theme.list.mediaPlaceholderColor + } else if let _ = item.themeReference?.emoticon { } else { - string = "🎨" + string = item.channelMode ? "" : "🎨" } - let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Wallpaper_NoWallpaper, font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - let emojiTitle = NSAttributedString(string: string ?? "", font: Font.regular(20.0), textColor: .black) let (_, emojiApply) = makeEmojiLayout(TextNodeLayoutArguments(attributedString: emojiTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) @@ -401,20 +390,18 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode { strongSelf.item = item if updatedThemeReference || updatedWallpaper || updatedNightMode || updatedChannelMode { - var themeReference = item.themeReference - if case .builtin = themeReference, item.nightMode { - themeReference = .builtin(.night) - } - - let color = item.themeSpecificAccentColors[themeReference.index] - let wallpaper = item.themeSpecificChatWallpapers[themeReference.index] - - if case .builtin = themeReference, item.isNone { - strongSelf.imageNode.reset() - strongSelf.imageNode.backgroundColor = item.theme.list.mediaPlaceholderColor //item.theme.list.disclosureArrowColor.withAlphaComponent(0.2) - } else { + if var themeReference = item.themeReference { + if case .builtin = themeReference, item.nightMode { + themeReference = .builtin(.night) + } + + let color = item.themeSpecificAccentColors[themeReference.index] + let wallpaper = item.themeSpecificChatWallpapers[themeReference.index] + strongSelf.imageNode.setSignal(themeIconImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, theme: themeReference, color: color, wallpaper: wallpaper ?? item.wallpaper, nightMode: item.nightMode, channelMode: item.channelMode, emoticon: true)) strongSelf.imageNode.backgroundColor = nil + } else { + } } @@ -435,14 +422,8 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode { strongSelf.emojiContainerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0) strongSelf.emojiContainerNode.frame = CGRect(origin: CGPoint(x: 15.0, y: -15.0), size: CGSize(width: 90.0, height: 120.0)) - if item.isNone { - let _ = textApply() - } let _ = emojiApply() - - let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((itemLayout.size.height - textLayout.size.width) / 2.0), y: floorToScreenPixels((itemLayout.size.width - textLayout.size.height) / 2.0) - 16.0), size: textLayout.size) - strongSelf.textNode.frame = textFrame - + let imageSize = CGSize(width: 82.0, height: 108.0) strongSelf.imageNode.frame = CGRect(origin: CGPoint(x: 4.0, y: 6.0), size: imageSize) let applyLayout = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: .clear)) @@ -452,6 +433,10 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode { strongSelf.emojiNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 78.0), size: CGSize(width: 90.0, height: 30.0)) strongSelf.emojiNode.isHidden = string == nil + let _ = textApply() + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((90.0 - textLayout.size.width) / 2.0), y: 24.0), size: textLayout.size) + strongSelf.textNode.isHidden = item.themeReference != nil + let emojiFrame = CGRect(origin: CGPoint(x: 33.0, y: 79.0), size: CGSize(width: 24.0, height: 24.0)) if let file = item.emojiFile, currentItem?.emojiFile == nil { let imageApply = strongSelf.emojiImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: emojiFrame.size, boundingSize: emojiFrame.size, intrinsicInsets: UIEdgeInsets())) @@ -490,7 +475,7 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode { } let presentationData = item.context.sharedContext.currentPresentationData.with { $0 } - strongSelf.activateAreaNode.accessibilityLabel = item.themeReference.emoticon.flatMap { presentationData.strings.Appearance_VoiceOver_Theme($0).string } + strongSelf.activateAreaNode.accessibilityLabel = item.themeReference?.emoticon.flatMap { presentationData.strings.Appearance_VoiceOver_Theme($0).string } if item.selected { strongSelf.activateAreaNode.accessibilityTraits = [.button, .selected] } else { @@ -553,28 +538,30 @@ public class ThemeCarouselThemeItem: ListViewItem, ItemListItem, ListItemCompone public let theme: PresentationTheme public let strings: PresentationStrings public let themes: [PresentationThemeReference] - public let firstIsNone : Bool + public let hasNoTheme: Bool public let animatedEmojiStickers: [String: [StickerPackItem]] public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] public let themeSpecificChatWallpapers: [Int64: TelegramWallpaper] public let nightMode: Bool public let channelMode: Bool - public let currentTheme: PresentationThemeReference - public let updatedTheme: (PresentationThemeReference) -> Void + public let selectedWallpaper: TelegramWallpaper? + public let currentTheme: PresentationThemeReference? + public let updatedTheme: (PresentationThemeReference?) -> Void public let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)? public let tag: ItemListItemTag? - public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], firstIsNone: Bool = false, animatedEmojiStickers: [String: [StickerPackItem]], themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], nightMode: Bool, channelMode: Bool = false, currentTheme: PresentationThemeReference, updatedTheme: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, tag: ItemListItemTag? = nil) { + public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], hasNoTheme: Bool, animatedEmojiStickers: [String: [StickerPackItem]], themeSpecificAccentColors: [Int64: PresentationThemeAccentColor], themeSpecificChatWallpapers: [Int64: TelegramWallpaper], nightMode: Bool, channelMode: Bool = false, selectedWallpaper: TelegramWallpaper? = nil, currentTheme: PresentationThemeReference?, updatedTheme: @escaping (PresentationThemeReference?) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, tag: ItemListItemTag? = nil) { self.context = context self.theme = theme self.strings = strings self.themes = themes - self.firstIsNone = firstIsNone + self.hasNoTheme = hasNoTheme self.animatedEmojiStickers = animatedEmojiStickers self.themeSpecificAccentColors = themeSpecificAccentColors self.themeSpecificChatWallpapers = themeSpecificChatWallpapers self.nightMode = nightMode self.channelMode = channelMode + self.selectedWallpaper = selectedWallpaper self.currentTheme = currentTheme self.updatedTheme = updatedTheme self.contextAction = contextAction @@ -664,7 +651,7 @@ private struct ThemeCarouselThemeItemNodeTransition { let updatePosition: Bool } -private func preparedTransition(context: AccountContext, action: @escaping (PresentationThemeReference) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, from fromEntries: [ThemeCarouselThemeEntry], to toEntries: [ThemeCarouselThemeEntry], crossfade: Bool, updatePosition: Bool) -> ThemeCarouselThemeItemNodeTransition { +private func preparedTransition(context: AccountContext, action: @escaping (PresentationThemeReference?) -> Void, contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)?, from fromEntries: [ThemeCarouselThemeEntry], to toEntries: [ThemeCarouselThemeEntry], crossfade: Bool, updatePosition: Bool) -> ThemeCarouselThemeItemNodeTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } @@ -678,7 +665,7 @@ private func ensureThemeVisible(listNode: ListView, themeReference: Presentation var resultNode: ThemeCarouselThemeItemIconNode? listNode.forEachItemNode { node in if resultNode == nil, let node = node as? ThemeCarouselThemeItemIconNode { - if node.item?.themeReference.index == themeReference.index { + if node.item?.themeReference?.index == themeReference.index { resultNode = node } } @@ -766,7 +753,7 @@ public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode { var scrollToItem: ListViewScrollToItem? if !self.initialized || !self.tapping { if let index = transition.entries.firstIndex(where: { entry in - return entry.themeReference.index == item.currentTheme.index + return entry.themeReference?.index == item.currentTheme?.index }) { scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down) self.initialized = true @@ -866,31 +853,35 @@ public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode { var index: Int = 0 var hasCurrentTheme = false - for theme in item.themes { - let selected = item.currentTheme.index == theme.index + if item.hasNoTheme { + let selected = item.currentTheme == nil if selected { hasCurrentTheme = true } - - let emojiFile: TelegramMediaFile? - if item.firstIsNone && index == 0 { - emojiFile = item.animatedEmojiStickers["❌"]?.first?.file - } else { - emojiFile = theme.emoticon.flatMap { item.animatedEmojiStickers[$0]?.first?.file } + entries.append(ThemeCarouselThemeEntry(index: index, emojiFile: nil, themeReference: nil, nightMode: item.nightMode, channelMode: item.channelMode, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: selected, theme: item.theme, strings: item.strings, wallpaper: nil)) + index += 1 + } + for theme in item.themes { + let selected = item.currentTheme?.index == theme.index + if selected { + hasCurrentTheme = true } - entries.append(ThemeCarouselThemeEntry(index: index, emojiFile: emojiFile, themeReference: theme, nightMode: item.nightMode, channelMode: item.channelMode, firstIsNone: item.firstIsNone, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: selected, theme: item.theme, strings: item.strings, wallpaper: nil)) + let emojiFile = theme.emoticon.flatMap { item.animatedEmojiStickers[$0]?.first?.file } + entries.append(ThemeCarouselThemeEntry(index: index, emojiFile: emojiFile, themeReference: theme, nightMode: item.nightMode, channelMode: item.channelMode, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: selected, theme: item.theme, strings: item.strings, wallpaper: nil)) index += 1 } if !hasCurrentTheme { - entries.append(ThemeCarouselThemeEntry(index: index, emojiFile: nil, themeReference: item.currentTheme, nightMode: false, channelMode: item.channelMode, firstIsNone: item.firstIsNone, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: true, theme: item.theme, strings: item.strings, wallpaper: nil)) + entries.insert(ThemeCarouselThemeEntry(index: index, emojiFile: nil, themeReference: item.currentTheme, nightMode: false, channelMode: item.channelMode, themeSpecificAccentColors: item.themeSpecificAccentColors, themeSpecificChatWallpapers: item.themeSpecificChatWallpapers, selected: true, theme: item.theme, strings: item.strings, wallpaper: item.hasNoTheme ? item.selectedWallpaper : nil), at: item.hasNoTheme ? 1 : entries.count) } - let action: (PresentationThemeReference) -> Void = { [weak self] themeReference in + let action: (PresentationThemeReference?) -> Void = { [weak self] themeReference in if let strongSelf = self { strongSelf.tapping = true strongSelf.item?.updatedTheme(themeReference) - let _ = ensureThemeVisible(listNode: strongSelf.listNode, themeReference: themeReference, animated: true) + if let themeReference { + let _ = ensureThemeVisible(listNode: strongSelf.listNode, themeReference: themeReference, animated: true) + } Queue.mainQueue().after(0.4) { strongSelf.tapping = false } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift index 85b35b03b3..5a5e9811e0 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift @@ -716,7 +716,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.initialWallpaper = wallpaper switch wallpaper { - case .builtin: + case .builtin, .emoticon: displaySize = CGSize(width: 1308.0, height: 2688.0).fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor contentSize = displaySize signal = settingsBuiltinWallpaperImage(account: self.context.account) @@ -1386,7 +1386,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { motionFrame = rightButtonFrame case let .wallpaper(wallpaper, _): switch wallpaper { - case .builtin: + case .builtin, .emoticon: motionAlpha = 1.0 motionFrame = centerButtonFrame case .color: @@ -1681,7 +1681,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode { if let _ = serviceMessageText, let messageNodes = self.messageNodes, let node = messageNodes.last { if let backgroundNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.first, let backdropNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.last?.subnodes?.last?.subnodes?.first { - backdropNode.isHidden = true + if !(backdropNode is TextNode) { + backdropNode.isHidden = true + } let serviceBackgroundFrame = backgroundNode.view.convert(backgroundNode.bounds, to: self.view).offsetBy(dx: 0.0, dy: -1.0).insetBy(dx: 0.0, dy: -1.0) transition.updateFrame(node: self.serviceBackgroundNode, frame: serviceBackgroundFrame) self.serviceBackgroundNode.update(size: serviceBackgroundFrame.size, cornerRadius: serviceBackgroundFrame.height / 2.0, transition: transition) diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift index efee407998..843d570a98 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift @@ -249,7 +249,9 @@ public final class ThemeGridController: ViewController { if let strongSelf = self { if case .peer = mode { strongSelf.completion(.custom(wallpaperEntry: wallpaper, options: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness)) - dismissControllers() + Queue.mainQueue().after(0.15) { + dismissControllers() + } } else { uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { dismissControllers() diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift index 8c424bac35..c65730aeb2 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift @@ -16,7 +16,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec if let image = cachedBorderImages[key] { return image } else { - let image = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in + let image = generateImage(CGSize(width: 20.0, height: 20.0), rotatedContext: { size, context in let bounds = CGRect(origin: CGPoint(), size: size) context.clear(bounds) @@ -26,7 +26,7 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec context.setLineWidth(lineWidth) context.setStrokeColor(theme.list.itemBlocksBackgroundColor.cgColor) - context.strokeEllipse(in: bounds.insetBy(dx: 3.0 + lineWidth / 2.0, dy: 3.0 + lineWidth / 2.0)) + context.strokeEllipse(in: bounds.insetBy(dx: 2.0 + lineWidth / 2.0, dy: 2.0 + lineWidth / 2.0)) var accentColor = theme.list.itemAccentColor if accentColor.rgb == 0xffffff { @@ -40,9 +40,9 @@ private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selec if bordered || selected { context.setLineWidth(lineWidth) - context.strokeEllipse(in: bounds.insetBy(dx: 1.0 + lineWidth / 2.0, dy: 1.0 + lineWidth / 2.0)) + context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0)) } - })?.stretchableImage(withLeftCapWidth: 9, topCapHeight: 9) + })?.stretchableImage(withLeftCapWidth: 10, topCapHeight: 10) cachedBorderImages[key] = image return image } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift index 5ca1aa7be6..edeaa31173 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift @@ -46,6 +46,7 @@ struct ThemeGridControllerEntry: Comparable, Identifiable { case gradient([UInt32]) case file(Int64, [UInt32], Int32) case image(String) + case emoticon(String) } var index: Int @@ -77,6 +78,8 @@ struct ThemeGridControllerEntry: Comparable, Identifiable { } else { return .image("") } + case let .emoticon(emoticon): + return .emoticon(emoticon) } } @@ -303,7 +306,7 @@ final class ThemeGridControllerNode: ASDisplayNode { self.gridNode.addSubnode(self.colorItemNode) } self.gridNode.addSubnode(self.galleryItemNode) - if case let .peer(_, _, wallpaper, _, _) = mode, wallpaper != nil { + if case let .peer(_, _, wallpaper, _, _) = mode, let wallpaper, !wallpaper.isPattern { self.gridNode.addSubnode(self.removeItemNode) } self.gridNode.addSubnode(self.descriptionItemNode) @@ -662,6 +665,9 @@ final class ThemeGridControllerNode: ASDisplayNode { self.galleryItem = ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/Image"), color: presentationData.theme.list.itemAccentColor), title: presentationData.strings.Wallpaper_SetCustomBackground, additionalBadgeIcon: requiredCustomWallpaperLevel.flatMap { generateDisclosureActionBoostLevelBadgeImage(text: "Level \($0)") }, alwaysPlain: false, hasSeparator: true, sectionId: 0, height: .generic, color: .accent, editing: false, action: { [weak self] in self?.presentGallery() }) + self.removeItem = ItemListPeerActionItem(presentationData: ItemListPresentationData(presentationData), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.list.itemDestructiveColor), title: presentationData.strings.Wallpaper_ChannelRemoveBackground, alwaysPlain: false, hasSeparator: true, sectionId: 0, height: .generic, color: .destructive, editing: false, action: { [weak self] in + self?.presentGallery() + }) } self.descriptionItem = ItemListTextItem(presentationData: ItemListPresentationData(presentationData), text: .plain(presentationData.strings.Wallpaper_SetCustomBackgroundInfo), sectionId: 0) @@ -751,6 +757,7 @@ final class ThemeGridControllerNode: ASDisplayNode { let makeColorLayout = self.colorItemNode.asyncLayout() let makeGalleryLayout = (self.galleryItemNode as? ItemListActionItemNode)?.asyncLayout() let makeGalleryIconLayout = (self.galleryItemNode as? ItemListPeerActionItemNode)?.asyncLayout() + let makeRemoveLayout = self.removeItemNode.asyncLayout() let makeDescriptionLayout = self.descriptionItemNode.asyncLayout() var listInsets = insets @@ -773,33 +780,43 @@ final class ThemeGridControllerNode: ASDisplayNode { } var isChannel = false - if case .peer = self.mode { + var hasCustomWallpaper = false + if case let .peer(_, _, wallpaper, _, _) = self.mode { isChannel = true + if let wallpaper, !wallpaper.isPattern { + hasCustomWallpaper = true + } } let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: listInsets.left, rightInset: listInsets.right, availableHeight: layout.size.height) let (colorLayout, colorApply) = makeColorLayout(self.colorItem, params, ItemListNeighbors(top: .none, bottom: .sameSection(alwaysPlain: false))) let (galleryLayout, galleryApply): (ListViewItemNodeLayout, (Bool) -> Void) if let makeGalleryIconLayout, let galleryItem = self.galleryItem as? ItemListPeerActionItem { - (galleryLayout, galleryApply) = makeGalleryIconLayout(galleryItem, params, ItemListNeighbors(top: isChannel ? .none : .sameSection(alwaysPlain: false), bottom: .sameSection(alwaysPlain: true))) + (galleryLayout, galleryApply) = makeGalleryIconLayout(galleryItem, params, ItemListNeighbors(top: isChannel ? .none : .sameSection(alwaysPlain: false), bottom: .sameSection(alwaysPlain: !hasCustomWallpaper))) } else if let makeGalleryLayout, let galleryItem = self.galleryItem as? ItemListActionItem { - (galleryLayout, galleryApply) = makeGalleryLayout(galleryItem, params, ItemListNeighbors(top: isChannel ? .none : .sameSection(alwaysPlain: false), bottom: .sameSection(alwaysPlain: true))) + (galleryLayout, galleryApply) = makeGalleryLayout(galleryItem, params, ItemListNeighbors(top: isChannel ? .none : .sameSection(alwaysPlain: false), bottom: .sameSection(alwaysPlain: false))) } else { fatalError() } + let (removeLayout, removeApply) = makeRemoveLayout(self.removeItem, params, ItemListNeighbors(top: .sameSection(alwaysPlain: false), bottom: .none)) let (descriptionLayout, descriptionApply) = makeDescriptionLayout(self.descriptionItem, params, ItemListNeighbors(top: .none, bottom: .none)) + colorApply(false) galleryApply(false) + removeApply(false) descriptionApply() let buttonTopInset: CGFloat = 32.0 let buttonHeight: CGFloat = 44.0 - let buttonBottomInset: CGFloat = descriptionLayout.contentSize.height + 17.0 + var buttonBottomInset: CGFloat = descriptionLayout.contentSize.height + 17.0 + if hasCustomWallpaper { + buttonBottomInset = 17.0 + } var buttonInset: CGFloat = buttonTopInset + buttonHeight + buttonBottomInset - if !isChannel { + if !isChannel || hasCustomWallpaper { buttonInset += buttonHeight } let buttonOffset = buttonInset + 10.0 @@ -815,7 +832,16 @@ final class ThemeGridControllerNode: ASDisplayNode { transition.updateFrame(node: self.galleryItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: originY), size: galleryLayout.contentSize)) originY += galleryLayout.contentSize.height - transition.updateFrame(node: self.descriptionItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: originY), size: descriptionLayout.contentSize)) + if hasCustomWallpaper { + self.descriptionItemNode.isHidden = true + self.removeItemNode.isHidden = false + + transition.updateFrame(node: self.removeItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: originY), size: removeLayout.contentSize)) + } else { + self.descriptionItemNode.isHidden = false + self.removeItemNode.isHidden = true + transition.updateFrame(node: self.descriptionItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: originY), size: descriptionLayout.contentSize)) + } self.leftOverlayNode.frame = CGRect(x: 0.0, y: -buttonOffset, width: listInsets.left, height: buttonTopInset + colorLayout.contentSize.height + galleryLayout.contentSize.height + 10000.0) self.rightOverlayNode.frame = CGRect(x: layout.size.width - listInsets.right, y: -buttonOffset, width: listInsets.right, height: buttonTopInset + colorLayout.contentSize.height + galleryLayout.contentSize.height + 10000.0) diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift index 563244c10e..fafe52e6e9 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift @@ -10,6 +10,7 @@ import TelegramUIPreferences import AccountContext import LegacyComponents import WallpaperGalleryScreen +import ImageBlur public func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?, completion: @escaping () -> Void) { var imageSignal: Signal @@ -164,6 +165,98 @@ public func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperG }).start() } +public func getTemporaryCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?) -> Signal { + var imageSignal: Signal + switch wallpaper { + case .wallpaper: + fatalError() + 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() + } + } + + if let editedImage { + imageSignal = .single(editedImage) + } + + return imageSignal + |> map { image -> TelegramWallpaper? 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) + + 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, synchronous: true) + context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true) + + let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() + + var intensity: Int32? + if let brightness { + intensity = max(0, min(100, Int32(brightness * 100.0))) + } + + let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: intensity) + 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) + + return temporaryWallpaper + } + return nil + } +} + public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?, peerId: PeerId, forBoth: Bool, completion: @escaping () -> Void) { var imageSignal: Signal switch wallpaper { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 9b54e69fb4..7d96e3fd13 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -6368,6 +6368,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var presentationData = presentationData var useDarkAppearance = presentationData.theme.overallDarkAppearance + if let wallpaper = chatWallpaper, case let .emoticon(wallpaperEmoticon) = wallpaper, let theme = chatThemes.first(where: { $0.emoticon?.strippedEmoji == wallpaperEmoticon.strippedEmoji }) { + if let themeWallpaper = theme.settings?.first?.wallpaper { + chatWallpaper = themeWallpaper + } + } if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon?.strippedEmoji == themeEmoticon.strippedEmoji }) { if let darkAppearancePreview = darkAppearancePreview { useDarkAppearance = darkAppearancePreview @@ -7400,17 +7405,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let peerId = self.chatLocation.peerId { self.chatThemeEmoticonPromise.set(self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ThemeEmoticon(id: peerId))) - let chatWallpaper = self.context.account.viewTracker.peerView(peerId) + let chatWallpaper = self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Wallpaper(id: peerId)) |> take(1) - |> map { view -> TelegramWallpaper? in - if let cachedData = view.cachedData as? CachedUserData { - return cachedData.wallpaper - } else if let cachedData = view.cachedData as? CachedChannelData { - return cachedData.wallpaper - } else { - return nil - } - } self.chatWallpaperPromise.set(chatWallpaper) } else { self.chatThemeEmoticonPromise.set(.single(nil)) diff --git a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift index a14f0dc79c..6039167ad2 100644 --- a/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchResultsContollerNode.swift @@ -266,6 +266,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe }, openStorageManagement: { }, openPasswordSetup: { }, openPremiumIntro: { + }, openPremiumGift: { }, openActiveSessions: { }, performActiveSessionAction: { _, _ in }, openChatFolderUpdates: { diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index d3a39df2eb..a07d619330 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -1452,7 +1452,7 @@ public func themeIconImage(account: Account, accountManager: AccountManager