diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 51ab8c6524..dc27f99853 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10694,7 +10694,7 @@ Sorry for the inconvenience."; "Premium.Gift.ApplyLink" = "Apply for Free"; "Premium.Gift.ApplyLink.AlreadyHasPremium.Title" = "You already have Telegram Premium"; -"Premium.Gift.ApplyLink.AlreadyHasPremium.Text" = "You can activate this gift link after %@ or [send the link]() to a friend."; +"Premium.Gift.ApplyLink.AlreadyHasPremium.Text" = "You can activate this gift link after **%@** or [send the link]() to a friend."; "Premium.Gift.Link.Text" = "This link allows you or [anyone you choose]() to activate a **Telegram Premium** subscription."; "Premium.Gift.UsedLink.Text" = "This link was used to activate a **Telegram Premium** subscription."; @@ -10742,17 +10742,17 @@ Sorry for the inconvenience."; "ChannelBoost.EmojiStatus" = "Use Emoji Statuses"; "ChannelBoost.EnableEmojiStatusLevelText" = "Your channel needs **Level %1$@** to use emoji statuses."; -"ChannelBoost.Wallpaper" = "Set Channel Background"; -"ChannelBoost.EnableWallpaperLevelText" = "Your channel needs **Level %1$@** to set channel background."; +"ChannelBoost.Wallpaper" = "Set Channel Wallpaper"; +"ChannelBoost.EnableWallpaperLevelText" = "Your channel needs **Level %1$@** to set channel wallpaper."; -"ChannelBoost.CustomWallpaper" = "Set Custom Channel Background"; -"ChannelBoost.EnableCustomWallpaperLevelText" = "Your channel needs **Level %1$@** to set custom channel background."; +"ChannelBoost.CustomWallpaper" = "Set Custom Channel Wallpaper"; +"ChannelBoost.EnableCustomWallpaperLevelText" = "Your channel needs **Level %1$@** to set custom channel wallpaper."; "WallpaperPreview.ChannelHeader" = "All subscribers will see this wallpaper"; "WallpaperPreview.ChannelTopText" = "Details to follow shortly.\nStay tuned!"; "WallpaperPreview.ChannelReplyText" = "Breaking News"; -"Wallpaper.ApplyForChannel" = "Apply Background"; +"Wallpaper.ApplyForChannel" = "Apply Wallpaper"; "Notification.ChannelChangedWallpaper" = "Channel set a new wallpaper"; "Story.MessageReposted.Personal" = "Message reposted to your stories."; @@ -10763,3 +10763,8 @@ Sorry for the inconvenience."; "Share.RepostToStory" = "Repost\nto Story"; "Conversation.ReadMore" = "Read More"; + +"Wallpaper.ChannelTitle" = "Channel Wallpaper"; +"Wallpaper.ChannelCustomBackgroundInfo" = "Upload your own background image for the channel."; +"Wallpaper.ChannelRemoveBackground" = "Remove Wallpaper"; +"Wallpaper.NoWallpaper" = "No\nWallpaper"; diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 8e7f0feded..bb316752f1 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -101,7 +101,9 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject { public final var supportedOrientations: ViewControllerSupportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) { didSet { if self.supportedOrientations != oldValue { - self.window?.invalidateSupportedOrientations() + if self.isNodeLoaded { + self.window?.invalidateSupportedOrientations() + } } } } diff --git a/submodules/DrawingUI/Sources/DrawingScreen.swift b/submodules/DrawingUI/Sources/DrawingScreen.swift index 1d749f720d..86cdc8829c 100644 --- a/submodules/DrawingUI/Sources/DrawingScreen.swift +++ b/submodules/DrawingUI/Sources/DrawingScreen.swift @@ -3152,12 +3152,13 @@ public final class DrawingToolsInteraction { })) } } - #if DEBUG - if isRectangleImage { + + + if #available(iOS 17.0, *), isRectangleImage { actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Paint_CutOut, accessibilityLabel: presentationData.strings.Paint_CutOut), action: { [weak self, weak entityView] in if let self, let entityView, let entity = entityView.entity as? DrawingStickerEntity, case let .image(image, _) = entity.content { let _ = (cutoutStickerImage(from: image) - |> deliverOnMainQueue).start(next: { [weak entity] result in + |> deliverOnMainQueue).start(next: { [weak entity] result in if let result, let entity { let newEntity = DrawingStickerEntity(content: .image(result, .sticker)) newEntity.referenceDrawingSize = entity.referenceDrawingSize @@ -3180,7 +3181,7 @@ public final class DrawingToolsInteraction { } })) } - #endif + let entityFrame = entityView.convert(entityView.selectionBounds, to: node.view).offsetBy(dx: 0.0, dy: -6.0) let controller = makeContextMenuController(actions: actions) let bounds = node.bounds.insetBy(dx: 0.0, dy: 160.0) diff --git a/submodules/Emoji/Sources/EmojiUtils.swift b/submodules/Emoji/Sources/EmojiUtils.swift index e30a0e09c3..690a82aebc 100644 --- a/submodules/Emoji/Sources/EmojiUtils.swift +++ b/submodules/Emoji/Sources/EmojiUtils.swift @@ -68,37 +68,14 @@ public extension String { var isSingleEmoji: Bool { return self.count == 1 && self.containsEmoji -// return self.emojis.count == 1 && self.containsEmoji } var containsEmoji: Bool { return self.contains { $0.isEmoji } - //return self.unicodeScalars.contains { $0.isEmoji } } var containsOnlyEmoji: Bool { return !self.isEmpty && !self.contains { !$0.isEmoji } -// guard !self.isEmpty else { -// return false -// } -// var nextShouldBeVariationSelector = false -// for scalar in self.unicodeScalars { -// if nextShouldBeVariationSelector { -// if scalar == UnicodeScalar.VariationSelector { -// nextShouldBeVariationSelector = false -// continue -// } else { -// return false -// } -// } -// if !scalar.isEmoji && scalar.maybeEmoji { -// nextShouldBeVariationSelector = true -// } -// else if !scalar.isEmoji && scalar != UnicodeScalar.ZeroWidthJoiner { -// return false -// } -// } -// return !nextShouldBeVariationSelector } var emojis: [String] { diff --git a/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift b/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift index 3bd18d4852..591f017748 100644 --- a/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift +++ b/submodules/ItemListPeerActionItem/Sources/ItemListPeerActionItem.swift @@ -24,19 +24,21 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem { let icon: UIImage? let iconSignal: Signal? let title: String + let additionalBadgeIcon: UIImage? public let alwaysPlain: Bool let hasSeparator: Bool let editing: Bool let height: ItemListPeerActionItemHeight let color: ItemListPeerActionItemColor public let sectionId: ItemListSectionId - let action: (() -> Void)? + public let action: (() -> Void)? - public init(presentationData: ItemListPresentationData, icon: UIImage?, iconSignal: Signal? = nil, title: String, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, editing: Bool = false, action: (() -> Void)?) { + public init(presentationData: ItemListPresentationData, icon: UIImage?, iconSignal: Signal? = nil, title: String, additionalBadgeIcon: UIImage? = nil, alwaysPlain: Bool = false, hasSeparator: Bool = true, sectionId: ItemListSectionId, height: ItemListPeerActionItemHeight = .peerList, color: ItemListPeerActionItemColor = .accent, editing: Bool = false, action: (() -> Void)?) { self.presentationData = presentationData self.icon = icon self.iconSignal = iconSignal self.title = title + self.additionalBadgeIcon = additionalBadgeIcon self.alwaysPlain = alwaysPlain self.hasSeparator = hasSeparator self.editing = editing @@ -102,7 +104,7 @@ public class ItemListPeerActionItem: ListViewItem, ItemListItem { } } -class ItemListPeerActionItemNode: ListViewItemNode { +public final class ItemListPeerActionItemNode: ListViewItemNode { private let backgroundNode: ASDisplayNode private let topStripeNode: ASDisplayNode private let bottomStripeNode: ASDisplayNode @@ -111,6 +113,7 @@ class ItemListPeerActionItemNode: ListViewItemNode { private let iconNode: ASImageNode private let titleNode: TextNode + private var additionalLabelBadgeNode: ASImageNode? private let activateArea: AccessibilityAreaNode @@ -118,7 +121,7 @@ class ItemListPeerActionItemNode: ListViewItemNode { private let iconDisposable = MetaDisposable() - init() { + public init() { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true @@ -157,7 +160,7 @@ class ItemListPeerActionItemNode: ListViewItemNode { self.iconDisposable.dispose() } - func asyncLayout() -> (_ item: ItemListPeerActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) { + public func asyncLayout() -> (_ item: ItemListPeerActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let currentItem = self.item @@ -177,7 +180,7 @@ class ItemListPeerActionItemNode: ListViewItemNode { switch item.height { case .generic: iconOffset = 1.0 - verticalInset = 11.0 + verticalInset = 12.0 verticalOffset = 0.0 leftInset = (item.icon == nil && item.iconSignal == nil ? 16.0 : 59.0) + params.leftInset case .peerList: @@ -296,15 +299,37 @@ class ItemListPeerActionItemNode: ListViewItemNode { strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)) transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))) - transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size)) + let titleFrame = CGRect(origin: CGPoint(x: leftInset + editingOffset, y: verticalInset + verticalOffset), size: titleLayout.size) + transition.updateFrame(node: strongSelf.titleNode, frame: titleFrame) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel)) + + if let additionalBadgeIcon = item.additionalBadgeIcon { + let additionalLabelBadgeNode: ASImageNode + if let current = strongSelf.additionalLabelBadgeNode { + additionalLabelBadgeNode = current + } else { + additionalLabelBadgeNode = ASImageNode() + additionalLabelBadgeNode.isUserInteractionEnabled = false + strongSelf.additionalLabelBadgeNode = additionalLabelBadgeNode + strongSelf.addSubnode(additionalLabelBadgeNode) + } + additionalLabelBadgeNode.image = additionalBadgeIcon + + let additionalLabelSize = additionalBadgeIcon.size + additionalLabelBadgeNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX + 6.0, y: floor((contentSize.height - additionalLabelSize.height) / 2.0)), size: additionalLabelSize) + } else { + if let additionalLabelBadgeNode = strongSelf.additionalLabelBadgeNode { + strongSelf.additionalLabelBadgeNode = nil + additionalLabelBadgeNode.removeFromSupernode() + } + } } }) } } - override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { + public override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { super.setHighlighted(highlighted, at: point, animated: animated) if highlighted { @@ -342,11 +367,11 @@ class ItemListPeerActionItemNode: ListViewItemNode { } } - override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { + public override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) } - override func animateRemoved(_ currentTimestamp: Double, duration: Double) { + public override func animateRemoved(_ currentTimestamp: Double, duration: Double) { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) } } diff --git a/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift b/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift index 047594a926..518e61d108 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListActionItem.swift @@ -52,7 +52,7 @@ public class ItemListActionItem: ListViewItem, ItemListItem { Queue.mainQueue().async { completion(node, { - return (nil, { _ in apply() }) + return (nil, { _ in apply(false) }) }) } } @@ -67,7 +67,7 @@ public class ItemListActionItem: ListViewItem, ItemListItem { let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) Queue.mainQueue().async { completion(layout, { _ in - apply() + apply(false) }) } } @@ -130,7 +130,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { self.addSubnode(self.activateArea) } - public func asyncLayout() -> (_ item: ItemListActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + public func asyncLayout() -> (_ item: ItemListActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let currentItem = self.item @@ -179,7 +179,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode { let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) - return (layout, { [weak self] in + return (layout, { [weak self] _ in if let strongSelf = self { strongSelf.item = item diff --git a/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift b/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift index 30c658bc32..597fd11f87 100644 --- a/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift +++ b/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift @@ -93,6 +93,7 @@ public final class ListSectionHeaderNode: ASDisplayNode { self.label = ImmediateTextNode() self.label.isUserInteractionEnabled = false self.label.isAccessibilityElement = true + self.label.displaysAsynchronously = false super.init() diff --git a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift index 64ce650d95..3d4e24f4d1 100644 --- a/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift +++ b/submodules/PremiumUI/Sources/ReplaceBoostScreen.swift @@ -229,7 +229,7 @@ private final class ReplaceBoostScreenComponent: CombinedComponent { selectionPosition: .right, isEnabled: isEnabled, hasNext: i != occupiedBoosts.count - 1, - action: { [weak state] _, _ in + action: { [weak state] _, _, _ in guard let state, hasSelection else { return } diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index b54577ef6d..d989444aba 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -121,6 +121,9 @@ swift_library( "//submodules/TelegramUI/Components/Settings/ThemeSettingsThemeItem", "//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen", "//submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode", + "//submodules/TelegramUI/Components/Settings/WallpaperGridScreen", + "//submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen", + "//submodules/TelegramUI/Components/Settings/GenerateThemeName", ], visibility = [ "//visibility:public", diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index a8de19b0b0..39af380686 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -22,6 +22,7 @@ import QrCodeUI import PremiumUI import StorageUsageScreen import PeerInfoStoryGridScreen +import WallpaperGridScreen enum SettingsSearchableItemIcon { case profile diff --git a/submodules/SettingsUI/Sources/ThemePickerController.swift b/submodules/SettingsUI/Sources/ThemePickerController.swift index 97198c6691..cb70fa94ea 100644 --- a/submodules/SettingsUI/Sources/ThemePickerController.swift +++ b/submodules/SettingsUI/Sources/ThemePickerController.swift @@ -20,6 +20,7 @@ import UndoUI import ItemListPeerActionItem import AnimationUI import ThemeSettingsThemeItem +import ThemeAccentColorScreen private final class ThemePickerControllerArguments { let context: AccountContext diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index f908091732..d4ac7c9af5 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -10,8 +10,10 @@ import MediaResources import AccountContext import LegacyUI import LegacyMediaPickerUI +import LegacyComponents import LocalMediaResources import ImageBlur +import WallpaperGridScreen import WallpaperGalleryScreen func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (ViewController) -> Void, push: @escaping (ViewController) -> Void) { @@ -47,275 +49,3 @@ func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (V present(legacyController) }) } - -func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?, completion: @escaping () -> Void) { - var imageSignal: Signal - switch wallpaper { - case let .wallpaper(wallpaper, _): - switch wallpaper { - case let .file(file): - if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data) - let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() - let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() - } - case let .image(representations, _): - for representation in representations { - let resource = representation.resource - if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) - let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() - } - } - default: - break - } - imageSignal = .complete() - completion() - case let .asset(asset): - imageSignal = fetchPhotoLibraryImage(localIdentifier: asset.localIdentifier, thumbnail: false) - |> filter { value in - return !(value?.1 ?? true) - } - |> mapToSignal { result -> Signal in - if let result = result { - return .single(result.0) - } else { - return .complete() - } - } - case let .contextResult(result): - var imageResource: TelegramMediaResource? - switch result { - case let .externalReference(externalReference): - if let content = externalReference.content { - imageResource = content.resource - } - case let .internalReference(internalReference): - if let image = internalReference.image { - if let imageRepresentation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 1000, height: 800)) { - imageResource = imageRepresentation.resource - } - } - } - - if let imageResource = imageResource { - imageSignal = .single(context.account.postbox.mediaBox.completedResourcePath(imageResource)) - |> mapToSignal { path -> Signal in - if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]), let image = UIImage(data: data) { - return .single(image) - } else { - return .complete() - } - } - } else { - imageSignal = .complete() - } - } - - if let editedImage { - imageSignal = .single(editedImage) - } - - let _ = (imageSignal - |> map { image -> UIImage in - var croppedImage = UIImage() - - let finalCropRect: CGRect - if let cropRect = cropRect { - finalCropRect = cropRect - } else { - let screenSize = TGScreenSize() - let fittedSize = TGScaleToFit(screenSize, image.size) - finalCropRect = CGRect(x: (image.size.width - fittedSize.width) / 2.0, y: (image.size.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height) - } - croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true) - - 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 autoNightModeTriggered = context.sharedContext.currentPresentationData.with {$0 }.autoNightModeTriggered - let accountManager = context.sharedContext.accountManager - let account = context.account - let updateWallpaper: (TelegramWallpaper) -> Void = { wallpaper in - var resource: MediaResource? - if case let .image(representations, _) = wallpaper, let representation = largestImageRepresentation(representations) { - resource = representation.resource - } else if case let .file(file) = wallpaper { - resource = file.file.resource - } - - if let resource = resource { - let _ = accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start(completed: {}) - let _ = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start(completed: {}) - } - - let _ = (updatePresentationThemeSettingsInteractively(accountManager: accountManager, { current in - var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers - let themeReference: PresentationThemeReference - if autoNightModeTriggered { - themeReference = current.automaticThemeSwitchSetting.theme - } else { - themeReference = current.theme - } - let accentColor = current.themeSpecificAccentColors[themeReference.index] - if let accentColor = accentColor, accentColor.baseColor == .custom { - themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] = wallpaper - } else { - themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] = nil - themeSpecificChatWallpapers[themeReference.index] = wallpaper - } - return current.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers) - })).start() - } - - let apply: () -> Void = { - let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: nil) - let wallpaper: 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) - updateWallpaper(wallpaper) - DispatchQueue.main.async { - completion() - } - } - - if mode.contains(.blur) { - let representation = CachedBlurredWallpaperRepresentation() - let _ = context.account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true).start() - let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true).start(completed: { - apply() - }) - } else { - apply() - } - } - return croppedImage - }).start() -} - -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 { - case let .wallpaper(wallpaper, _): - imageSignal = .complete() - switch wallpaper { - case let .file(file): - if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let image = UIImage(data: data) { - context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data, synchronous: true) - let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() - let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() - - imageSignal = .single(image) - } - case let .image(representations, _): - for representation in representations { - let resource = representation.resource - if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { - context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) - let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() - } - } - default: - break - } - completion() - case let .asset(asset): - imageSignal = fetchPhotoLibraryImage(localIdentifier: asset.localIdentifier, thumbnail: false) - |> filter { value in - return !(value?.1 ?? true) - } - |> mapToSignal { result -> Signal in - if let result = result { - return .single(result.0) - } else { - return .complete() - } - } - case let .contextResult(result): - var imageResource: TelegramMediaResource? - switch result { - case let .externalReference(externalReference): - if let content = externalReference.content { - imageResource = content.resource - } - case let .internalReference(internalReference): - if let image = internalReference.image { - if let imageRepresentation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 1000, height: 800)) { - imageResource = imageRepresentation.resource - } - } - } - - if let imageResource = imageResource { - imageSignal = .single(context.account.postbox.mediaBox.completedResourcePath(imageResource)) - |> mapToSignal { path -> Signal in - if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]), let image = UIImage(data: data) { - return .single(image) - } else { - return .complete() - } - } - } else { - imageSignal = .complete() - } - } - - if let editedImage { - imageSignal = .single(editedImage) - } - - let _ = (imageSignal - |> map { image -> UIImage in - var croppedImage = UIImage() - - let finalCropRect: CGRect - if let cropRect = cropRect { - finalCropRect = cropRect - } else { - let screenSize = TGScreenSize() - let fittedSize = TGScaleToFit(screenSize, image.size) - finalCropRect = CGRect(x: (image.size.width - fittedSize.width) / 2.0, y: (image.size.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height) - } - croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true) - - 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))) - } - - Queue.mainQueue().after(0.05) { - 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) - - context.account.pendingPeerMediaUploadManager.add(peerId: peerId, content: .wallpaper(wallpaper: temporaryWallpaper, forBoth: forBoth)) - - Queue.mainQueue().after(0.05) { - completion() - } - } - } - return croppedImage - }).start() -} diff --git a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift index c840a41700..c8030edd2e 100644 --- a/submodules/SettingsUI/Sources/Themes/EditThemeController.swift +++ b/submodules/SettingsUI/Sources/Themes/EditThemeController.swift @@ -14,6 +14,8 @@ import LegacyMediaPickerUI import WallpaperResources import AccountContext import MediaResources +import ThemeAccentColorScreen +import GenerateThemeName private final class EditThemeControllerArguments { let context: AccountContext diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerItem.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerItem.swift deleted file mode 100644 index 1a6813b555..0000000000 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerItem.swift +++ /dev/null @@ -1,134 +0,0 @@ -import Foundation -import UIKit -import Display -import TelegramCore -import SwiftSignalKit -import AsyncDisplayKit -import Postbox -import AccountContext -import GridMessageSelectionNode -import SettingsThemeWallpaperNode - -final class ThemeGridControllerItem: GridItem { - let context: AccountContext - let wallpaper: TelegramWallpaper - let wallpaperId: ThemeGridControllerEntry.StableId - let index: Int - let editable: Bool - let selected: Bool - let interaction: ThemeGridControllerInteraction - - let section: GridSection? = nil - - init(context: AccountContext, wallpaper: TelegramWallpaper, wallpaperId: ThemeGridControllerEntry.StableId, index: Int, editable: Bool, selected: Bool, interaction: ThemeGridControllerInteraction) { - self.context = context - self.wallpaper = wallpaper - self.wallpaperId = wallpaperId - self.index = index - self.editable = editable - self.selected = selected - self.interaction = interaction - } - - func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { - let node = ThemeGridControllerItemNode() - node.setup(item: self, synchronousLoad: synchronousLoad) - return node - } - - func update(node: GridItemNode) { - guard let node = node as? ThemeGridControllerItemNode else { - assertionFailure() - return - } - node.setup(item: self, synchronousLoad: false) - } -} - -final class ThemeGridControllerItemNode: GridItemNode { - private let wallpaperNode: SettingsThemeWallpaperNode - private var selectionNode: GridMessageSelectionNode? - - private var item: ThemeGridControllerItem? - - override init() { - self.wallpaperNode = SettingsThemeWallpaperNode(displayLoading: false) - - super.init() - - self.addSubnode(self.wallpaperNode) - } - - override func didLoad() { - super.didLoad() - - self.view.isExclusiveTouch = true - self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) - } - - func setup(item: ThemeGridControllerItem, synchronousLoad: Bool) { - self.item = item - self.updateSelectionState(animated: false) - self.setNeedsLayout() - } - - @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - if let item = self.item { - item.interaction.openWallpaper(item.wallpaper) - } - } - } - - func updateSelectionState(animated: Bool) { - if let item = self.item { - let (editing, selectedIds) = item.interaction.selectionState - - if editing && item.editable { - let selected = selectedIds.contains(item.wallpaperId) - - if let selectionNode = self.selectionNode { - selectionNode.updateSelected(selected, animated: animated) - selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) - } else { - let theme = item.context.sharedContext.currentPresentationData.with { $0 }.theme - let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in - if let strongSelf = self { - strongSelf.item?.interaction.toggleWallpaperSelection(item.wallpaperId, value) - } - }) - - selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) - self.addSubnode(selectionNode) - self.selectionNode = selectionNode - selectionNode.updateSelected(selected, animated: false) - if animated { - selectionNode.animateIn() - } - } - } - else { - if let selectionNode = self.selectionNode { - self.selectionNode = nil - if animated { - selectionNode.animateOut { [weak selectionNode] in - selectionNode?.removeFromSupernode() - } - } else { - selectionNode.removeFromSupernode() - } - } - } - } - } - - override func layout() { - super.layout() - - let bounds = self.bounds - if let item = self.item { - self.wallpaperNode.setWallpaper(context: item.context, wallpaper: item.wallpaper, selected: item.selected, size: bounds.size, synchronousLoad: false) - self.selectionNode?.frame = CGRect(origin: CGPoint(), size: bounds.size) - } - } -} diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift index 08e5dc5254..e21f3b4f7b 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift @@ -20,6 +20,8 @@ import UndoUI import PremiumUI import PeerNameColorScreen import ThemeCarouselItem +import ThemeAccentColorScreen +import WallpaperGridScreen private final class ThemeSettingsControllerArguments { let context: AccountContext diff --git a/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift b/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift index 651a93f1a6..4872e91bf2 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/Wallpaper.swift @@ -7,7 +7,7 @@ import TelegramApi extension WallpaperSettings { init(apiWallpaperSettings: Api.WallPaperSettings) { switch apiWallpaperSettings { - case let .wallPaperSettings(flags, backgroundColor, secondBackgroundColor, thirdBackgroundColor, fourthBackgroundColor, intensity, rotation, _): + case let .wallPaperSettings(flags, backgroundColor, secondBackgroundColor, thirdBackgroundColor, fourthBackgroundColor, intensity, rotation, emoticon): var colors: [UInt32] = [] if let backgroundColor = backgroundColor { colors.append(UInt32(bitPattern: backgroundColor)) @@ -21,7 +21,7 @@ extension WallpaperSettings { if let fourthBackgroundColor = fourthBackgroundColor { colors.append(UInt32(bitPattern: fourthBackgroundColor)) } - self = WallpaperSettings(blur: (flags & 1 << 1) != 0, motion: (flags & 1 << 2) != 0, colors: colors, intensity: intensity, rotation: rotation) + self = WallpaperSettings(blur: (flags & 1 << 1) != 0, motion: (flags & 1 << 2) != 0, colors: colors, intensity: intensity, rotation: rotation, emoticon: emoticon) } } } @@ -42,6 +42,9 @@ func apiWallpaperSettings(_ wallpaperSettings: WallpaperSettings) -> Api.WallPap if let _ = wallpaperSettings.intensity { flags |= (1 << 3) } + if let _ = wallpaperSettings.emoticon { + flags |= (1 << 7) + } var secondBackgroundColor: Int32? if wallpaperSettings.colors.count >= 2 { flags |= (1 << 4) @@ -57,7 +60,7 @@ func apiWallpaperSettings(_ wallpaperSettings: WallpaperSettings) -> Api.WallPap flags |= (1 << 6) fourthBackgroundColor = Int32(bitPattern: wallpaperSettings.colors[3]) } - return .wallPaperSettings(flags: flags, backgroundColor: backgroundColor, secondBackgroundColor: secondBackgroundColor, thirdBackgroundColor: thirdBackgroundColor, fourthBackgroundColor: fourthBackgroundColor, intensity: wallpaperSettings.intensity, rotation: wallpaperSettings.rotation ?? 0, emoticon: nil) + return .wallPaperSettings(flags: flags, backgroundColor: backgroundColor, secondBackgroundColor: secondBackgroundColor, thirdBackgroundColor: thirdBackgroundColor, fourthBackgroundColor: fourthBackgroundColor, intensity: wallpaperSettings.intensity, rotation: wallpaperSettings.rotation ?? 0, emoticon: wallpaperSettings.emoticon) } extension TelegramWallpaper { @@ -109,4 +112,8 @@ extension TelegramWallpaper { return nil } } + + func apiInputWallpaper(emoticon: String) -> (Api.InputWallPaper, Api.WallPaperSettings) { + return (.inputWallPaperNoFile(id: 0), apiWallpaperSettings(WallpaperSettings(emoticon: emoticon))) + } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramWallpaper.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramWallpaper.swift index 3645af6204..b39e0f5850 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramWallpaper.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramWallpaper.swift @@ -6,13 +6,15 @@ public struct WallpaperSettings: Codable, Equatable { public var colors: [UInt32] public var intensity: Int32? public var rotation: Int32? + public var emoticon: String? - public init(blur: Bool = false, motion: Bool = false, colors: [UInt32] = [], intensity: Int32? = nil, rotation: Int32? = nil) { + public init(blur: Bool = false, motion: Bool = false, colors: [UInt32] = [], intensity: Int32? = nil, rotation: Int32? = nil, emoticon: String? = nil) { self.blur = blur self.motion = motion self.colors = colors self.intensity = intensity self.rotation = rotation + self.emoticon = emoticon } public init(from decoder: Decoder) throws { @@ -32,6 +34,7 @@ public struct WallpaperSettings: Codable, Equatable { self.intensity = try container.decodeIfPresent(Int32.self, forKey: "i") self.rotation = try container.decodeIfPresent(Int32.self, forKey: "r") + self.emoticon = try container.decodeIfPresent(String.self, forKey: "e") } public func encode(to encoder: Encoder) throws { @@ -42,6 +45,7 @@ public struct WallpaperSettings: Codable, Equatable { try container.encode(self.colors.map(Int32.init(bitPattern:)), forKey: "colors") try container.encodeIfPresent(self.intensity, forKey: "i") try container.encodeIfPresent(self.rotation, forKey: "r") + try container.encodeIfPresent(self.emoticon, forKey: "e") } public static func ==(lhs: WallpaperSettings, rhs: WallpaperSettings) -> Bool { @@ -60,6 +64,9 @@ public struct WallpaperSettings: Codable, Equatable { if lhs.rotation != rhs.rotation { return false } + if lhs.emoticon != rhs.emoticon { + return false + } return true } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index 7e4ebeff10..825cfb9c37 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -318,6 +318,40 @@ public extension TelegramEngine.EngineData.Item { } } } + + public struct Wallpaper: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { + public typealias Result = Optional + + fileprivate var id: EnginePeer.Id + public var mapKey: EnginePeer.Id { + return self.id + } + + public init(id: EnginePeer.Id) { + self.id = id + } + + var key: PostboxViewKey { + return .cachedPeerData(peerId: self.id) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? CachedPeerDataView else { + preconditionFailure() + } + guard let cachedPeerData = view.cachedPeerData else { + return nil + } + switch cachedPeerData { + case let channel as CachedChannelData: + return channel.wallpaper + case let user as CachedUserData: + return user.wallpaper + default: + return nil + } + } + } public struct GroupCallDescription: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem { public typealias Result = Optional diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift index 1764f9fb4f..b8ec852b77 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineStoryViewListContext.swift @@ -427,7 +427,7 @@ public final class EngineStoryViewListContext { let sortMode = self.sortMode let searchQuery = self.searchQuery let currentOffset = state.nextOffset - let limit = state.items.isEmpty ? 50 : 100 + let limit = 50 let signal: Signal @@ -659,6 +659,9 @@ public final class EngineStoryViewListContext { } var flags: Int32 = 0 + if let _ = currentOffset { + flags |= (1 << 1) + } if case .repostsFirst = sortMode { flags |= (1 << 2) } @@ -883,4 +886,3 @@ public final class EngineStoryViewListContext { } } } - diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/LoadMessagesIfNecessary.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/LoadMessagesIfNecessary.swift index 52ecce87b7..6d54271de4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/LoadMessagesIfNecessary.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/LoadMessagesIfNecessary.swift @@ -14,7 +14,11 @@ public enum GetMessagesStrategy { case cloud(skipLocal: Bool) } -func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Postbox, network: Network, accountPeerId: PeerId, strategy: GetMessagesStrategy = .cloud(skipLocal: false)) -> Signal { +public enum GetMessagesError { + case privateChannel +} + +func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Postbox, network: Network, accountPeerId: PeerId, strategy: GetMessagesStrategy = .cloud(skipLocal: false)) -> Signal { let postboxSignal = postbox.transaction { transaction -> ([Message], Set, SimpleDictionary) in var ids = messageIds @@ -50,8 +54,9 @@ func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Po if case .cloud = strategy { return postboxSignal + |> castError(GetMessagesError.self) |> mapToSignal { (existMessages, missingMessageIds, supportPeers) in - var signals: [Signal<(Peer, [Api.Message], [Api.Chat], [Api.User]), NoError>] = [] + var signals: [Signal<(Peer, [Api.Message], [Api.Chat], [Api.User]), GetMessagesError>] = [] for (peerId, messageIds) in messagesIdsGroupedByPeerId(missingMessageIds) { if let peer = supportPeers[peerId] { var signal: Signal? @@ -75,14 +80,20 @@ func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Po case .messagesNotModified: return (peer, [], [], []) } - } |> `catch` { _ in - return Signal<(Peer, [Api.Message], [Api.Chat], [Api.User]), NoError>.single((peer, [], [], [])) + } |> `catch` { error in + if error.errorDescription == "CHANNEL_PRIVATE" { + return .fail(.privateChannel) + } else { + return Signal<(Peer, [Api.Message], [Api.Chat], [Api.User]), GetMessagesError>.single((peer, [], [], [])) + } }) } } } - return .single(.progress) |> then(combineLatest(signals) |> mapToSignal { results -> Signal in + return .single(.progress) + |> castError(GetMessagesError.self) + |> then(combineLatest(signals) |> mapToSignal { results -> Signal in return postbox.transaction { transaction -> GetMessagesResult in for (peer, messages, chats, users) in results { if !messages.isEmpty { @@ -108,10 +119,12 @@ func _internal_getMessagesLoadIfNecessary(_ messageIds: [MessageId], postbox: Po return .result(existMessages + loadedMessages) } + |> castError(GetMessagesError.self) }) } } else { return postboxSignal + |> castError(GetMessagesError.self) |> map { return .result($0.0) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index 2d0215fd49..76ed1a447b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -159,7 +159,7 @@ public extension TelegramEngine { return _internal_markAllChatsAsRead(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager) } - public func getMessagesLoadIfNecessary(_ messageIds: [MessageId], strategy: GetMessagesStrategy = .cloud(skipLocal: false)) -> Signal { + public func getMessagesLoadIfNecessary(_ messageIds: [MessageId], strategy: GetMessagesStrategy = .cloud(skipLocal: false)) -> Signal { return _internal_getMessagesLoadIfNecessary(messageIds, postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId, strategy: strategy) } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index 58464c12c4..5199856ec8 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -418,6 +418,7 @@ swift_library( "//submodules/UIKitRuntimeUtils", "//submodules/TelegramUI/Components/SavedMessages/SavedMessagesScreen", "//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen", + "//submodules/TelegramUI/Components/Settings/WallpaperGridScreen", ] + select({ "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, "//build-system:ios_sim_arm64": [], diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index 805eb58882..661123bb1d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -686,7 +686,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { candidateTitleString = NSAttributedString(string: title ?? (arguments.file.fileName ?? "Unknown Track"), font: titleFont, textColor: arguments.customTintColor ?? messageTheme.fileTitleColor) let descriptionText: String if let performer = performer { - descriptionText = performer + descriptionText = performer.trimmingTrailingSpaces() } else if let size = arguments.file.size, size > 0 && size != .max { descriptionText = dataSizeString(size, formatting: DataSizeStringFormatting(chatPresentationData: arguments.presentationData)) } else { @@ -876,7 +876,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { var viewCount: Int? var dateReplies = 0 var dateReactionsAndPeers = mergedMessageReactionsAndPeers(accountPeer: arguments.associatedData.accountPeer, message: arguments.topMessage) - if arguments.topMessage.isRestricted(platform: "ios", contentSettings: arguments.context.currentContentSettings.with { $0 }) { + if arguments.topMessage.isRestricted(platform: "ios", contentSettings: arguments.context.currentContentSettings.with { $0 }) || arguments.presentationData.isPreview { dateReactionsAndPeers = ([], []) } for attribute in arguments.message.attributes { @@ -954,7 +954,11 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { minLayoutWidth = max(titleLayout.size.width, descriptionMaxWidth) + 44.0 + 8.0 } + var statusHeightAddition: CGFloat = 0.0 if let statusSuggestedWidthAndContinue = statusSuggestedWidthAndContinue { + if statusSuggestedWidthAndContinue.0 > minLayoutWidth { + statusHeightAddition = 6.0 + } minLayoutWidth = max(minLayoutWidth, statusSuggestedWidthAndContinue.0) } @@ -1020,6 +1024,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { statusOffset = -10.0 fittedLayoutSize.height += statusOffset } + fittedLayoutSize.height += statusHeightAddition } } @@ -1223,7 +1228,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { if textString != nil { statusFrame = CGRect(origin: CGPoint(x: fittedLayoutSize.width - 6.0 - statusSizeAndApply.0.width, y: textFrame.maxY + 4.0), size: statusSizeAndApply.0) } else { - statusFrame = CGRect(origin: CGPoint(x: statusReferenceFrame.minX, y: statusReferenceFrame.maxY + statusOffset), size: statusSizeAndApply.0) + statusFrame = CGRect(origin: CGPoint(x: statusReferenceFrame.minX, y: statusReferenceFrame.maxY + statusOffset + statusHeightAddition), size: statusSizeAndApply.0) } if strongSelf.dateAndStatusNode.supernode == nil { strongSelf.dateAndStatusNode.frame = statusFrame diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index b4f484f7e4..6df56367d9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -1312,7 +1312,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr switch wallpaper.content { case let .file(file, _, _, _, isTheme, _): if isTheme { - return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .file(FileMediaReference.message(message: MessageReference(message), media: file))) + return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .file(FileMediaReference.message(message: MessageReference(message), media: file)), synchronousLoad: synchronousLoad) } else { var representations: [ImageRepresentationWithReference] = file.previewRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference($0.resource)) }) if file.mimeType == "image/svg+xml" || file.mimeType == "application/x-tgwallpattern" { @@ -1328,7 +1328,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr } } } else { - return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), representations: representations, alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true) + return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), representations: representations, alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true, synchronousLoad: synchronousLoad) } } case let .image(representations): diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index 39ed0d3bb7..a26274fa0d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -512,7 +512,9 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { var cutout: TextNodeCutout? = nil if item.presentationData.isPreview { moreLayoutAndApply = moreLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Conversation_ReadMore, font: textFont, textColor: messageTheme.accentTextColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize)) - cutout = TextNodeCutout(bottomRight: moreLayoutAndApply?.0.size) + if let moreSize = moreLayoutAndApply?.0.size { + cutout = TextNodeCutout(bottomRight: CGSize(width: moreSize.width + 8.0, height: moreSize.height)) + } } let textInsets = UIEdgeInsets(top: 2.0, left: 2.0, bottom: 5.0, right: 2.0) diff --git a/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift b/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift index e0d7b3070e..cbc53bc538 100644 --- a/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift +++ b/submodules/TelegramUI/Components/Chat/EditableTokenListNode/Sources/EditableTokenListNode.swift @@ -342,12 +342,16 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { public func updateLayout(tokens: [EditableTokenListToken], width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { let validTokens = Set(tokens.map { $0.id }) + var placeholderSnapshot: UIView? if let shortPlaceholder = self.shortPlaceholder { - if validTokens.count > 0 { - self.placeholderNode.attributedText = NSAttributedString(string: shortPlaceholder, font: Font.regular(15.0), textColor: self.theme.placeholderTextColor) - } else { - self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(15.0), textColor: self.theme.placeholderTextColor) + let previousPlaceholder = self.placeholderNode.attributedText?.string ?? "" + let placeholder = validTokens.count > 0 ? shortPlaceholder : self.placeholder + + if !previousPlaceholder.isEmpty && placeholder != previousPlaceholder { + placeholderSnapshot = self.placeholderNode.layer.snapshotContentTreeAsView() + placeholderSnapshot?.frame = self.placeholderNode.frame } + self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(15.0), textColor: self.theme.placeholderTextColor) } for i in (0 ..< self.tokenNodes.count).reversed() { @@ -444,10 +448,23 @@ public final class EditableTokenListNode: ASDisplayNode, UITextFieldDelegate { currentOffset.y += 28.0 currentOffset.x = sideInset } + + let previousPlaceholderWidth = self.placeholderNode.bounds.width let placeholderFrame = CGRect(origin: CGPoint(x: currentOffset.x + 4.0, y: currentOffset.y + floor((28.0 - placeholderSize.height) / 2.0)), size: placeholderSize) self.placeholderNode.bounds = CGRect(origin: .zero, size: placeholderSize) transition.updatePosition(node: self.placeholderNode, position: placeholderFrame.center) + if let placeholderSnapshot { + self.placeholderNode.view.superview?.insertSubview(placeholderSnapshot, belowSubview: self.placeholderNode.view) + self.placeholderNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + placeholderSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + placeholderSnapshot.removeFromSuperview() + }) + let delta = (placeholderSize.width - previousPlaceholderWidth) / 2.0 + transition.updatePosition(layer: placeholderSnapshot.layer, position: CGPoint(x: placeholderFrame.center.x - delta, y: placeholderFrame.center.y)) + transition.animatePositionAdditive(node: self.placeholderNode, offset: CGPoint(x: delta, y: 0.0)) + } + let textNodeFrame = CGRect(origin: CGPoint(x: currentOffset.x + 4.0, y: currentOffset.y + UIScreenPixel), size: CGSize(width: width - currentOffset.x - sideInset - 8.0, height: 28.0)) let caretNodeFrame = CGRect(origin: CGPoint(x: textNodeFrame.minX, y: textNodeFrame.minY + 4.0 - UIScreenPixel), size: CGSize(width: 2.0, height: 19.0 + UIScreenPixel)) if case .immediate = transition { diff --git a/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuNode.swift b/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuNode.swift index be2e5f3e76..9e046f5987 100644 --- a/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuNode.swift +++ b/submodules/TelegramUI/Components/ContextMenuScreen/Sources/ContextMenuNode.swift @@ -64,6 +64,7 @@ private final class ArrowNode: HighlightTrackingButtonNode { } final class ContextMenuNode: ASDisplayNode { + private let blurred: Bool private let isDark: Bool private let actions: [ContextMenuAction] @@ -93,6 +94,7 @@ final class ContextMenuNode: ASDisplayNode { private let feedback: HapticFeedback? init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, dismissOnTap: @escaping (UIView, CGPoint) -> Bool, catchTapsOutside: Bool, hasHapticFeedback: Bool, blurred: Bool = false, isDark: Bool = true) { + self.blurred = blurred self.isDark = isDark self.actions = actions @@ -264,9 +266,11 @@ final class ContextMenuNode: ASDisplayNode { self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: containerPosition.x, y: containerPosition.y + (self.arrowOnBottom ? 1.0 : -1.0) * self.containerNode.bounds.size.height / 2.0)), to: NSValue(cgPoint: containerPosition), keyPath: "position", duration: 0.4) } - self.allowsGroupOpacity = true - self.layer.rasterizationScale = UIScreen.main.scale - self.layer.shouldRasterize = true + if !(self.blurred && self.isDark) { + self.allowsGroupOpacity = true + self.layer.rasterizationScale = UIScreen.main.scale + self.layer.shouldRasterize = true + } self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1, completion: { [weak self] _ in self?.allowsGroupOpacity = false self?.layer.shouldRasterize = false @@ -278,9 +282,11 @@ final class ContextMenuNode: ASDisplayNode { } func animateOut(bounce: Bool, completion: @escaping () -> Void) { - self.allowsGroupOpacity = true - self.layer.rasterizationScale = UIScreen.main.scale - self.layer.shouldRasterize = true + if !(self.blurred && self.isDark) { + self.allowsGroupOpacity = true + self.layer.rasterizationScale = UIScreen.main.scale + self.layer.shouldRasterize = true + } self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in self?.allowsGroupOpacity = false self?.layer.shouldRasterize = false diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 32cea5ef8d..b3da601579 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -1575,6 +1575,102 @@ final class MediaEditorScreenComponent: Component { } var topButtonOffsetX: CGFloat = 0.0 + + if let subject = controller.node.subject, case .message = subject { + let isNightTheme = mediaEditor?.values.nightTheme == true + + let dayNightContentComponent: AnyComponentWithIdentity + if component.hasAppeared { + dayNightContentComponent = AnyComponentWithIdentity( + id: "animatedIcon", + component: AnyComponent( + LottieAnimationComponent( + animation: LottieAnimationComponent.AnimationItem( + name: isNightTheme ? "anim_sun" : "anim_sun_reverse", + mode: state.dayNightDidChange ? .animating(loop: false) : .still(position: .end) + ), + colors: ["__allcolors__": .white], + size: CGSize(width: 30.0, height: 30.0) + ).tagged(dayNightButtonTag) + ) + ) + } else { + dayNightContentComponent = AnyComponentWithIdentity( + id: "staticIcon", + component: AnyComponent( + BundleIconComponent( + name: "Media Editor/MuteIcon", + tintColor: nil + ) + ) + ) + } + + let dayNightButtonSize = self.dayNightButton.update( + transition: transition, + component: AnyComponent(CameraButton( + content: dayNightContentComponent, + action: { [weak self, weak state, weak mediaEditor] in + guard let environment = self?.environment, let controller = environment.controller() as? MediaEditorScreen else { + return + } + guard !controller.node.recording.isActive else { + return + } + + if let mediaEditor { + state?.dayNightDidChange = true + + if let snapshotView = controller.node.previewContainerView.snapshotView(afterScreenUpdates: false) { + controller.node.previewContainerView.addSubview(snapshotView) + + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, delay: 0.1, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + } + + Queue.mainQueue().after(0.1) { + mediaEditor.toggleNightTheme() + controller.node.entitiesView.eachView { view in + if let stickerEntityView = view as? DrawingStickerEntityView { + stickerEntityView.toggleNightTheme() + } + } + } + } + } + )), + environment: {}, + containerSize: CGSize(width: 44.0, height: 44.0) + ) + let dayNightButtonFrame = CGRect( + origin: CGPoint(x: availableSize.width - 20.0 - dayNightButtonSize.width - 50.0, y: max(environment.statusBarHeight + 10.0, environment.safeInsets.top + 20.0)), + size: dayNightButtonSize + ) + if let dayNightButtonView = self.dayNightButton.view { + if dayNightButtonView.superview == nil { + setupButtonShadow(dayNightButtonView) + self.addSubview(dayNightButtonView) + + dayNightButtonView.layer.animateAlpha(from: 0.0, to: dayNightButtonView.alpha, duration: self.animatingButtons ? 0.1 : 0.2) + dayNightButtonView.layer.animateScale(from: 0.4, to: 1.0, duration: self.animatingButtons ? 0.1 : 0.2) + } + transition.setPosition(view: dayNightButtonView, position: dayNightButtonFrame.center) + transition.setBounds(view: dayNightButtonView, bounds: CGRect(origin: .zero, size: dayNightButtonFrame.size)) + transition.setScale(view: dayNightButtonView, scale: displayTopButtons ? 1.0 : 0.01) + transition.setAlpha(view: dayNightButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities ? topButtonsAlpha : 0.0) + } + + topButtonOffsetX += 50.0 + } else { + if let dayNightButtonView = self.dayNightButton.view, dayNightButtonView.superview != nil { + dayNightButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak dayNightButtonView] _ in + dayNightButtonView?.removeFromSuperview() + }) + dayNightButtonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + } + } + if let playerState = state.playerState, playerState.hasAudio { let isVideoMuted = mediaEditor?.values.videoIsMuted ?? false @@ -1632,7 +1728,7 @@ final class MediaEditorScreenComponent: Component { containerSize: CGSize(width: 44.0, height: 44.0) ) let muteButtonFrame = CGRect( - origin: CGPoint(x: availableSize.width - 20.0 - muteButtonSize.width - 50.0, y: max(environment.statusBarHeight + 10.0, environment.safeInsets.top + 20.0)), + origin: CGPoint(x: availableSize.width - 20.0 - muteButtonSize.width - 50.0 - topButtonOffsetX, y: max(environment.statusBarHeight + 10.0, environment.safeInsets.top + 20.0)), size: muteButtonSize ) if let muteButtonView = self.muteButton.view { @@ -1736,101 +1832,6 @@ final class MediaEditorScreenComponent: Component { } } - if let subject = controller.node.subject, case .message = subject { - let isNightTheme = mediaEditor?.values.nightTheme == true - - let dayNightContentComponent: AnyComponentWithIdentity - if component.hasAppeared { - dayNightContentComponent = AnyComponentWithIdentity( - id: "animatedIcon", - component: AnyComponent( - LottieAnimationComponent( - animation: LottieAnimationComponent.AnimationItem( - name: isNightTheme ? "anim_sun" : "anim_sun_reverse", - mode: state.dayNightDidChange ? .animating(loop: false) : .still(position: .end) - ), - colors: ["__allcolors__": .white], - size: CGSize(width: 30.0, height: 30.0) - ).tagged(dayNightButtonTag) - ) - ) - } else { - dayNightContentComponent = AnyComponentWithIdentity( - id: "staticIcon", - component: AnyComponent( - BundleIconComponent( - name: "Media Editor/MuteIcon", - tintColor: nil - ) - ) - ) - } - - let dayNightButtonSize = self.dayNightButton.update( - transition: transition, - component: AnyComponent(CameraButton( - content: dayNightContentComponent, - action: { [weak self, weak state, weak mediaEditor] in - guard let environment = self?.environment, let controller = environment.controller() as? MediaEditorScreen else { - return - } - guard !controller.node.recording.isActive else { - return - } - - if let mediaEditor { - state?.dayNightDidChange = true - - if let snapshotView = controller.node.previewContainerView.snapshotView(afterScreenUpdates: false) { - controller.node.previewContainerView.addSubview(snapshotView) - - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, delay: 0.1, removeOnCompletion: false, completion: { _ in - snapshotView.removeFromSuperview() - }) - } - - Queue.mainQueue().after(0.1) { - mediaEditor.toggleNightTheme() - controller.node.entitiesView.eachView { view in - if let stickerEntityView = view as? DrawingStickerEntityView { - stickerEntityView.toggleNightTheme() - } - } - } - } - } - )), - environment: {}, - containerSize: CGSize(width: 44.0, height: 44.0) - ) - let dayNightButtonFrame = CGRect( - origin: CGPoint(x: availableSize.width - 20.0 - dayNightButtonSize.width - 50.0, y: max(environment.statusBarHeight + 10.0, environment.safeInsets.top + 20.0)), - size: dayNightButtonSize - ) - if let dayNightButtonView = self.dayNightButton.view { - if dayNightButtonView.superview == nil { - setupButtonShadow(dayNightButtonView) - self.addSubview(dayNightButtonView) - - dayNightButtonView.layer.animateAlpha(from: 0.0, to: dayNightButtonView.alpha, duration: self.animatingButtons ? 0.1 : 0.2) - dayNightButtonView.layer.animateScale(from: 0.4, to: 1.0, duration: self.animatingButtons ? 0.1 : 0.2) - } - transition.setPosition(view: dayNightButtonView, position: dayNightButtonFrame.center) - transition.setBounds(view: dayNightButtonView, bounds: CGRect(origin: .zero, size: dayNightButtonFrame.size)) - transition.setScale(view: dayNightButtonView, scale: displayTopButtons ? 1.0 : 0.01) - transition.setAlpha(view: dayNightButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities ? topButtonsAlpha : 0.0) - } - - topButtonOffsetX += 50.0 - } else { - if let dayNightButtonView = self.dayNightButton.view, dayNightButtonView.superview != nil { - dayNightButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak dayNightButtonView] _ in - dayNightButtonView?.removeFromSuperview() - }) - dayNightButtonView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) - } - } - let switchCameraButtonSize = self.switchCameraButton.update( transition: transition, component: AnyComponent(Button( diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/ContextResultPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/ContextResultPanelComponent.swift index 118426c4fb..6d096bf69e 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/ContextResultPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/ContextResultPanelComponent.swift @@ -230,7 +230,7 @@ final class ContextResultPanelComponent: Component { presence: nil, selectionState: .none, hasNext: index != peers.count - 1, - action: { [weak self] peer, _ in + action: { [weak self] peer, _, _ in guard let self, let component = self.component else { return } @@ -299,7 +299,7 @@ final class ContextResultPanelComponent: Component { presence: nil, selectionState: .none, hasNext: true, - action: { _, _ in + action: { _, _, _ in } )), environment: {}, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD index c2f038c2f9..1c31e02af7 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/BUILD @@ -135,6 +135,7 @@ swift_library( "//submodules/TelegramUI/Components/PeerInfo/PeerInfoChatListPaneNode", "//submodules/MediaPickerUI", "//submodules/AttachmentUI", + "//submodules/TelegramUI/Components/Settings/BoostLevelIconComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index bb71033973..7e0ef15460 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -101,6 +101,7 @@ import ChatHistorySearchContainerNode import PeerInfoPaneNode import MediaPickerUI import AttachmentUI +import BoostLevelIconComponent public enum PeerInfoAvatarEditingMode { case generic diff --git a/submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent/Sources/PremiumLockButtonSubtitleComponent.swift b/submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent/Sources/PremiumLockButtonSubtitleComponent.swift index 8e868f52f7..c9b75a09f8 100644 --- a/submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent/Sources/PremiumLockButtonSubtitleComponent.swift +++ b/submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent/Sources/PremiumLockButtonSubtitleComponent.swift @@ -8,20 +8,24 @@ import AnimatedTextComponent public final class PremiumLockButtonSubtitleComponent: CombinedComponent { public let count: Int - public let theme: PresentationTheme + public let color: UIColor public let strings: PresentationStrings - public init(count: Int, theme: PresentationTheme, strings: PresentationStrings) { + public init(count: Int, color: UIColor, strings: PresentationStrings) { self.count = count - self.theme = theme + self.color = color self.strings = strings } + public convenience init(count: Int, theme: PresentationTheme, strings: PresentationStrings) { + self.init(count: count, color: theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7), strings: strings) + } + public static func ==(lhs: PremiumLockButtonSubtitleComponent, rhs: PremiumLockButtonSubtitleComponent) -> Bool { if lhs.count != rhs.count { return false } - if lhs.theme !== rhs.theme { + if lhs.color !== rhs.color { return false } if lhs.strings !== rhs.strings { @@ -38,7 +42,7 @@ public final class PremiumLockButtonSubtitleComponent: CombinedComponent { let icon = icon.update( component: BundleIconComponent( name: "Chat/Input/Accessory Panels/TextLockIcon", - tintColor: context.component.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7), + tintColor: context.component.color, maxSize: CGSize(width: 10.0, height: 10.0) ), availableSize: CGSize(width: 100.0, height: 100.0), @@ -63,7 +67,7 @@ public final class PremiumLockButtonSubtitleComponent: CombinedComponent { } let text = text.update( - component: AnimatedTextComponent(font: Font.medium(11.0), color: context.component.theme.list.itemCheckColors.foregroundColor.withMultipliedAlpha(0.7), items: textItems), + component: AnimatedTextComponent(font: Font.medium(11.0), color: context.component.color, items: textItems), availableSize: CGSize(width: context.availableSize.width - 20.0, height: 100.0), transition: context.transition ) diff --git a/submodules/TelegramUI/Components/Settings/BoostLevelIconComponent/BUILD b/submodules/TelegramUI/Components/Settings/BoostLevelIconComponent/BUILD new file mode 100644 index 0000000000..9419d42c9b --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/BoostLevelIconComponent/BUILD @@ -0,0 +1,20 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "BoostLevelIconComponent", + module_name = "BoostLevelIconComponent", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/ComponentFlow", + "//submodules/TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/Settings/BoostLevelIconComponent/Sources/BoostLevelIconComponent.swift b/submodules/TelegramUI/Components/Settings/BoostLevelIconComponent/Sources/BoostLevelIconComponent.swift new file mode 100644 index 0000000000..74c0fc126c --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/BoostLevelIconComponent/Sources/BoostLevelIconComponent.swift @@ -0,0 +1,106 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData + +public func generateDisclosureActionBoostLevelBadgeImage(text: String) -> UIImage { + let attributedText = NSAttributedString(string: text, attributes: [ + .font: Font.medium(12.0), + .foregroundColor: UIColor.white + ]) + let bounds = attributedText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + let leftInset: CGFloat = 16.0 + let rightInset: CGFloat = 4.0 + let size = CGSize(width: leftInset + rightInset + ceil(bounds.width), height: 20.0) + return generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 6.0).cgPath) + context.clip() + + var locations: [CGFloat] = [0.0, 1.0] + let colors: [CGColor] = [UIColor(rgb: 0x9076FF).cgColor, UIColor(rgb: 0xB86DEA).cgColor] + let colorSpace = CGColorSpaceCreateDeviceRGB() + let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions()) + + context.resetClip() + + UIGraphicsPushContext(context) + + if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) { + let imageFit: CGFloat = 14.0 + let imageSize = image.size.aspectFitted(CGSize(width: imageFit, height: imageFit)) + let imageRect = CGRect(origin: CGPoint(x: 2.0, y: UIScreenPixel + floorToScreenPixels((size.height - imageSize.height) * 0.5)), size: imageSize) + image.draw(in: imageRect) + } + + attributedText.draw(at: CGPoint(x: leftInset, y: floorToScreenPixels((size.height - bounds.height) * 0.5))) + + UIGraphicsPopContext() + })! +} + +public final class BoostLevelIconComponent: Component { + let strings: PresentationStrings + let level: Int + + public init( + strings: PresentationStrings, + level: Int + ) { + self.strings = strings + self.level = level + } + + public static func ==(lhs: BoostLevelIconComponent, rhs: BoostLevelIconComponent) -> Bool { + if lhs.strings !== rhs.strings { + return false + } + if lhs.level != rhs.level { + return false + } + return true + } + + public final class View: UIView { + private let imageView: UIImageView + + private var component: BoostLevelIconComponent? + + override init(frame: CGRect) { + self.imageView = UIImageView() + + super.init(frame: frame) + + self.addSubview(self.imageView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: BoostLevelIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + if self.component != component { + //TODO:localize + self.imageView.image = generateDisclosureActionBoostLevelBadgeImage(text: "Level \(component.level)") + } + self.component = component + + if let image = self.imageView.image { + self.imageView.frame = CGRect(origin: CGPoint(), size: image.size) + return image.size + } else { + return CGSize(width: 1.0, height: 20.0) + } + } + } + + public func makeView() -> View { + return View(frame: CGRect()) + } + + public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/Settings/GenerateThemeName/BUILD b/submodules/TelegramUI/Components/Settings/GenerateThemeName/BUILD new file mode 100644 index 0000000000..32c10189fc --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/GenerateThemeName/BUILD @@ -0,0 +1,18 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "GenerateThemeName", + module_name = "GenerateThemeName", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/SettingsUI/Sources/Themes/GenerateGradientColors.swift b/submodules/TelegramUI/Components/Settings/GenerateThemeName/Sources/GenerateGradientColors.swift similarity index 99% rename from submodules/SettingsUI/Sources/Themes/GenerateGradientColors.swift rename to submodules/TelegramUI/Components/Settings/GenerateThemeName/Sources/GenerateGradientColors.swift index 48d4c93dc9..d9af1c22bf 100644 --- a/submodules/SettingsUI/Sources/Themes/GenerateGradientColors.swift +++ b/submodules/TelegramUI/Components/Settings/GenerateThemeName/Sources/GenerateGradientColors.swift @@ -302,7 +302,7 @@ private let colorPairs: [(UInt32, UInt32)] = [ (0x00416a, 0xe4e5e6) ] -func generateGradientColors(color: UIColor) -> (UIColor, UIColor) { +public func generateGradientColors(color: UIColor) -> (UIColor, UIColor) { var nearest: (colors: (lhs: UInt32, rhs: UInt32), distance: Int32)? for (lhs, rhs) in colorPairs { let lhsDistance = color.distance(to: UIColor(rgb: lhs)) diff --git a/submodules/SettingsUI/Sources/Themes/GenerateThemeName.swift b/submodules/TelegramUI/Components/Settings/GenerateThemeName/Sources/GenerateThemeName.swift similarity index 98% rename from submodules/SettingsUI/Sources/Themes/GenerateThemeName.swift rename to submodules/TelegramUI/Components/Settings/GenerateThemeName/Sources/GenerateThemeName.swift index 7c54bdf1f7..54df9cbfae 100644 --- a/submodules/SettingsUI/Sources/Themes/GenerateThemeName.swift +++ b/submodules/TelegramUI/Components/Settings/GenerateThemeName/Sources/GenerateThemeName.swift @@ -297,7 +297,7 @@ private let subjectives: [String] = [ "Zone" ] -func generateThemeName(accentColor: UIColor) -> String { +public func generateThemeName(accentColor: UIColor) -> String { var nearest: (color: UInt32, distance: Int32)? for (color, _) in colors { let distance = accentColor.distance(to: UIColor(rgb: color)) diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD index 182be8d78c..7d60b8c35c 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/BUILD @@ -45,6 +45,8 @@ swift_library( "//submodules/WallpaperResources", "//submodules/MediaPickerUI", "//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen", + "//submodules/TelegramUI/Components/Settings/WallpaperGridScreen", + "//submodules/TelegramUI/Components/Settings/BoostLevelIconComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift index ec052c8756..0ba287783d 100644 --- a/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift +++ b/submodules/TelegramUI/Components/Settings/PeerNameColorScreen/Sources/ChannelAppearanceScreen.swift @@ -32,6 +32,8 @@ import ComponentDisplayAdapters import WallpaperResources import MediaPickerUI import WallpaperGalleryScreen +import WallpaperGridScreen +import BoostLevelIconComponent private final class EmojiActionIconComponent: Component { let context: AccountContext @@ -127,107 +129,6 @@ private final class EmojiActionIconComponent: Component { } } -public func generateDisclosureActionBoostLevelBadgeImage(text: String) -> UIImage { - let attributedText = NSAttributedString(string: text, attributes: [ - .font: Font.medium(12.0), - .foregroundColor: UIColor.white - ]) - let bounds = attributedText.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - let leftInset: CGFloat = 16.0 - let rightInset: CGFloat = 4.0 - let size = CGSize(width: leftInset + rightInset + ceil(bounds.width), height: 20.0) - return generateImage(size, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 6.0).cgPath) - context.clip() - - var locations: [CGFloat] = [0.0, 1.0] - let colors: [CGColor] = [UIColor(rgb: 0x9076FF).cgColor, UIColor(rgb: 0xB86DEA).cgColor] - let colorSpace = CGColorSpaceCreateDeviceRGB() - let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! - context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions()) - - context.resetClip() - - UIGraphicsPushContext(context) - - if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) { - let imageFit: CGFloat = 14.0 - let imageSize = image.size.aspectFitted(CGSize(width: imageFit, height: imageFit)) - let imageRect = CGRect(origin: CGPoint(x: 2.0, y: UIScreenPixel + floorToScreenPixels((size.height - imageSize.height) * 0.5)), size: imageSize) - image.draw(in: imageRect) - } - - attributedText.draw(at: CGPoint(x: leftInset, y: floorToScreenPixels((size.height - bounds.height) * 0.5))) - - UIGraphicsPopContext() - })! -} - -private final class BoostLevelIconComponent: Component { - let strings: PresentationStrings - let level: Int - - init( - strings: PresentationStrings, - level: Int - ) { - self.strings = strings - self.level = level - } - - static func ==(lhs: BoostLevelIconComponent, rhs: BoostLevelIconComponent) -> Bool { - if lhs.strings !== rhs.strings { - return false - } - if lhs.level != rhs.level { - return false - } - return true - } - - final class View: UIView { - private let imageView: UIImageView - - private var component: BoostLevelIconComponent? - - override init(frame: CGRect) { - self.imageView = UIImageView() - - super.init(frame: frame) - - self.addSubview(self.imageView) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(component: BoostLevelIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - if self.component != component { - //TODO:localize - self.imageView.image = generateDisclosureActionBoostLevelBadgeImage(text: "Level \(component.level)") - } - self.component = component - - if let image = self.imageView.image { - self.imageView.frame = CGRect(origin: CGPoint(), size: image.size) - return image.size - } else { - return CGSize(width: 1.0, height: 20.0) - } - } - } - - func makeView() -> View { - return View(frame: CGRect()) - } - - func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { - return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) - } -} - final class ChannelAppearanceScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment @@ -255,11 +156,13 @@ final class ChannelAppearanceScreenComponent: Component { private final class ContentsData { let peer: EnginePeer? + let wallpaper: TelegramWallpaper? let subscriberCount: Int? let availableThemes: [TelegramTheme] - init(peer: EnginePeer?, subscriberCount: Int?, availableThemes: [TelegramTheme]) { + init(peer: EnginePeer?, wallpaper: TelegramWallpaper?, subscriberCount: Int?, availableThemes: [TelegramTheme]) { self.peer = peer + self.wallpaper = wallpaper self.subscriberCount = subscriberCount self.availableThemes = availableThemes } @@ -268,14 +171,16 @@ final class ChannelAppearanceScreenComponent: Component { return combineLatest( context.engine.data.subscribe( TelegramEngine.EngineData.Item.Peer.Peer(id: peerId), - TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: peerId) + TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: peerId), + TelegramEngine.EngineData.Item.Peer.Wallpaper(id: peerId) ), telegramThemes(postbox: context.account.postbox, network: context.account.network, accountManager: context.sharedContext.accountManager) ) |> map { peerData, cloudThemes -> ContentsData in - let (peer, subscriberCount) = peerData + let (peer, subscriberCount, wallpaper) = peerData return ContentsData( peer: peer, + wallpaper: wallpaper, subscriberCount: subscriberCount, availableThemes: cloudThemes ) @@ -295,15 +200,17 @@ final class ChannelAppearanceScreenComponent: Component { var replyFileId: Int64? var backgroundFileId: Int64? var emojiStatus: PeerEmojiStatus? + var wallpaper: TelegramWallpaper? var hasChanges: Bool - init(nameColor: PeerNameColor, profileColor: PeerNameColor?, replyFileId: Int64?, backgroundFileId: Int64?, emojiStatus: PeerEmojiStatus?, hasChanges: Bool) { + init(nameColor: PeerNameColor, profileColor: PeerNameColor?, replyFileId: Int64?, backgroundFileId: Int64?, emojiStatus: PeerEmojiStatus?, wallpaper: TelegramWallpaper?, hasChanges: Bool) { self.nameColor = nameColor self.profileColor = profileColor self.replyFileId = replyFileId self.backgroundFileId = backgroundFileId self.emojiStatus = emojiStatus + self.wallpaper = wallpaper self.hasChanges = hasChanges } } @@ -339,6 +246,7 @@ final class ChannelAppearanceScreenComponent: Component { private var updatedPeerProfileColor: PeerNameColor?? private var updatedPeerProfileEmoji: Int64?? private var updatedPeerStatus: PeerEmojiStatus?? + private var updatedPeerWallpaper: WallpaperSelectionResult? private var requiredBoostSubject: BoostSubject? @@ -481,12 +389,32 @@ final class ChannelAppearanceScreenComponent: Component { hasChanges = true } + let wallpaper: TelegramWallpaper? + if let updatedPeerWallpaper = self.updatedPeerWallpaper { + switch updatedPeerWallpaper { + case .remove: + 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 + } + } else { + wallpaper = contentsData.wallpaper + } + return ResolvedState( nameColor: nameColor, profileColor: profileColor, replyFileId: replyFileId, backgroundFileId: backgroundFileId, emojiStatus: emojiStatus, + wallpaper: wallpaper, hasChanges: hasChanges ) } @@ -594,55 +522,37 @@ final class ChannelAppearanceScreenComponent: Component { } private func openCustomWallpaperSetup() { - guard let _ = self.component, let contentsData = self.contentsData else { + guard let component = self.component, let contentsData = self.contentsData, let peer = contentsData.peer, let premiumConfiguration = self.premiumConfiguration, let boostStatus = self.boostStatus, let resolvedState = self.resolveState() else { return } -// let dismissControllers = { [weak self] in -// if let self, let navigationController = self.controller?.navigationController as? NavigationController { -// let controllers = navigationController.viewControllers.filter({ controller in -// if controller is WallpaperGalleryController || controller is AttachmentController || controller is PeerInfoScreenImpl { -// return false -// } -// return true -// }) -// navigationController.setViewControllers(controllers, animated: true) -// } -// } -// var openWallpaperPickerImpl: ((Bool) -> Void)? - let openWallpaperPicker: (Bool) -> Void = { [weak self] animateAppearance in - guard let self, let component = self.component, let peer = contentsData.peer else { + let level = boostStatus.level + let requiredWallpaperLevel = Int(BoostSubject.wallpaper.requiredLevel(premiumConfiguration)) + let requiredCustomWallpaperLevel = Int(BoostSubject.customWallpaper.requiredLevel(premiumConfiguration)) + + let selectedWallpaper = resolvedState.wallpaper + + let controller = ThemeGridController( + context: component.context, + 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 { return } - let controller = wallpaperMediaPickerController( - context: component.context, - updatedPresentationData: nil, - peer: peer, - animateAppearance: true, - completion: { [weak self] _, result in - guard let self, let component = self.component, let asset = result as? PHAsset else { - return - } - let controller = WallpaperGalleryController(context: component.context, source: .asset(asset), mode: .peer(peer, false)) - controller.navigationPresentation = .modal - controller.apply = { wallpaper, options, editedImage, cropRect, brightness, forBoth in -// if let strongSelf = self { -// uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: peer.id, forBoth: forBoth, completion: { -// Queue.mainQueue().after(0.3, { -// dismissControllers() -// }) -// }) -// } - } - self.environment?.controller()?.push(controller) - }, - openColors: { + self.updatedPeerWallpaper = result + switch result { + case let .emoticon(emoticon): + if let selectedTheme = contentsData.availableThemes.first(where: { $0.emoticon == emoticon }) { + self.currentTheme = .cloud(PresentationCloudTheme(theme: selectedTheme, resolvedWallpaper: nil, creatorAccountId: nil)) } - ) - controller.navigationPresentation = .flatModal - self.environment?.controller()?.push(controller) + case .remove: + self.currentTheme = .builtin(.dayClassic) + case .custom: + self.currentTheme = .builtin(.dayClassic) + } + self.state?.updated(transition: .spring(duration: 0.4)) } -// openWallpaperPickerImpl = openWallpaperPicker - openWallpaperPicker(true) + self.environment?.controller()?.push(controller) } private enum EmojiSetupSubject { @@ -891,8 +801,12 @@ final class ChannelAppearanceScreenComponent: Component { } } - if self.currentTheme != nil && self.currentTheme != chatThemes.first { - requiredBoostSubject = .wallpaper + if resolvedState.wallpaper != nil { + if self.currentTheme != chatThemes.first { + requiredBoostSubject = .wallpaper + } else { + requiredBoostSubject = .customWallpaper + } } if case let .user(user) = peer { @@ -951,7 +865,7 @@ final class ChannelAppearanceScreenComponent: Component { )), maximumNumberOfLines: 0 )))) - if replyFileId != nil, let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelNameIconLevel { + if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelNameIconLevel { replyLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( strings: environment.strings, level: replyIconLevel @@ -1057,7 +971,7 @@ final class ChannelAppearanceScreenComponent: Component { )), maximumNumberOfLines: 0 )))) - if currentTheme != chatThemes[0], let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelCustomWallpaperLevel { + if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelCustomWallpaperLevel { wallpaperLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( strings: environment.strings, level: themeLevel @@ -1085,6 +999,7 @@ final class ChannelAppearanceScreenComponent: Component { strings: environment.strings, sectionId: 0, themes: chatThemes, + firstIsNone: true, animatedEmojiStickers: component.context.animatedEmojiStickers, themeSpecificAccentColors: [:], themeSpecificChatWallpapers: [:], @@ -1096,6 +1011,11 @@ final class ChannelAppearanceScreenComponent: Component { return } self.currentTheme = value + if case .builtin = value { + self.updatedPeerWallpaper = .remove + } else { + self.updatedPeerWallpaper = .emoticon(value.emoticon ?? "") + } self.state?.updated(transition: .spring(duration: 0.4)) }, contextAction: nil @@ -1138,7 +1058,7 @@ final class ChannelAppearanceScreenComponent: Component { )), maximumNumberOfLines: 0 )))) - if backgroundFileId != nil, let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelProfileIconLevel { + if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelProfileIconLevel { profileLogoContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( strings: environment.strings, level: profileIconLevel @@ -1249,7 +1169,7 @@ final class ChannelAppearanceScreenComponent: Component { )), maximumNumberOfLines: 0 )))) - if emojiStatus != nil, let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelEmojiStatusLevel { + if let boostLevel = self.boostLevel, boostLevel < premiumConfiguration.minChannelEmojiStatusLevel { emojiStatusContents.append(AnyComponentWithIdentity(id: 1, component: AnyComponent(BoostLevelIconComponent( strings: environment.strings, level: emojiStatusLevel @@ -1472,8 +1392,10 @@ public class ChannelAppearanceScreen: ViewControllerComponentContainer { peerId: peerId ), navigationBarAppearance: .default, theme: .default, updatedPresentationData: updatedPresentationData) + let presentationData = context.sharedContext.currentPresentationData.with { $0 } //TODO:localize self.title = "Appearance" + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.ready.set(.never()) diff --git a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD index fcb10d3032..d6fd0655ff 100644 --- a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD +++ b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/BUILD @@ -18,8 +18,11 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/GradientBackground", "//submodules/WallpaperResources", + "//submodules/StickerResources", "//submodules/AccountContext", "//submodules/RadialStatusNode", + "//submodules/AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift index b6916a0fe7..31beb5d74b 100644 --- a/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift +++ b/submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode/Sources/SettingsThemeWallpaperNode.swift @@ -10,6 +10,9 @@ import AccountContext import RadialStatusNode import WallpaperResources import GradientBackground +import StickerResources +import AnimatedStickerNode +import TelegramAnimatedStickerNode private func whiteColorImage(theme: PresentationTheme, color: UIColor) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { return .single({ arguments in @@ -46,12 +49,19 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { public var wallpaper: TelegramWallpaper? private var arguments: PatternWallpaperArguments? + private var emojiFile: TelegramMediaFile? + public let buttonNode = HighlightTrackingButtonNode() public let backgroundNode = ASImageNode() public let imageNode = TransformImageNode() private var gradientNode: GradientBackgroundNode? private let statusNode: RadialStatusNode + private let emojiContainerNode: ASDisplayNode + private let emojiImageNode: TransformImageNode + private var animatedStickerNode: AnimatedStickerNode? + private let stickerFetchedDisposable = MetaDisposable() + public var pressed: (() -> Void)? private let displayLoading: Bool @@ -69,6 +79,9 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter) self.statusNode.isUserInteractionEnabled = false + self.emojiContainerNode = ASDisplayNode() + self.emojiImageNode = TransformImageNode() + super.init() self.addSubnode(self.backgroundNode) @@ -76,11 +89,34 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { self.addSubnode(self.buttonNode) self.addSubnode(self.statusNode) + self.addSubnode(self.emojiContainerNode) +// self.emojiContainerNode.addSubnode(self.emojiNode) + self.emojiContainerNode.addSubnode(self.emojiImageNode) + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + + var firstTime = true + self.emojiImageNode.imageUpdated = { [weak self] image in + guard let strongSelf = self else { + return + } + if image != nil { + strongSelf.removePlaceholder(animated: !firstTime) + if firstTime { + strongSelf.emojiImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + firstTime = false + } } deinit { self.isLoadedDisposable.dispose() + self.stickerFetchedDisposable.dispose() + } + + private func removePlaceholder(animated: Bool) { + } public func setSelected(_ selected: Bool, animated: Bool = false) { @@ -114,7 +150,7 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { self.statusNode.backgroundNodeColor = color } - public func setWallpaper(context: AccountContext, wallpaper: TelegramWallpaper, selected: Bool, size: CGSize, cornerRadius: CGFloat = 0.0, synchronousLoad: Bool = false) { + public func setWallpaper(context: AccountContext, theme: PresentationTheme? = nil, wallpaper: TelegramWallpaper, isEmpty: Bool = false, emojiFile: TelegramMediaFile? = nil, selected: Bool, size: CGSize, cornerRadius: CGFloat = 0.0, synchronousLoad: Bool = false) { self.buttonNode.frame = CGRect(origin: CGPoint(), size: size) self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) self.imageNode.frame = CGRect(origin: CGPoint(), size: size) @@ -175,6 +211,11 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { self.backgroundNode.backgroundColor = UIColor(rgb: colors[0]) } } + + if isEmpty, let theme { + self.backgroundNode.image = nil + self.backgroundNode.backgroundColor = theme.list.mediaPlaceholderColor + } if let gradientNode = self.gradientNode { gradientNode.frame = CGRect(origin: CGPoint(), size: size) @@ -186,7 +227,7 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { let corners = ImageCorners(radius: cornerRadius) - if self.wallpaper != wallpaper { + if self.wallpaper != wallpaper && !isEmpty { self.wallpaper = wallpaper switch wallpaper { case .builtin: @@ -300,6 +341,51 @@ public final class SettingsThemeWallpaperNode: ASDisplayNode { } self.setSelected(selected, animated: false) + + + self.emojiContainerNode.frame = self.backgroundNode.frame + + var emojiFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - 42.0) / 2.0), y: 98.0), size: CGSize(width: 42.0, height: 42.0)) + if isEmpty { + emojiFrame = emojiFrame.insetBy(dx: 3.0, dy: 3.0) + } + if let file = emojiFile, self.emojiFile?.id != emojiFile?.id { + self.emojiFile = file + + let imageApply = self.emojiImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: emojiFrame.size, boundingSize: emojiFrame.size, intrinsicInsets: UIEdgeInsets())) + imageApply() + self.emojiImageNode.setSignal(chatMessageStickerPackThumbnail(postbox: context.account.postbox, resource: file.resource, animated: true, nilIfEmpty: true)) + self.emojiImageNode.frame = emojiFrame + + let animatedStickerNode: AnimatedStickerNode + if let current = self.animatedStickerNode { + animatedStickerNode = current + } else { + animatedStickerNode = DefaultAnimatedStickerNodeImpl() + animatedStickerNode.started = { [weak self] in + self?.emojiImageNode.isHidden = true + } + self.animatedStickerNode = animatedStickerNode + self.emojiContainerNode.addSubnode(animatedStickerNode) + let pathPrefix = context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(file.resource.id) + animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: context.account, resource: file.resource), width: 128, height: 128, playbackMode: .still(.start), mode: .direct(cachePathPrefix: pathPrefix)) + + animatedStickerNode.anchorPoint = CGPoint(x: 0.5, y: 1.0) + } + animatedStickerNode.autoplay = true + animatedStickerNode.visibility = true + + self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: MediaResourceReference.media(media: .standalone(media: file), resource: file.resource)).start()) + +// let thumbnailDimensions = PixelDimensions(width: 512, height: 512) +// self.placeholderNode.update(backgroundColor: nil, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.2), shimmeringColor: UIColor(rgb: 0xffffff, alpha: 0.3), data: file.immediateThumbnailData, size: emojiFrame.size, enableEffect: item.context.sharedContext.energyUsageSettings.fullTranslucency, imageSize: thumbnailDimensions.cgSize) +// self.placeholderNode.frame = emojiFrame + } + + if let animatedStickerNode = self.animatedStickerNode { + animatedStickerNode.frame = emojiFrame + animatedStickerNode.updateLayout(size: emojiFrame.size) + } } @objc func buttonPressed() { diff --git a/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD new file mode 100644 index 0000000000..49b5ed0d22 --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/BUILD @@ -0,0 +1,40 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "ThemeAccentColorScreen", + module_name = "ThemeAccentColorScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/PresentationDataUtils", + "//submodules/WallpaperBackgroundNode", + "//submodules/ComponentFlow", + "//submodules/SolidRoundedButtonNode", + "//submodules/AppBundle", + "//submodules/PremiumUI", + "//submodules/WallpaperResources", + "//submodules/HexColor", + "//submodules/MergeLists", + "//submodules/ShareController", + "//submodules/GalleryUI", + "//submodules/ChatListUI", + "//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen", + "//submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode", + "//submodules/TelegramUI/Components/Settings/GenerateThemeName", + "//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift similarity index 98% rename from submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift rename to submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift index f88868b1f0..89eecec894 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorController.swift @@ -11,16 +11,17 @@ import AccountContext import PresentationDataUtils import MediaResources import WallpaperGalleryScreen +import GenerateThemeName private let randomBackgroundColors: [Int32] = [0x007aff, 0x00c2ed, 0x29b327, 0xeb6ca4, 0xf08200, 0x9472ee, 0xd33213, 0xedb400, 0x6d839e] -extension TelegramThemeSettings { +public extension TelegramThemeSettings { convenience init(baseTheme: TelegramBaseTheme, accentColor: UIColor, outgoingAccentColor: UIColor?, messageColors: [UInt32], animateMessageColors: Bool, wallpaper: TelegramWallpaper?) { self.init(baseTheme: baseTheme, accentColor: accentColor.argb, outgoingAccentColor: outgoingAccentColor?.argb, messageColors: messageColors, animateMessageColors: animateMessageColors, wallpaper: wallpaper) } } -enum ThemeAccentColorControllerMode { +public enum ThemeAccentColorControllerMode { case colors(themeReference: PresentationThemeReference, create: Bool) case background(themeReference: PresentationThemeReference) case edit(settings: TelegramThemeSettings?, theme: PresentationTheme, wallpaper: TelegramWallpaper?, generalThemeReference: PresentationThemeReference?, defaultThemeReference: PresentationThemeReference?, create: Bool, completion: (PresentationTheme, TelegramThemeSettings?) -> Void) @@ -35,8 +36,8 @@ enum ThemeAccentColorControllerMode { } } -final class ThemeAccentColorController: ViewController { - enum ResultMode { +public final class ThemeAccentColorController: ViewController { + public enum ResultMode { case `default` case peer(EnginePeer) } @@ -53,7 +54,7 @@ final class ThemeAccentColorController: ViewController { } private let _ready = Promise() - override public var ready: Promise { + public override var ready: Promise { return self._ready } @@ -61,9 +62,9 @@ final class ThemeAccentColorController: ViewController { private var applyDisposable = MetaDisposable() - var completion: (() -> Void)? + public var completion: (() -> Void)? - init(context: AccountContext, mode: ThemeAccentColorControllerMode, resultMode: ResultMode = .default) { + public init(context: AccountContext, mode: ThemeAccentColorControllerMode, resultMode: ResultMode = .default) { self.context = context self.mode = mode self.resultMode = resultMode @@ -136,13 +137,13 @@ final class ThemeAccentColorController: ViewController { self.dismiss() } - override func viewDidAppear(_ animated: Bool) { + public override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.controllerNode.animateWallpaperAppeared() } - override func loadDisplayNode() { + public override func loadDisplayNode() { super.loadDisplayNode() let theme: PresentationTheme @@ -631,7 +632,7 @@ final class ThemeAccentColorController: ViewController { self.displayNodeDidLoad() } - override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift similarity index 100% rename from submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift rename to submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeAccentColorControllerNode.swift diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorSegmentedTitleView.swift b/submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeColorSegmentedTitleView.swift similarity index 100% rename from submodules/SettingsUI/Sources/Themes/ThemeColorSegmentedTitleView.swift rename to submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen/Sources/ThemeColorSegmentedTitleView.swift diff --git a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift index 2b0dc7d7f4..f32f429e33 100644 --- a/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift +++ b/submodules/TelegramUI/Components/Settings/ThemeCarouselItem/Sources/ThemeCarouselItem.swift @@ -27,6 +27,7 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable { let themeReference: PresentationThemeReference let nightMode: Bool let channelMode: Bool + let firstIsNone: Bool let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] let themeSpecificChatWallpapers: [Int64: TelegramWallpaper] var selected: Bool @@ -54,6 +55,9 @@ 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 } @@ -80,7 +84,11 @@ private struct ThemeCarouselThemeEntry: Comparable, Identifiable { } 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) + 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) } } @@ -89,6 +97,7 @@ public class ThemeCarouselThemeIconItem: ListViewItem { public let context: AccountContext public let emojiFile: TelegramMediaFile? public let themeReference: PresentationThemeReference + public let isNone: Bool public let nightMode: Bool public let channelMode: Bool public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] @@ -100,12 +109,13 @@ public class ThemeCarouselThemeIconItem: ListViewItem { public let action: (PresentationThemeReference) -> Void public let 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)?) { + 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)?) { 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 @@ -309,10 +319,14 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode { } override func selected() { - let wasSelected = self.item?.selected ?? false + guard let item = self.item else { + return + } + + let wasSelected = item.selected super.selected() - if let animatedStickerNode = self.animatedStickerNode { + if let animatedStickerNode = self.animatedStickerNode, !item.isNone { Queue.mainQueue().after(0.1) { if !wasSelected { animatedStickerNode.seekTo(.frameIndex(0)) @@ -336,6 +350,7 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode { } func asyncLayout() -> (ThemeCarouselThemeIconItem, ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) { + let makeTextLayout = TextNode.asyncLayout(self.textNode) let makeEmojiLayout = TextNode.asyncLayout(self.emojiNode) let makeImageLayout = self.imageNode.asyncLayout() @@ -375,6 +390,8 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode { string = "🎨" } + 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())) @@ -392,8 +409,13 @@ private final class ThemeCarouselThemeItemIconNode : ListViewItemNode { 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 + if case .builtin = themeReference, item.isNone { + strongSelf.imageNode.reset() + strongSelf.imageNode.backgroundColor = item.theme.list.mediaPlaceholderColor //item.theme.list.disclosureArrowColor.withAlphaComponent(0.2) + } else { + 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 + } } if updatedTheme || updatedSelected { @@ -413,8 +435,14 @@ 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)) @@ -525,6 +553,7 @@ public class ThemeCarouselThemeItem: ListViewItem, ItemListItem, ListItemCompone public let theme: PresentationTheme public let strings: PresentationStrings public let themes: [PresentationThemeReference] + public let firstIsNone : Bool public let animatedEmojiStickers: [String: [StickerPackItem]] public let themeSpecificAccentColors: [Int64: PresentationThemeAccentColor] public let themeSpecificChatWallpapers: [Int64: TelegramWallpaper] @@ -535,11 +564,12 @@ public class ThemeCarouselThemeItem: ListViewItem, ItemListItem, ListItemCompone public let contextAction: ((PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void)? public let tag: ItemListItemTag? - public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, sectionId: ItemListSectionId, themes: [PresentationThemeReference], 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], 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) { self.context = context self.theme = theme self.strings = strings self.themes = themes + self.firstIsNone = firstIsNone self.animatedEmojiStickers = animatedEmojiStickers self.themeSpecificAccentColors = themeSpecificAccentColors self.themeSpecificChatWallpapers = themeSpecificChatWallpapers @@ -841,13 +871,19 @@ public class ThemeCarouselThemeItemNode: ListViewItemNode, ItemListItemNode { if selected { hasCurrentTheme = true } - 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)) + + 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: 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)) index += 1 } if !hasCurrentTheme { - entries.append(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: nil)) + 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)) } let action: (PresentationThemeReference) -> Void = { [weak self] themeReference in diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD index 8fdf112188..882bfd1377 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/BUILD @@ -31,6 +31,7 @@ swift_library( "//submodules/CounterContollerTitleView", "//submodules/LegacyMediaPickerUI", "//submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode", + "//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift index 47df37785d..c306b30e67 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryController.swift @@ -234,6 +234,8 @@ public class WallpaperGalleryController: ViewController { private var savedPatternWallpaper: TelegramWallpaper? private var savedPatternIntensity: Int32? + public var requiredLevel: Int? + public init(context: AccountContext, source: WallpaperListSource, mode: Mode = .default) { self.context = context self.source = source @@ -506,6 +508,7 @@ public class WallpaperGalleryController: ViewController { } let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings, doneButtonType: doneButtonType) + toolbarNode.requiredLevel = self.requiredLevel switch self.source { case .asset, .contextResult: toolbarNode.dark = false diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift index 5bd41a827d..85b35b03b3 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryItem.swift @@ -1371,6 +1371,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let centerOffset: CGFloat = 32.0 + var isPattern = false if let entry = self.entry { switch entry { case .asset: @@ -1417,6 +1418,8 @@ final class WallpaperGalleryItemNode: GalleryItemNode { colorsAlpha = 1.0 case let .file(file): if file.isPattern { + isPattern = true + motionAlpha = 0.0 patternAlpha = 1.0 @@ -1453,6 +1456,13 @@ final class WallpaperGalleryItemNode: GalleryItemNode { if let mode = self.mode, case let .peer(peer, _) = mode, case .channel = peer { motionAlpha = 0.0 + if isPattern { + patternAlpha = 0.0 + colorsAlpha = 0.0 + blurAlpha = 0.0 + playAlpha = 0.0 + self.shareButtonNode.isHidden = true + } blurFrame = centerButtonFrame } @@ -1487,8 +1497,12 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.nativeNode.updateBubbleTheme(bubbleTheme: self.presentationData.theme, bubbleCorners: self.presentationData.chatBubbleCorners) var bottomInset: CGFloat = 132.0 - if let mode = self.mode, case let .peer(peer, _) = mode, case .user = peer { - bottomInset += 58.0 + if let mode = self.mode, case let .peer(peer, _) = mode { + if case .user = peer { + bottomInset += 58.0 + } else if case .channel = peer, let entry = self.entry, case let .wallpaper(wallpaper, _) = entry, case let .file(file) = wallpaper, file.isPattern { + bottomInset -= 42.0 + } } var items: [ListViewItem] = [] diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryToolbarNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryToolbarNode.swift index 268b1abd5c..0ce64b7b9c 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryToolbarNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen/Sources/WallpaperGalleryToolbarNode.swift @@ -4,6 +4,8 @@ import AsyncDisplayKit import Display import TelegramPresentationData import ManagedAnimationNode +import ComponentFlow +import PremiumLockButtonSubtitleComponent public enum WallpaperGalleryToolbarCancelButtonType { case cancel @@ -33,9 +35,13 @@ public protocol WallpaperGalleryToolbar: ASDisplayNode { public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryToolbar { class ButtonNode: ASDisplayNode { + private let strings: PresentationStrings + private let doneButton = HighlightTrackingButtonNode() private var doneButtonBackgroundNode: ASDisplayNode private let doneButtonTitleNode: ImmediateTextNode + private var doneButtonSubtitle: ComponentView? + private let doneButtonSolidBackgroundNode: ASDisplayNode private let doneButtonSolidTitleNode: ImmediateTextNode @@ -49,7 +55,11 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT } } - override init() { + var requiredLevel: Int? + + init(strings: PresentationStrings) { + self.strings = strings + self.doneButtonBackgroundNode = WallpaperLightButtonBackgroundNode() self.doneButtonBackgroundNode.cornerRadius = 14.0 @@ -102,6 +112,9 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT strongSelf.doneButtonBackgroundNode.alpha = 0.55 strongSelf.doneButtonTitleNode.layer.removeAnimation(forKey: "opacity") strongSelf.doneButtonTitleNode.alpha = 0.55 + + strongSelf.doneButtonSubtitle?.view?.layer.removeAnimation(forKey: "opacity") + strongSelf.doneButtonSubtitle?.view?.alpha = 0.55 } } else { if strongSelf.isSolid { @@ -114,6 +127,9 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT strongSelf.doneButtonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) strongSelf.doneButtonTitleNode.alpha = 1.0 strongSelf.doneButtonTitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) + + strongSelf.doneButtonSubtitle?.view?.alpha = 1.0 + strongSelf.doneButtonSubtitle?.view?.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2) } } } @@ -168,7 +184,45 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT let titleOriginX = floorToScreenPixels((bounds.width - totalWidth) / 2.0) self.animationNode.frame = CGRect(origin: CGPoint(x: titleOriginX, y: floorToScreenPixels((bounds.height - iconSize.height) / 2.0)), size: iconSize) - self.doneButtonTitleNode.frame = CGRect(origin: CGPoint(x: titleOriginX + totalWidth - doneTitleSize.width, y: floorToScreenPixels((bounds.height - doneTitleSize.height) / 2.0)), size: doneTitleSize).offsetBy(dx: bounds.minX, dy: bounds.minY) + + var titleFrame = CGRect(origin: CGPoint(x: titleOriginX + totalWidth - doneTitleSize.width, y: floorToScreenPixels((bounds.height - doneTitleSize.height) / 2.0)), size: doneTitleSize).offsetBy(dx: bounds.minX, dy: bounds.minY) + + if let requiredLevel = self.requiredLevel { + let subtitle: ComponentView + if let current = self.doneButtonSubtitle { + subtitle = current + } else { + subtitle = ComponentView() + self.doneButtonSubtitle = subtitle + } + + let subtitleSize = subtitle.update( + transition: .immediate, + component: AnyComponent( + PremiumLockButtonSubtitleComponent( + count: requiredLevel, + color: UIColor(rgb: 0xffffff, alpha: 0.7), + strings: self.strings + ) + ), + environment: {}, + containerSize: size + ) + + if let view = subtitle.view { + if view.superview == nil { + view.isUserInteractionEnabled = false + self.view.addSubview(view) + } + + titleFrame.origin.y -= 8.0 + + let subtitleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + 3.0), size: subtitleSize) + view.frame = subtitleFrame + } + } + + self.doneButtonTitleNode.frame = titleFrame let _ = self.doneButtonSolidTitleNode.updateLayout(constrainedSize) self.doneButtonSolidTitleNode.frame = self.doneButtonTitleNode.frame @@ -223,18 +277,27 @@ public final class WallpaperGalleryToolbarNode: ASDisplayNode, WallpaperGalleryT } } - private let applyButton = ButtonNode() - private let applyForBothButton = ButtonNode() + private let applyButton: ButtonNode + private let applyForBothButton: ButtonNode public var cancel: (() -> Void)? public var done: ((Bool) -> Void)? + var requiredLevel: Int? { + didSet { + self.applyButton.requiredLevel = self.requiredLevel + } + } + public init(theme: PresentationTheme, strings: PresentationStrings, cancelButtonType: WallpaperGalleryToolbarCancelButtonType = .cancel, doneButtonType: WallpaperGalleryToolbarDoneButtonType = .set) { self.theme = theme self.strings = strings self.cancelButtonType = cancelButtonType self.doneButtonType = doneButtonType + self.applyButton = ButtonNode(strings: strings) + self.applyForBothButton = ButtonNode(strings: strings) + super.init() self.addSubnode(self.applyButton) diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/BUILD b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/BUILD new file mode 100644 index 0000000000..e88a5216fd --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/BUILD @@ -0,0 +1,44 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "WallpaperGridScreen", + module_name = "WallpaperGridScreen", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/Display", + "//submodules/Postbox", + "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/TelegramPresentationData", + "//submodules/AccountContext", + "//submodules/PresentationDataUtils", + "//submodules/WallpaperBackgroundNode", + "//submodules/ComponentFlow", + "//submodules/SolidRoundedButtonNode", + "//submodules/AppBundle", + "//submodules/PremiumUI", + "//submodules/WallpaperResources", + "//submodules/HexColor", + "//submodules/MergeLists", + "//submodules/ShareController", + "//submodules/GalleryUI", + "//submodules/GridMessageSelectionNode", + "//submodules/SearchUI", + "//submodules/MediaPickerUI", + "//submodules/ItemListPeerActionItem", + "//submodules/TelegramUI/Components/Settings/WallpaperGalleryScreen", + "//submodules/TelegramUI/Components/Settings/SettingsThemeWallpaperNode", + "//submodules/TelegramUI/Components/Settings/ThemeAccentColorScreen", + "//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent", + "//submodules/TelegramUI/Components/Settings/BoostLevelIconComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift similarity index 99% rename from submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift rename to submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift index 0e342ae9cb..d7a193bade 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift @@ -10,6 +10,7 @@ import TelegramUIPreferences import AccountContext import AttachmentUI import WallpaperGalleryScreen +import ThemeAccentColorScreen private func availableGradients(dark: Bool) -> [[UInt32]] { if dark { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerItem.swift similarity index 100% rename from submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerItem.swift rename to submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerItem.swift diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift similarity index 99% rename from submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift rename to submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift index 9b4d211761..e61e5e7906 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridControllerNode.swift @@ -378,7 +378,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { let makeColorLayout = self.customColorItemNode.asyncLayout() let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: listInsets.left, rightInset: listInsets.right, availableHeight: layout.size.height) let (colorLayout, colorApply) = makeColorLayout(self.customColorItem, params, ItemListNeighbors(top: .none, bottom: .none)) - colorApply() + colorApply(false) transition.updateFrame(node: self.topBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: buttonInset + 500.0))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonInset - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift similarity index 73% rename from submodules/SettingsUI/Sources/Themes/ThemeGridController.swift rename to submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift index b9f9dbdb53..efee407998 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridController.swift @@ -17,7 +17,17 @@ import PresentationDataUtils import MediaPickerUI import WallpaperGalleryScreen +public enum WallpaperSelectionResult { + case remove + case emoticon(String) + case custom(wallpaperEntry: WallpaperGalleryEntry, options: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?) +} + public final class ThemeGridController: ViewController { + public enum Mode { + case generic + case peer(EnginePeer, [TelegramTheme], TelegramWallpaper?, Int?, Int?) + } private var controllerNode: ThemeGridControllerNode { return self.displayNode as! ThemeGridControllerNode } @@ -28,6 +38,7 @@ public final class ThemeGridController: ViewController { } private let context: AccountContext + private let mode: Mode private var presentationData: PresentationData private let presentationDataPromise = Promise() @@ -46,14 +57,24 @@ public final class ThemeGridController: ViewController { private var previousContentOffset: GridNodeVisibleContentOffset? - public init(context: AccountContext) { + public var completion: (WallpaperSelectionResult) -> Void = { _ in } + + public init(context: AccountContext, mode: Mode = .generic) { self.context = context + self.mode = mode + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationDataPromise.set(.single(self.presentationData)) super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) - self.title = self.presentationData.strings.Wallpaper_Title + switch mode { + case .generic: + self.title = self.presentationData.strings.Wallpaper_Title + case .peer: + self.title = self.presentationData.strings.Wallpaper_ChannelTitle + } + self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style @@ -81,10 +102,12 @@ public final class ThemeGridController: ViewController { } }) - self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Wallpaper_Search, activate: { [weak self] in - self?.activateSearch() - }) - self.navigationBar?.setContentNode(self.searchContentNode, animated: false) + if case .generic = mode { + self.searchContentNode = NavigationBarSearchContentNode(theme: self.presentationData.theme, placeholder: self.presentationData.strings.Wallpaper_Search, activate: { [weak self] in + self?.activateSearch() + }) + self.navigationBar?.setContentNode(self.searchContentNode, animated: false) + } } required public init(coder aDecoder: NSCoder) { @@ -99,12 +122,14 @@ public final class ThemeGridController: ViewController { self.title = self.presentationData.strings.Wallpaper_Title self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) - if let isEmpty = self.isEmpty, isEmpty { - } else { - if self.editingMode { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) + if case .generic = self.mode { + if let isEmpty = self.isEmpty, isEmpty { } else { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) + if self.editingMode { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.donePressed)) + } else { + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(self.editPressed)) + } } } @@ -118,35 +143,29 @@ public final class ThemeGridController: ViewController { } public override func loadDisplayNode() { - self.displayNode = ThemeGridControllerNode(context: self.context, presentationData: self.presentationData, presentPreviewController: { [weak self] source in - if let strongSelf = self { - let controller = WallpaperGalleryController(context: strongSelf.context, source: source) - controller.apply = { [weak self, weak controller] wallpaper, options, editedImage, cropRect, brightness, _ in - if let strongSelf = self { - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in - if let strongSelf = self { - strongSelf.deactivateSearch(animated: false) - strongSelf.controllerNode.scrollToTop(animated: false) - } - if let controller = controller { - switch wallpaper { - case .asset, .contextResult: - controller.dismiss(animated: true) - default: - break - } - } - }) - } - } - self?.push(controller) - } - }, presentGallery: { [weak self] in + var mode: WallpaperGalleryController.Mode = .default + var requiredLevel: Int? + var requiredCustomLevel: Int? + if case let .peer(peer, _, _, requiredLevelValue, requiredCustomLevelValue) = self.mode { + mode = .peer(peer, false) + requiredLevel = requiredLevelValue + requiredCustomLevel = requiredCustomLevelValue + } + + self.displayNode = ThemeGridControllerNode(context: self.context, mode: self.mode, presentationData: self.presentationData, presentPreviewController: { [weak self] source in if let strongSelf = self { let dismissControllers = { [weak self] in if let self, let navigationController = self.navigationController as? NavigationController { - let controllers = navigationController.viewControllers.filter({ controller in - if controller is WallpaperGalleryController || controller is MediaPickerScreen { + var controllers = navigationController.viewControllers.filter({ controller in + if controller is ThemeGridController { + return false + } + return true + }) + navigationController.setViewControllers(controllers, animated: false) + + controllers = navigationController.viewControllers.filter({ controller in + if controller is WallpaperGalleryController { return false } return true @@ -155,17 +174,87 @@ public final class ThemeGridController: ViewController { } } + let controller = WallpaperGalleryController(context: strongSelf.context, source: source, mode: mode) + controller.requiredLevel = requiredLevel + controller.apply = { [weak self, weak controller] wallpaper, options, editedImage, cropRect, brightness, _ in + if let strongSelf = self { + if case .peer = mode { + var emoticon = "" + if case let .wallpaper(wallpaper, _) = wallpaper { + emoticon = wallpaper.settings?.emoticon ?? "" + } + strongSelf.completion(.emoticon(emoticon)) + dismissControllers() + } else { + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in + if let strongSelf = self { + strongSelf.deactivateSearch(animated: false) + strongSelf.controllerNode.scrollToTop(animated: false) + } + if let controller = controller { + switch wallpaper { + case .asset, .contextResult: + controller.dismiss(animated: true) + default: + break + } + } + }) + } + } + } + self?.push(controller) + } + }, presentGallery: { [weak self] in + if let strongSelf = self { + let dismissControllers = { [weak self] in + if let self, let navigationController = self.navigationController as? NavigationController { + if case .peer = mode { + var controllers = navigationController.viewControllers.filter({ controller in + if controller is ThemeGridController || controller is MediaPickerScreen { + return false + } + return true + }) + navigationController.setViewControllers(controllers, animated: false) + + controllers = navigationController.viewControllers.filter({ controller in + if controller is WallpaperGalleryController { + return false + } + return true + }) + navigationController.setViewControllers(controllers, animated: true) + } else { + let controllers = navigationController.viewControllers.filter({ controller in + if controller is WallpaperGalleryController || controller is MediaPickerScreen { + return false + } + + return true + }) + navigationController.setViewControllers(controllers, animated: true) + } + } + } + let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper)) controller.customSelection = { [weak self] _, asset in guard let strongSelf = self, let asset = asset as? PHAsset else { return } - let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset)) + let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: mode) + controller.requiredLevel = requiredCustomLevel controller.apply = { [weak self] wallpaper, options, editedImage, cropRect, brightness, _ in if let strongSelf = self { - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { + if case .peer = mode { + strongSelf.completion(.custom(wallpaperEntry: wallpaper, options: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness)) dismissControllers() - }) + } else { + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { + dismissControllers() + }) + } } } strongSelf.push(controller) @@ -182,13 +271,15 @@ public final class ThemeGridController: ViewController { if empty != strongSelf.isEmpty { strongSelf.isEmpty = empty - if empty { - strongSelf.navigationItem.setRightBarButton(nil, animated: true) - } else { - if strongSelf.editingMode { - strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.donePressed)) + if case .generic = strongSelf.mode { + if empty { + strongSelf.navigationItem.setRightBarButton(nil, animated: true) } else { - strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed)) + if strongSelf.editingMode { + strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Done, style: .done, target: strongSelf, action: #selector(strongSelf.donePressed)) + } else { + strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: strongSelf, action: #selector(strongSelf.editPressed)) + } } } } diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift new file mode 100644 index 0000000000..8c424bac35 --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerItem.swift @@ -0,0 +1,229 @@ +import Foundation +import UIKit +import Display +import TelegramCore +import SwiftSignalKit +import AsyncDisplayKit +import Postbox +import AccountContext +import GridMessageSelectionNode +import SettingsThemeWallpaperNode +import TelegramPresentationData + +private var cachedBorderImages: [String: UIImage] = [:] +private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? { + let key = "\(theme.list.itemBlocksBackgroundColor.hexString)_\(selected ? "s" + theme.list.itemAccentColor.hexString : theme.list.disclosureArrowColor.hexString)" + if let image = cachedBorderImages[key] { + return image + } else { + let image = generateImage(CGSize(width: 18.0, height: 18.0), rotatedContext: { size, context in + let bounds = CGRect(origin: CGPoint(), size: size) + context.clear(bounds) + + let lineWidth: CGFloat + if selected { + lineWidth = 2.0 + 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)) + + var accentColor = theme.list.itemAccentColor + if accentColor.rgb == 0xffffff { + accentColor = UIColor(rgb: 0x999999) + } + context.setStrokeColor(accentColor.cgColor) + } else { + context.setStrokeColor(theme.list.disclosureArrowColor.withAlphaComponent(0.4).cgColor) + lineWidth = 1.0 + } + + if bordered || selected { + context.setLineWidth(lineWidth) + context.strokeEllipse(in: bounds.insetBy(dx: 1.0 + lineWidth / 2.0, dy: 1.0 + lineWidth / 2.0)) + } + })?.stretchableImage(withLeftCapWidth: 9, topCapHeight: 9) + cachedBorderImages[key] = image + return image + } +} + +final class ThemeGridControllerItem: GridItem { + let context: AccountContext + let theme: PresentationTheme? + let wallpaper: TelegramWallpaper + let wallpaperId: ThemeGridControllerEntry.StableId + let isEmpty: Bool + let emojiFile: TelegramMediaFile? + let channelMode: Bool + let index: Int + let editable: Bool + let selected: Bool + let interaction: ThemeGridControllerInteraction + + let section: GridSection? = nil + + init(context: AccountContext, theme: PresentationTheme? = nil, wallpaper: TelegramWallpaper, wallpaperId: ThemeGridControllerEntry.StableId, isEmpty: Bool = false, emojiFile: TelegramMediaFile? = nil, channelMode: Bool = false, index: Int, editable: Bool, selected: Bool, interaction: ThemeGridControllerInteraction) { + self.context = context + self.theme = theme + self.wallpaper = wallpaper + self.wallpaperId = wallpaperId + self.isEmpty = isEmpty + self.emojiFile = emojiFile + self.channelMode = channelMode + self.index = index + self.editable = editable + self.selected = selected + self.interaction = interaction + } + + func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode { + let node = ThemeGridControllerItemNode() + node.setup(item: self, synchronousLoad: synchronousLoad) + return node + } + + func update(node: GridItemNode) { + guard let node = node as? ThemeGridControllerItemNode else { + assertionFailure() + return + } + node.setup(item: self, synchronousLoad: false) + } +} + +final class ThemeGridControllerItemNode: GridItemNode { + private let wallpaperNode: SettingsThemeWallpaperNode + private var selectionNode: GridMessageSelectionNode? + private var selectionBorderNode: ASImageNode? + + private var textNode: ImmediateTextNode? + + private var item: ThemeGridControllerItem? + + override init() { + self.wallpaperNode = SettingsThemeWallpaperNode(displayLoading: false) + + super.init() + + self.clipsToBounds = true + + self.addSubnode(self.wallpaperNode) + } + + override func didLoad() { + super.didLoad() + + self.view.layer.cornerRadius = 10.0 + + self.view.isExclusiveTouch = true + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + + func setup(item: ThemeGridControllerItem, synchronousLoad: Bool) { + self.item = item + self.updateSelectionState(animated: false) + + if item.channelMode, item.selected, let theme = item.theme { + let selectionBorderNode: ASImageNode + if let current = self.selectionBorderNode { + selectionBorderNode = current + } else { + selectionBorderNode = ASImageNode() + selectionBorderNode.displaysAsynchronously = false + self.selectionBorderNode = selectionBorderNode + + self.addSubnode(selectionBorderNode) + } + + selectionBorderNode.image = generateBorderImage(theme: theme, bordered: true, selected: true) + } else { + self.selectionBorderNode?.removeFromSupernode() + } + + if item.channelMode, item.isEmpty, let theme = item.theme { + let textNode: ImmediateTextNode + if let current = self.textNode { + textNode = current + } else { + textNode = ImmediateTextNode() + textNode.maximumNumberOfLines = 2 + textNode.textAlignment = .center + self.textNode = textNode + + self.addSubnode(textNode) + } + + let strings = item.context.sharedContext.currentPresentationData.with { $0 }.strings + textNode.attributedText = NSAttributedString(string: strings.Wallpaper_NoWallpaper, font: Font.regular(15.0), textColor: theme.list.itemSecondaryTextColor) + } + + self.setNeedsLayout() + } + + @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + if let item = self.item, !item.isEmpty { + item.interaction.openWallpaper(item.wallpaper) + } + } + } + + func updateSelectionState(animated: Bool) { + if let item = self.item { + let (editing, selectedIds) = item.interaction.selectionState + + if editing && item.editable { + let selected = selectedIds.contains(item.wallpaperId) + + if let selectionNode = self.selectionNode { + selectionNode.updateSelected(selected, animated: animated) + selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) + } else { + let theme = item.context.sharedContext.currentPresentationData.with { $0 }.theme + let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in + if let strongSelf = self { + strongSelf.item?.interaction.toggleWallpaperSelection(item.wallpaperId, value) + } + }) + + selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size) + self.addSubnode(selectionNode) + self.selectionNode = selectionNode + selectionNode.updateSelected(selected, animated: false) + if animated { + selectionNode.animateIn() + } + } + } + else { + if let selectionNode = self.selectionNode { + self.selectionNode = nil + if animated { + selectionNode.animateOut { [weak selectionNode] in + selectionNode?.removeFromSupernode() + } + } else { + selectionNode.removeFromSupernode() + } + } + } + } + } + + override func layout() { + super.layout() + + let bounds = self.bounds + if let item = self.item { + self.wallpaperNode.setWallpaper(context: item.context, theme: item.theme, wallpaper: item.wallpaper, isEmpty: item.isEmpty, emojiFile: item.emojiFile, selected: !item.channelMode && item.selected, size: bounds.size, synchronousLoad: false) + self.selectionNode?.frame = CGRect(origin: CGPoint(), size: bounds.size) + } + self.selectionBorderNode?.frame = CGRect(origin: CGPoint(), size: bounds.size) + + if let textNode = self.textNode { + let textSize = textNode.updateLayout(bounds.size) + textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - textSize.width) / 2.0), y: floorToScreenPixels((bounds.height - textSize.height) / 2.0) - 18.0), size: textSize) + } + } +} diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift similarity index 71% rename from submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift rename to submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift index ffb747354e..5ca1aa7be6 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridControllerNode.swift @@ -10,12 +10,14 @@ import TelegramPresentationData import TelegramUIPreferences import MergeLists import ItemListUI +import ItemListPeerActionItem import PresentationDataUtils import AccountContext import SearchBarNode import SearchUI import WallpaperResources import WallpaperGalleryScreen +import BoostLevelIconComponent struct ThemeGridControllerNodeState: Equatable { var editing: Bool @@ -47,7 +49,11 @@ struct ThemeGridControllerEntry: Comparable, Identifiable { } var index: Int + var theme: PresentationTheme? var wallpaper: TelegramWallpaper + var isEmpty: Bool = false + var emoji: TelegramMediaFile? + var channelMode: Bool = false var isEditable: Bool var isSelected: Bool @@ -75,7 +81,7 @@ struct ThemeGridControllerEntry: Comparable, Identifiable { } func item(context: AccountContext, interaction: ThemeGridControllerInteraction) -> ThemeGridControllerItem { - return ThemeGridControllerItem(context: context, wallpaper: self.wallpaper, wallpaperId: self.stableId, index: self.index, editable: self.isEditable, selected: self.isSelected, interaction: interaction) + return ThemeGridControllerItem(context: context, theme: self.theme, wallpaper: self.wallpaper, wallpaperId: self.stableId, isEmpty: self.isEmpty, emojiFile: self.emoji, channelMode: self.channelMode, index: self.index, editable: self.isEditable, selected: self.isSelected, interaction: interaction) } } @@ -141,6 +147,7 @@ final class ThemeGridControllerNode: ASDisplayNode { } private let context: AccountContext + private let mode: ThemeGridController.Mode private var presentationData: PresentationData private var controllerInteraction: ThemeGridControllerInteraction? @@ -153,17 +160,21 @@ final class ThemeGridControllerNode: ASDisplayNode { var requestDeactivateSearch: (() -> Void)? let ready = ValuePromise() - private let wallpapersPromise: Promise<[Wallpaper]> + private let wallpapersPromise = Promise<[Wallpaper]>() + private let themesPromise = Promise<[TelegramTheme]>() private var backgroundNode: ASDisplayNode private var separatorNode: ASDisplayNode private var bottomBackgroundNode: ASDisplayNode private var bottomSeparatorNode: ASDisplayNode + private let maskNode: ASImageNode private let colorItemNode: ItemListActionItemNode private var colorItem: ItemListActionItem - private let galleryItemNode: ItemListActionItemNode - private var galleryItem: ItemListActionItem + private let galleryItemNode: ListViewItemNode + private var galleryItem: ItemListItem + private let removeItemNode: ItemListPeerActionItemNode + private var removeItem: ItemListPeerActionItem private let descriptionItemNode: ItemListTextItemNode private var descriptionItem: ItemListTextItem private let resetItemNode: ItemListActionItemNode @@ -193,8 +204,9 @@ final class ThemeGridControllerNode: ASDisplayNode { private var disposable: Disposable? - init(context: AccountContext, presentationData: PresentationData, presentPreviewController: @escaping (WallpaperListSource) -> Void, presentGallery: @escaping () -> Void, presentColors: @escaping () -> Void, emptyStateUpdated: @escaping (Bool) -> Void, deleteWallpapers: @escaping ([TelegramWallpaper], @escaping () -> Void) -> Void, shareWallpapers: @escaping ([TelegramWallpaper]) -> Void, resetWallpapers: @escaping () -> Void, popViewController: @escaping () -> Void) { + init(context: AccountContext, mode: ThemeGridController.Mode, presentationData: PresentationData, presentPreviewController: @escaping (WallpaperListSource) -> Void, presentGallery: @escaping () -> Void, presentColors: @escaping () -> Void, emptyStateUpdated: @escaping (Bool) -> Void, deleteWallpapers: @escaping ([TelegramWallpaper], @escaping () -> Void) -> Void, shareWallpapers: @escaping ([TelegramWallpaper]) -> Void, resetWallpapers: @escaping () -> Void, popViewController: @escaping () -> Void) { self.context = context + self.mode = mode self.presentationData = presentationData self.presentPreviewController = presentPreviewController self.presentGallery = presentGallery @@ -221,16 +233,48 @@ final class ThemeGridControllerNode: ASDisplayNode { self.bottomSeparatorNode = ASDisplayNode() self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor + self.maskNode = ASImageNode() + self.maskNode.isUserInteractionEnabled = false + self.colorItemNode = ItemListActionItemNode() self.colorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_SetColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { presentColors() }) - self.galleryItemNode = ItemListActionItemNode() - self.galleryItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_SetCustomBackground, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { + + switch mode { + case .generic: + self.galleryItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_SetCustomBackground, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { + presentGallery() + }) + self.galleryItemNode = ItemListActionItemNode() + case .peer: + var requiredCustomWallpaperLevel: Int? + if case let .peer(_, _, _, _, customLevel) = mode { + requiredCustomWallpaperLevel = customLevel + } + //TODO:localize + 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: { + presentGallery() + }) + self.galleryItemNode = ItemListPeerActionItemNode() + } + + 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: { presentGallery() }) + self.removeItemNode = ItemListPeerActionItemNode() + self.descriptionItemNode = ItemListTextItemNode() - self.descriptionItem = ItemListTextItem(presentationData: ItemListPresentationData(presentationData), text: .plain(presentationData.strings.Wallpaper_SetCustomBackgroundInfo), sectionId: 0) + + let descriptionText: String + switch mode { + case .generic: + descriptionText = presentationData.strings.Wallpaper_SetCustomBackgroundInfo + case .peer: + descriptionText = presentationData.strings.Wallpaper_ChannelCustomBackgroundInfo + } + self.descriptionItem = ItemListTextItem(presentationData: ItemListPresentationData(presentationData), text: .plain(descriptionText), sectionId: 0) + self.resetItemNode = ItemListActionItemNode() self.resetItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_ResetWallpapers, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { resetWallpapers() @@ -241,9 +285,6 @@ final class ThemeGridControllerNode: ASDisplayNode { self.currentState = ThemeGridControllerNodeState(editing: false, selectedIds: Set()) self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true) - let wallpapersPromise = Promise<[Wallpaper]>() - self.wallpapersPromise = wallpapersPromise - let deletedWallpaperIdsValue = Atomic>(value: Set()) let deletedWallpaperIdsPromise = ValuePromise>(Set()) @@ -256,16 +297,25 @@ final class ThemeGridControllerNode: ASDisplayNode { self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor self.gridNode.addSubnode(self.backgroundNode) - self.gridNode.addSubnode(self.separatorNode) self.gridNode.addSubnode(self.bottomBackgroundNode) - self.gridNode.addSubnode(self.bottomSeparatorNode) - self.gridNode.addSubnode(self.colorItemNode) +// self.gridNode.addSubnode(self.bottomSeparatorNode) + if case .generic = mode { + self.gridNode.addSubnode(self.colorItemNode) + } self.gridNode.addSubnode(self.galleryItemNode) + if case let .peer(_, _, wallpaper, _, _) = mode, wallpaper != nil { + self.gridNode.addSubnode(self.removeItemNode) + } self.gridNode.addSubnode(self.descriptionItemNode) - self.gridNode.addSubnode(self.resetItemNode) - self.gridNode.addSubnode(self.resetDescriptionItemNode) + + if case .generic = mode { + self.gridNode.addSubnode(self.resetItemNode) + self.gridNode.addSubnode(self.resetDescriptionItemNode) + } self.addSubnode(self.gridNode) + self.gridNode.addSubnode(self.maskNode) + self.maskNode.image = PresentationResourcesItemList.cornersImage(presentationData.theme, top: true, bottom: true) let previousEntries = Atomic<[ThemeGridControllerEntry]?>(value: nil) let interaction = ThemeGridControllerInteraction(openWallpaper: { [weak self] wallpaper in @@ -340,80 +390,113 @@ final class ThemeGridControllerNode: ASDisplayNode { }) self.controllerInteraction = interaction - let transition = combineLatest(self.wallpapersPromise.get(), deletedWallpaperIdsPromise.get(), context.sharedContext.presentationData) - |> map { wallpapers, deletedWallpaperIds, presentationData -> (ThemeGridEntryTransition, Bool) in + let transition = combineLatest(self.wallpapersPromise.get(), self.themesPromise.get(), deletedWallpaperIdsPromise.get(), context.sharedContext.presentationData) + |> map { wallpapers, themes, deletedWallpaperIds, presentationData -> (ThemeGridEntryTransition, Bool) in var entries: [ThemeGridControllerEntry] = [] var index: Int = 0 - - entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, isEditable: false, isSelected: true), at: 0) - index += 1 - var defaultWallpaper: TelegramWallpaper? - if !presentationData.chatWallpaper.isBasicallyEqual(to: presentationData.theme.chat.defaultWallpaper) { - let entry = ThemeGridControllerEntry(index: 1, wallpaper: presentationData.theme.chat.defaultWallpaper, isEditable: false, isSelected: false) - if !entries.contains(where: { $0.stableId == entry.stableId }) { - defaultWallpaper = presentationData.theme.chat.defaultWallpaper - entries.insert(entry, at: index) - index += 1 + if !themes.isEmpty { + var selectedWallpaper: TelegramWallpaper? + if case let .peer(_, _, wallpaper, _, _) = mode { + selectedWallpaper = wallpaper } - } - - var sortedWallpapers: [TelegramWallpaper] = [] - if presentationData.theme.overallDarkAppearance { - var localWallpapers: [TelegramWallpaper] = [] - var darkWallpapers: [TelegramWallpaper] = [] - for wallpaper in wallpapers { - if wallpaper.isLocal { - localWallpapers.append(wallpaper.wallpaper) - } else { - if case let .file(file) = wallpaper.wallpaper, file.isDark { - darkWallpapers.append(wallpaper.wallpaper) - } else { - sortedWallpapers.append(wallpaper.wallpaper) - } - } - } - sortedWallpapers = localWallpapers + darkWallpapers + sortedWallpapers - } else { - sortedWallpapers = wallpapers.map(\.wallpaper) - } - - if let builtinIndex = sortedWallpapers.firstIndex(where: { wallpaper in - if case .builtin = wallpaper { - return true + + if let selectedWallpaper, !selectedWallpaper.isPattern { + entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: selectedWallpaper, channelMode: true, isEditable: false, isSelected: true)) } else { - return false + let emojiFile = context.animatedEmojiStickers["❌"]?.first?.file + + entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: .color(0), isEmpty: true, emoji: emojiFile, channelMode: true, isEditable: false, isSelected: selectedWallpaper == nil)) } - }) { - sortedWallpapers[builtinIndex] = defaultBuiltinWallpaper(data: .legacy, colors: legacyBuiltinWallpaperGradientColors.map(\.rgb)) - } - - for wallpaper in sortedWallpapers { - if case let .file(file) = wallpaper, (wallpaper.isPattern && file.settings.colors.isEmpty) { - continue - } - let selected = presentationData.chatWallpaper.isBasicallyEqual(to: wallpaper) - var isDefault = false - if let defaultWallpaper = defaultWallpaper, defaultWallpaper.isBasicallyEqual(to: wallpaper) { - isDefault = true - } - var isEditable = true - if case .builtin = wallpaper { - isEditable = false - } - if isDefault || presentationData.chatWallpaper.isBasicallyEqual(to: wallpaper) { - isEditable = false - } - if !selected && !isDefault { - let entry = ThemeGridControllerEntry(index: index, wallpaper: wallpaper, isEditable: isEditable, isSelected: false) - if deletedWallpaperIds.contains(entry.stableId) { + index += 1 + + for theme in themes { + guard let wallpaper = theme.settings?.first?.wallpaper, let emoticon = theme.emoticon else { continue } + + var updatedWallpaper = wallpaper + if let settings = wallpaper.settings { + var updatedSettings = settings + updatedSettings.emoticon = emoticon + updatedWallpaper = wallpaper.withUpdatedSettings(updatedSettings) + } + + let emoji = context.animatedEmojiStickers[emoticon] + entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: updatedWallpaper, emoji: emoji?.first?.file, channelMode: true, isEditable: false, isSelected: selectedWallpaper?.isBasicallyEqual(to: wallpaper) ?? false)) + index += 1 + } + } else { + entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, emoji: nil, isEditable: false, isSelected: true), at: 0) + index += 1 + + var defaultWallpaper: TelegramWallpaper? + if !presentationData.chatWallpaper.isBasicallyEqual(to: presentationData.theme.chat.defaultWallpaper) { + let entry = ThemeGridControllerEntry(index: 1, wallpaper: presentationData.theme.chat.defaultWallpaper, emoji: nil, isEditable: false, isSelected: false) if !entries.contains(where: { $0.stableId == entry.stableId }) { - entries.append(entry) + defaultWallpaper = presentationData.theme.chat.defaultWallpaper + entries.insert(entry, at: index) index += 1 } } + + var sortedWallpapers: [TelegramWallpaper] = [] + if presentationData.theme.overallDarkAppearance { + var localWallpapers: [TelegramWallpaper] = [] + var darkWallpapers: [TelegramWallpaper] = [] + for wallpaper in wallpapers { + if wallpaper.isLocal { + localWallpapers.append(wallpaper.wallpaper) + } else { + if case let .file(file) = wallpaper.wallpaper, file.isDark { + darkWallpapers.append(wallpaper.wallpaper) + } else { + sortedWallpapers.append(wallpaper.wallpaper) + } + } + } + sortedWallpapers = localWallpapers + darkWallpapers + sortedWallpapers + } else { + sortedWallpapers = wallpapers.map(\.wallpaper) + } + + if let builtinIndex = sortedWallpapers.firstIndex(where: { wallpaper in + if case .builtin = wallpaper { + return true + } else { + return false + } + }) { + sortedWallpapers[builtinIndex] = defaultBuiltinWallpaper(data: .legacy, colors: legacyBuiltinWallpaperGradientColors.map(\.rgb)) + } + + for wallpaper in sortedWallpapers { + if case let .file(file) = wallpaper, (wallpaper.isPattern && file.settings.colors.isEmpty) { + continue + } + let selected = presentationData.chatWallpaper.isBasicallyEqual(to: wallpaper) + var isDefault = false + if let defaultWallpaper = defaultWallpaper, defaultWallpaper.isBasicallyEqual(to: wallpaper) { + isDefault = true + } + var isEditable = true + if case .builtin = wallpaper { + isEditable = false + } + if isDefault || presentationData.chatWallpaper.isBasicallyEqual(to: wallpaper) { + isEditable = false + } + if !selected && !isDefault { + let entry = ThemeGridControllerEntry(index: index, wallpaper: wallpaper, isEditable: isEditable, isSelected: false) + if deletedWallpaperIds.contains(entry.stableId) { + continue + } + if !entries.contains(where: { $0.stableId == entry.stableId }) { + entries.append(entry) + index += 1 + } + } + } } let previous = previousEntries.swap(entries) @@ -476,11 +559,14 @@ final class ThemeGridControllerNode: ASDisplayNode { let (resetLayout, resetApply) = makeResetLayout(strongSelf.resetItem, params, ItemListNeighbors(top: .none, bottom: .sameSection(alwaysPlain: true))) let (resetDescriptionLayout, resetDescriptionApply) = makeResetDescriptionLayout(strongSelf.resetDescriptionItem, params, ItemListNeighbors(top: .none, bottom: .none)) - resetApply() + resetApply(false) resetDescriptionApply() transition.updateFrame(node: strongSelf.resetItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height + 35.0), size: resetLayout.contentSize)) transition.updateFrame(node: strongSelf.resetDescriptionItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: gridLayout.contentSize.height + 35.0 + resetLayout.contentSize.height), size: resetDescriptionLayout.contentSize)) + + let sideInset = strongSelf.leftOverlayNode.frame.maxX + strongSelf.maskNode.frame = CGRect(origin: CGPoint(x: sideInset, y: strongSelf.separatorNode.frame.minY + UIScreenPixel + 4.0), size: CGSize(width: layout.size.width - sideInset * 2.0, height: gridLayout.contentSize.height + 6.0)) } } } @@ -494,7 +580,11 @@ final class ThemeGridControllerNode: ASDisplayNode { if self.colorItemNode.frame.contains(location) { self.colorItem.action() } else if self.galleryItemNode.frame.contains(location) { - self.galleryItem.action() + if let galleryItem = self.galleryItem as? ItemListActionItem { + galleryItem.action() + } else if let galleryItem = self.galleryItem as? ItemListPeerActionItem { + galleryItem.action?() + } } else if self.resetItemNode.frame.contains(location) { self.resetItem.action() } @@ -508,31 +598,38 @@ final class ThemeGridControllerNode: ASDisplayNode { } func updateWallpapers() { - self.wallpapersPromise.set(combineLatest(queue: .mainQueue(), - telegramWallpapers(postbox: self.context.account.postbox, network: self.context.account.network), - self.context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.wallapersState]) - ) - |> map { remoteWallpapers, sharedData -> [Wallpaper] in - let localState = sharedData.entries[SharedDataKeys.wallapersState]?.get(WallpapersState.self) ?? WallpapersState.default + switch self.mode { + case .generic: + self.wallpapersPromise.set(combineLatest(queue: .mainQueue(), + telegramWallpapers(postbox: self.context.account.postbox, network: self.context.account.network), + self.context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.wallapersState]) + ) + |> map { remoteWallpapers, sharedData -> [Wallpaper] in + let localState = sharedData.entries[SharedDataKeys.wallapersState]?.get(WallpapersState.self) ?? WallpapersState.default - var wallpapers: [Wallpaper] = [] - for wallpaper in localState.wallpapers { - if !wallpapers.contains(where: { - $0.wallpaper.isBasicallyEqual(to: wallpaper) - }) { - wallpapers.append(Wallpaper(wallpaper: wallpaper, isLocal: true)) + var wallpapers: [Wallpaper] = [] + for wallpaper in localState.wallpapers { + if !wallpapers.contains(where: { + $0.wallpaper.isBasicallyEqual(to: wallpaper) + }) { + wallpapers.append(Wallpaper(wallpaper: wallpaper, isLocal: true)) + } } - } - for wallpaper in remoteWallpapers { - if !wallpapers.contains(where: { - $0.wallpaper.isBasicallyEqual(to: wallpaper) - }) { - wallpapers.append(Wallpaper(wallpaper: wallpaper, isLocal: false)) + for wallpaper in remoteWallpapers { + if !wallpapers.contains(where: { + $0.wallpaper.isBasicallyEqual(to: wallpaper) + }) { + wallpapers.append(Wallpaper(wallpaper: wallpaper, isLocal: false)) + } } - } - return wallpapers - }) + return wallpapers + }) + self.themesPromise.set(.single([])) + case let .peer(_, themes, _, _, _): + self.themesPromise.set(.single(themes)) + self.wallpapersPromise.set(.single([])) + } } func updatePresentationData(_ presentationData: PresentationData) { @@ -551,9 +648,22 @@ final class ThemeGridControllerNode: ASDisplayNode { self.colorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_SetColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in self?.presentColors() }) - self.galleryItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_SetCustomBackground, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in - self?.presentGallery() - }) + + switch self.mode { + case .generic: + self.galleryItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_SetCustomBackground, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in + self?.presentGallery() + }) + case .peer: + var requiredCustomWallpaperLevel: Int? + if case let .peer(_, _, _, _, customLevel) = mode { + requiredCustomWallpaperLevel = customLevel + } + 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.descriptionItem = ItemListTextItem(presentationData: ItemListPresentationData(presentationData), text: .plain(presentationData.strings.Wallpaper_SetCustomBackgroundInfo), sectionId: 0) self.resetItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.Wallpaper_ResetWallpapers, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in self?.resetWallpapers() @@ -621,28 +731,32 @@ final class ThemeGridControllerNode: ASDisplayNode { var scrollIndicatorInsets = insets - let minSpacing: CGFloat = 8.0 + let minSpacing: CGFloat = 6.0 let referenceImageSize: CGSize let screenWidth = min(layout.size.width, layout.size.height) if screenWidth >= 390.0 { - referenceImageSize = CGSize(width: 108.0, height: 230.0) + referenceImageSize = CGSize(width: 112.0, height: 150.0) } else { referenceImageSize = CGSize(width: 91.0, height: 161.0) } - let imageCount = Int((layout.size.width - insets.left - insets.right - minSpacing * 2.0) / (referenceImageSize.width + minSpacing)) - let imageSize = referenceImageSize.aspectFilled(CGSize(width: floor((layout.size.width - CGFloat(imageCount + 1) * minSpacing) / CGFloat(imageCount)), height: referenceImageSize.height)) - let spacing = floor((layout.size.width - CGFloat(imageCount) * imageSize.width) / CGFloat(imageCount + 1)) + + let sideInset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) + + let gridWidth = layout.size.width - sideInset * 2.0 + + let imageCount = Int((gridWidth - minSpacing * 2.0) / (referenceImageSize.width)) + let imageSize = referenceImageSize.aspectFilled(CGSize(width: floor((gridWidth - CGFloat(imageCount + 1) * minSpacing) / CGFloat(imageCount)), height: referenceImageSize.height)) + let spacing = floor((gridWidth - CGFloat(imageCount) * imageSize.width) / CGFloat(imageCount + 1)) let makeColorLayout = self.colorItemNode.asyncLayout() - let makeGalleryLayout = self.galleryItemNode.asyncLayout() + let makeGalleryLayout = (self.galleryItemNode as? ItemListActionItemNode)?.asyncLayout() + let makeGalleryIconLayout = (self.galleryItemNode as? ItemListPeerActionItemNode)?.asyncLayout() let makeDescriptionLayout = self.descriptionItemNode.asyncLayout() var listInsets = insets if layout.size.width >= 375.0 { - let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0)) - listInsets.left += inset - listInsets.right += inset - + listInsets.left = sideInset + listInsets.right = sideInset if self.leftOverlayNode.supernode == nil { self.gridNode.addSubnode(self.leftOverlayNode) } @@ -658,33 +772,56 @@ final class ThemeGridControllerNode: ASDisplayNode { } } + var isChannel = false + if case .peer = self.mode { + isChannel = 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) = makeGalleryLayout(self.galleryItem, params, ItemListNeighbors(top: .sameSection(alwaysPlain: false), bottom: .sameSection(alwaysPlain: true))) + 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))) + } 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))) + } else { + fatalError() + } + let (descriptionLayout, descriptionApply) = makeDescriptionLayout(self.descriptionItem, params, ItemListNeighbors(top: .none, bottom: .none)) - colorApply() - galleryApply() + colorApply(false) + galleryApply(false) descriptionApply() let buttonTopInset: CGFloat = 32.0 let buttonHeight: CGFloat = 44.0 let buttonBottomInset: CGFloat = descriptionLayout.contentSize.height + 17.0 - let buttonInset: CGFloat = buttonTopInset + buttonHeight * 2.0 + buttonBottomInset + var buttonInset: CGFloat = buttonTopInset + buttonHeight + buttonBottomInset + if !isChannel { + buttonInset += buttonHeight + } let buttonOffset = buttonInset + 10.0 - transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: buttonInset + 500.0))) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: buttonInset + 504.0))) transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonInset - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - transition.updateFrame(node: self.colorItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonTopInset), size: colorLayout.contentSize)) - transition.updateFrame(node: self.galleryItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonTopInset + colorLayout.contentSize.height), size: galleryLayout.contentSize)) - transition.updateFrame(node: self.descriptionItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonTopInset + colorLayout.contentSize.height + galleryLayout.contentSize.height), size: descriptionLayout.contentSize)) + var originY = -buttonOffset + buttonTopInset + if !isChannel { + transition.updateFrame(node: self.colorItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: originY), size: colorLayout.contentSize)) + originY += colorLayout.contentSize.height + } + transition.updateFrame(node: self.galleryItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: originY), size: galleryLayout.contentSize)) + originY += galleryLayout.contentSize.height - self.leftOverlayNode.frame = CGRect(x: 0.0, y: -buttonOffset, width: listInsets.left, height: buttonTopInset + colorLayout.contentSize.height + galleryLayout.contentSize.height) - self.rightOverlayNode.frame = CGRect(x: layout.size.width - listInsets.right, y: -buttonOffset, width: listInsets.right, height: buttonTopInset + colorLayout.contentSize.height + galleryLayout.contentSize.height) + 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) insets.top += spacing + buttonInset + listInsets.top = insets.top if self.currentState.editing { let panelHeight: CGFloat @@ -748,10 +885,12 @@ final class ThemeGridControllerNode: ASDisplayNode { let makeResetDescriptionLayout = self.resetDescriptionItemNode.asyncLayout() let (resetDescriptionLayout, _) = makeResetDescriptionLayout(self.resetDescriptionItem, params, ItemListNeighbors(top: .none, bottom: .none)) - insets.bottom += buttonHeight + 35.0 + resetDescriptionLayout.contentSize.height + 32.0 + if !isChannel { + insets.bottom += buttonHeight + 35.0 + resetDescriptionLayout.contentSize.height + 32.0 + } self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: nil, lineSpacing: spacing, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: listInsets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: nil, lineSpacing: spacing, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) if !hadValidLayout { self.dequeueTransitions() diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchColorsItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchColorsItem.swift similarity index 100% rename from submodules/SettingsUI/Sources/Themes/ThemeGridSearchColorsItem.swift rename to submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchColorsItem.swift diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchContentNode.swift similarity index 100% rename from submodules/SettingsUI/Sources/Themes/ThemeGridSearchContentNode.swift rename to submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchContentNode.swift diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSearchItem.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchItem.swift similarity index 100% rename from submodules/SettingsUI/Sources/Themes/ThemeGridSearchItem.swift rename to submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSearchItem.swift diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridSelectionPanelNode.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSelectionPanelNode.swift similarity index 100% rename from submodules/SettingsUI/Sources/Themes/ThemeGridSelectionPanelNode.swift rename to submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeGridSelectionPanelNode.swift diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperSearchRecentQueries.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperSearchRecentQueries.swift similarity index 100% rename from submodules/SettingsUI/Sources/Themes/WallpaperSearchRecentQueries.swift rename to submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperSearchRecentQueries.swift diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift new file mode 100644 index 0000000000..563244c10e --- /dev/null +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/WallpaperUtils.swift @@ -0,0 +1,284 @@ +import Foundation +import UIKit +import Display +import SwiftSignalKit +import Postbox +import TelegramCore +import MediaResources +import LocalMediaResources +import TelegramUIPreferences +import AccountContext +import LegacyComponents +import WallpaperGalleryScreen + +public func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?, completion: @escaping () -> Void) { + var imageSignal: Signal + switch wallpaper { + case let .wallpaper(wallpaper, _): + switch wallpaper { + case let .file(file): + if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data) + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() + } + case let .image(representations, _): + for representation in representations { + let resource = representation.resource + if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data) + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() + } + } + default: + break + } + imageSignal = .complete() + completion() + case let .asset(asset): + imageSignal = fetchPhotoLibraryImage(localIdentifier: asset.localIdentifier, thumbnail: false) + |> filter { value in + return !(value?.1 ?? true) + } + |> mapToSignal { result -> Signal in + if let result = result { + return .single(result.0) + } else { + return .complete() + } + } + case let .contextResult(result): + var imageResource: TelegramMediaResource? + switch result { + case let .externalReference(externalReference): + if let content = externalReference.content { + imageResource = content.resource + } + case let .internalReference(internalReference): + if let image = internalReference.image { + if let imageRepresentation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 1000, height: 800)) { + imageResource = imageRepresentation.resource + } + } + } + + if let imageResource = imageResource { + imageSignal = .single(context.account.postbox.mediaBox.completedResourcePath(imageResource)) + |> mapToSignal { path -> Signal in + if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]), let image = UIImage(data: data) { + return .single(image) + } else { + return .complete() + } + } + } else { + imageSignal = .complete() + } + } + + if let editedImage { + imageSignal = .single(editedImage) + } + + let _ = (imageSignal + |> map { image -> UIImage in + var croppedImage = UIImage() + + let finalCropRect: CGRect + if let cropRect = cropRect { + finalCropRect = cropRect + } else { + let screenSize = TGScreenSize() + let fittedSize = TGScaleToFit(screenSize, image.size) + finalCropRect = CGRect(x: (image.size.width - fittedSize.width) / 2.0, y: (image.size.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height) + } + croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true) + + 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 autoNightModeTriggered = context.sharedContext.currentPresentationData.with {$0 }.autoNightModeTriggered + let accountManager = context.sharedContext.accountManager + let account = context.account + let updateWallpaper: (TelegramWallpaper) -> Void = { wallpaper in + var resource: MediaResource? + if case let .image(representations, _) = wallpaper, let representation = largestImageRepresentation(representations) { + resource = representation.resource + } else if case let .file(file) = wallpaper { + resource = file.file.resource + } + + if let resource = resource { + let _ = accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start(completed: {}) + let _ = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start(completed: {}) + } + + let _ = (updatePresentationThemeSettingsInteractively(accountManager: accountManager, { current in + var themeSpecificChatWallpapers = current.themeSpecificChatWallpapers + let themeReference: PresentationThemeReference + if autoNightModeTriggered { + themeReference = current.automaticThemeSwitchSetting.theme + } else { + themeReference = current.theme + } + let accentColor = current.themeSpecificAccentColors[themeReference.index] + if let accentColor = accentColor, accentColor.baseColor == .custom { + themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] = wallpaper + } else { + themeSpecificChatWallpapers[coloredThemeIndex(reference: themeReference, accentColor: accentColor)] = nil + themeSpecificChatWallpapers[themeReference.index] = wallpaper + } + return current.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers) + })).start() + } + + let apply: () -> Void = { + let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: nil) + let wallpaper: 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) + updateWallpaper(wallpaper) + DispatchQueue.main.async { + completion() + } + } + + if mode.contains(.blur) { + let representation = CachedBlurredWallpaperRepresentation() + let _ = context.account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true).start() + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: representation, complete: true, fetch: true).start(completed: { + apply() + }) + } else { + apply() + } + } + return croppedImage + }).start() +} + +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 { + case let .wallpaper(wallpaper, _): + imageSignal = .complete() + switch wallpaper { + case let .file(file): + if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let image = UIImage(data: data) { + context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data, synchronous: true) + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start() + + imageSignal = .single(image) + } + case let .image(representations, _): + for representation in representations { + let resource = representation.resource + if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) { + context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true) + let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start() + } + } + default: + break + } + completion() + case let .asset(asset): + imageSignal = fetchPhotoLibraryImage(localIdentifier: asset.localIdentifier, thumbnail: false) + |> filter { value in + return !(value?.1 ?? true) + } + |> mapToSignal { result -> Signal in + if let result = result { + return .single(result.0) + } else { + return .complete() + } + } + case let .contextResult(result): + var imageResource: TelegramMediaResource? + switch result { + case let .externalReference(externalReference): + if let content = externalReference.content { + imageResource = content.resource + } + case let .internalReference(internalReference): + if let image = internalReference.image { + if let imageRepresentation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 1000, height: 800)) { + imageResource = imageRepresentation.resource + } + } + } + + if let imageResource = imageResource { + imageSignal = .single(context.account.postbox.mediaBox.completedResourcePath(imageResource)) + |> mapToSignal { path -> Signal in + if let path = path, let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: [.mappedIfSafe]), let image = UIImage(data: data) { + return .single(image) + } else { + return .complete() + } + } + } else { + imageSignal = .complete() + } + } + + if let editedImage { + imageSignal = .single(editedImage) + } + + let _ = (imageSignal + |> map { image -> UIImage in + var croppedImage = UIImage() + + let finalCropRect: CGRect + if let cropRect = cropRect { + finalCropRect = cropRect + } else { + let screenSize = TGScreenSize() + let fittedSize = TGScaleToFit(screenSize, image.size) + finalCropRect = CGRect(x: (image.size.width - fittedSize.width) / 2.0, y: (image.size.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height) + } + croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true) + + 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))) + } + + Queue.mainQueue().after(0.05) { + 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) + + context.account.pendingPeerMediaUploadManager.add(peerId: peerId, content: .wallpaper(wallpaper: temporaryWallpaper, forBoth: forBoth)) + + Queue.mainQueue().after(0.05) { + completion() + } + } + } + return croppedImage + }).start() +} diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 2883eb6582..7eb14f88d4 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -1066,7 +1066,7 @@ final class ShareWithPeersScreenComponent: Component { rightAccessory: accessory, selectionState: .none, hasNext: i < peers.count - 1, - action: { [weak self] peer, _ in + action: { [weak self] peer, _, _ in guard let self, let component = self.component else { return } @@ -1414,7 +1414,7 @@ final class ShareWithPeersScreenComponent: Component { presence: stateValue.presences[peer.id], selectionState: .editing(isSelected: isSelected, isTinted: false), hasNext: true, - action: { [weak self] peer, _ in + action: { [weak self] peer, _, _ in guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else { return } @@ -2143,7 +2143,7 @@ final class ShareWithPeersScreenComponent: Component { presence: nil, selectionState: .editing(isSelected: false, isTinted: false), hasNext: true, - action: { _, _ in + action: { _, _, _ in } )), environment: {}, diff --git a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift index 8e1b923c9a..c23e8cae70 100644 --- a/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift +++ b/submodules/TelegramUI/Components/Stories/PeerListItemComponent/Sources/PeerListItemComponent.swift @@ -116,10 +116,9 @@ public final class PeerListItemComponent: Component { let selectionPosition: SelectionPosition let isEnabled: Bool let hasNext: Bool - let action: (EnginePeer, EngineMessage.Id?) -> Void + let action: (EnginePeer, EngineMessage.Id?, UIView?) -> Void let contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? let openStories: ((EnginePeer, AvatarNode) -> Void)? - let openStory: ((EnginePeer, Int32, UIView) -> Void)? public init( context: AccountContext, @@ -141,10 +140,9 @@ public final class PeerListItemComponent: Component { selectionPosition: SelectionPosition = .left, isEnabled: Bool = true, hasNext: Bool, - action: @escaping (EnginePeer, EngineMessage.Id?) -> Void, + action: @escaping (EnginePeer, EngineMessage.Id?, UIView?) -> Void, contextAction: ((EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil, - openStories: ((EnginePeer, AvatarNode) -> Void)? = nil, - openStory: ((EnginePeer, Int32, UIView) -> Void)? = nil + openStories: ((EnginePeer, AvatarNode) -> Void)? = nil ) { self.context = context self.theme = theme @@ -168,7 +166,6 @@ public final class PeerListItemComponent: Component { self.action = action self.contextAction = contextAction self.openStories = openStories - self.openStory = openStory } public static func ==(lhs: PeerListItemComponent, rhs: PeerListItemComponent) -> Bool { @@ -353,7 +350,7 @@ public final class PeerListItemComponent: Component { guard let component = self.component, let peer = component.peer else { return } - component.action(peer, component.message?.id) + component.action(peer, component.message?.id, self.imageNode?.view) } @objc private func avatarButtonPressed() { @@ -363,13 +360,6 @@ public final class PeerListItemComponent: Component { component.openStories?(peer, self.avatarNode) } - @objc private func imageButtonPressed() { - guard let component = self.component, let peer = component.peer, let story = component.story, let imageNode = self.imageNode else { - return - } - component.openStory?(peer, story.id, imageNode.view) - } - private func updateReactionLayer() { guard let component = self.component else { return @@ -506,6 +496,9 @@ public final class PeerListItemComponent: Component { if component.reaction != nil || component.rightAccessory != .none { rightInset += 32.0 } + if component.story != nil { + rightInset += 40.0 + } var avatarLeftInset: CGFloat = component.sideInset + 10.0 @@ -913,15 +906,13 @@ public final class PeerListItemComponent: Component { self.imageNode = imageNode imageButtonView = HighlightTrackingButton() - imageButtonView.addTarget(self, action: #selector(self.imageButtonPressed), for: .touchUpInside) - imageButtonView.isEnabled = component.message == nil + imageButtonView.isEnabled = false self.imageButtonView = imageButtonView self.containerButton.addSubview(imageNode.view) self.addSubview(imageButtonView) var imageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? - if let imageReference = mediaReference.concrete(TelegramMediaImage.self) { imageSignal = mediaGridMessagePhoto(account: component.context.account, userLocation: .peer(peer.id), photoReference: imageReference) } else if let fileReference = mediaReference.concrete(TelegramMediaFile.self) { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 010cfcfaf2..ad8c16d3fb 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -3162,7 +3162,13 @@ public final class StoryItemSetContainerComponent: Component { for (id, views) in preloadViewListIds { if component.sharedViewListsContext.viewLists[StoryId(peerId: component.slice.peer.id, id: id)] == nil { - let viewList = component.context.engine.messages.storyViewList(peerId: component.slice.peer.id, id: id, views: views, listMode: .everyone, sortMode: .reactionsFirst) + let defaultSortMode: EngineStoryViewListContext.SortMode + if component.slice.peer.id.isGroupOrChannel { + defaultSortMode = .repostsFirst + } else { + defaultSortMode = .reactionsFirst + } + let viewList = component.context.engine.messages.storyViewList(peerId: component.slice.peer.id, id: id, views: views, listMode: .everyone, sortMode: defaultSortMode) component.sharedViewListsContext.viewLists[StoryId(peerId: component.slice.peer.id, id: id)] = viewList } } @@ -3512,7 +3518,7 @@ public final class StoryItemSetContainerComponent: Component { } self.openPeerStories(peer: peer, avatarNode: avatarNode) }, - openStory: { [weak self] peer, id, stories, sourceView in + openReposts: { [weak self] peer, id, sourceView in guard let self else { return } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index 30ccbb145d..485c033110 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -3311,20 +3311,39 @@ final class StoryItemSetContainerSendMessage { action() })) case let .channelMessage(_, messageId): - let action = { [weak self, weak view] in - let _ = ((context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .local) - |> mapToSignal { result -> Signal in + let action = { [weak self, weak view, weak controller] in + let _ = ((context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: true)) + |> mapToSignal { result -> Signal in if case let .result(messages) = result { return .single(messages.first) + } else { + return .complete() } - return .single(nil) }) - |> deliverOnMainQueue).startStandalone(next: { [weak self, weak view] message in + |> deliverOnMainQueue).startStandalone(next: { [weak self, weak view, weak controller] message in guard let self, let view else { return } if let message, let peer = message.peers[message.id.peerId] { self.openResolved(view: view, result: .channelMessage(peer: peer, messageId: message.id, timecode: nil)) + } else { + controller?.present(UndoOverlayController(presentationData: updatedPresentationData.initial, content: .info(title: nil, text: updatedPresentationData.initial.strings.Conversation_MessageDoesntExist, timeout: nil, customUndoText: nil), elevatedLayout: false, position: .top, action: { _ in return true }), in: .current) + } + }, error: { [weak self, weak view] error in + guard let self, let view else { + return + } + switch error { + case .privateChannel: + let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)) + |> deliverOnMainQueue).startStandalone(next: { [weak self, weak view] peer in + guard let self, let view else { + return + } + if let peer { + self.openResolved(view: view, result: .peer(peer._asPeer(), .default)) + } + }) } }) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift index 699e67cc9e..ddbb3b83ee 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetViewListComponent.swift @@ -72,7 +72,7 @@ final class StoryItemSetViewListComponent: Component { let openMessage: (EnginePeer, EngineMessage.Id) -> Void let peerContextAction: (EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void let openPeerStories: (EnginePeer, AvatarNode) -> Void - let openStory: (EnginePeer, Int32, [(EnginePeer, EngineStoryItem)], UIView) -> Void + let openReposts: (EnginePeer, Int32, UIView) -> Void let openPremiumIntro: () -> Void let setIsSearchActive: (Bool) -> Void let controller: () -> ViewController? @@ -98,7 +98,7 @@ final class StoryItemSetViewListComponent: Component { openMessage: @escaping (EnginePeer, EngineMessage.Id) -> Void, peerContextAction: @escaping (EnginePeer, ContextExtractedContentContainingView, ContextGesture) -> Void, openPeerStories: @escaping (EnginePeer, AvatarNode) -> Void, - openStory: @escaping (EnginePeer, Int32, [(EnginePeer, EngineStoryItem)], UIView) -> Void, + openReposts: @escaping (EnginePeer, Int32, UIView) -> Void, openPremiumIntro: @escaping () -> Void, setIsSearchActive: @escaping (Bool) -> Void, controller: @escaping () -> ViewController? @@ -123,7 +123,7 @@ final class StoryItemSetViewListComponent: Component { self.openMessage = openMessage self.peerContextAction = peerContextAction self.openPeerStories = openPeerStories - self.openStory = openStory + self.openReposts = openReposts self.openPremiumIntro = openPremiumIntro self.setIsSearchActive = setIsSearchActive self.controller = controller @@ -580,12 +580,14 @@ final class StoryItemSetViewListComponent: Component { message: item.message, selectionState: .none, hasNext: index != viewListState.totalCount - 1 || itemLayout.premiumFooterSize != nil, - action: { [weak self] peer, messageId in + action: { [weak self] peer, messageId, sourceView in guard let self, let component = self.component else { return } if let messageId { component.openMessage(peer, messageId) + } else if let storyItem, let sourceView { + component.openReposts(peer, storyItem.id, sourceView) } else { component.openPeer(peer) } @@ -598,19 +600,6 @@ final class StoryItemSetViewListComponent: Component { return } component.openPeerStories(peer, avatarNode) - }, - openStory: { [weak self] peer, id, sourceView in - guard let self, let component = self.component, let state = self.viewListState else { - return - } - - var stories: [(EnginePeer, EngineStoryItem)] = [] - for item in state.items { - if let story = item.story { - stories.append((item.peer, story)) - } - } - component.openStory(peer, id, stories, sourceView) } )), environment: {}, @@ -801,7 +790,7 @@ final class StoryItemSetViewListComponent: Component { } else { let defaultSortMode: SortMode if component.peerId.isGroupOrChannel { - defaultSortMode = .recentFirst + defaultSortMode = .repostsFirst } else { defaultSortMode = .reactionsFirst } @@ -933,7 +922,7 @@ final class StoryItemSetViewListComponent: Component { presence: nil, selectionState: .none, hasNext: true, - action: { _, _ in + action: { _, _, _ in } )), environment: {}, @@ -1494,7 +1483,7 @@ final class StoryItemSetViewListComponent: Component { if self.mainViewList == nil { if component.peerId.isGroupOrChannel { - self.sortMode = .recentFirst + self.sortMode = .repostsFirst } else { self.sortMode = .reactionsFirst } diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index c009a04742..c5dc13c766 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -246,6 +246,9 @@ func makeAttachmentFileControllerImpl(context: AccountContext, updatedPresentati }, send: { message in let _ = (context.engine.messages.getMessagesLoadIfNecessary([message.id], strategy: .cloud(skipLocal: true)) + |> `catch` { _ in + return .single(.result([])) + } |> mapToSignal { result -> Signal<[Message], NoError> in guard case let .result(result) = result else { return .complete() diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift index 9047c3dbfa..f33d0ca9d4 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerNavigateToMessage.swift @@ -108,6 +108,9 @@ extension ChatControllerImpl { let _ = (combineLatest( self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)), self.context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .local) + |> `catch` { _ in + return .single(.result([])) + } |> mapToSignal { result -> Signal<[Message], NoError> in guard case let .result(result) = result else { return .complete() diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 7d65005d95..9b54e69fb4 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -119,6 +119,7 @@ import ChatQrCodeScreen import PeerInfoScreen import MediaEditorScreen import WallpaperGalleryScreen +import WallpaperGridScreen public enum ChatControllerPeekActions { case standard diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index b798fbde48..5b01fa8432 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -943,7 +943,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto } |> distinctUntilChanged(isEqual: { $0 == $1 }) |> mapToSignal { messageId -> Signal in if let messageId = messageId { - return context.engine.messages.getMessagesLoadIfNecessary([messageId]) |> map { _ -> Void in return Void() } + return context.engine.messages.getMessagesLoadIfNecessary([messageId]) + |> `catch` { _ in + return .single(.result([])) + } + |> map { _ -> Void in return Void() } } else { return .complete() } diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index fd59597607..b6179adffd 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -702,6 +702,9 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) if case let .channel(channel) = peer, channel.flags.contains(.isForum) { let messageId = MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: id) return context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: false)) + |> `catch` { _ in + return .single(.result([])) + } |> take(1) |> mapToSignal { result -> Signal in switch result { @@ -830,6 +833,9 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl) } } else { return context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: false)) + |> `catch` { _ in + return .single(.result([])) + } |> mapToSignal { result -> Signal in switch result { case .progress: