diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c250508d09..4f21a5b6a0 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -255,6 +255,8 @@ "PUSH_MESSAGE_SUGGEST_USERPIC" = "%1$@|suggested you new profile photo"; +"PUSH_MESSAGE_WALLPAPER" = "%1$@ set a new background for the chat with you"; +"PUSH_MESSAGE_SAME_WALLPAPER" = "%1$@ set the same background for the chat with you"; "PUSH_REMINDER_TITLE" = "🗓 Reminder"; "PUSH_SENDER_YOU" = "📅 You"; @@ -9133,3 +9135,19 @@ Sorry for the inconvenience."; "Premium.MaxChannelsText" = "You can only join **%1$@** groups and channels. Upgrade to **Telegram Premium** to increase the links limit to **%2$@**."; "Premium.MaxChannelsNoPremiumText" = "You can only join **%1$@** groups and channels. We are working to let you increase this limit in the future."; "Premium.MaxChannelsFinalText" = "Sorry, you can only join **%1$@** groups and channels"; + +"Username.BotLinkHint" = "This username cannot be edited."; +"Username.BotLinkHintExtended" = "This username cannot be edited. You can acquire additional usernames on [Fragment]()."; + +"PeerInfo.Bot.EditIntro" = "Edit Intro"; +"PeerInfo.Bot.EditCommands" = "Edit Commands"; +"PeerInfo.Bot.ChangeSettings" = "Change Bot Settings"; +"PeerInfo.Bot.BotFatherInfo" = "Use [@BotFather]() to manage this bot."; + +"WallpaperPreview.NotAppliedInfo" = "Background will not be applied for **%@**"; +"WallpaperPreview.ChatTopText" = "Apply the background in this chat."; +"WallpaperPreview.ChatBottomText" = "Enjoy the view."; + +"Conversation.Theme.SetPhotoWallpaper" = "Choose Background from Photos"; +"Conversation.Theme.SetColorWallpaper" = "Choose Color as a Background"; +"Conversation.Theme.OtherOptions" = "Other Options..."; diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index b5e43dee22..261e946cf4 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -853,7 +853,7 @@ public final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDele var didBeginSelectingChats: (() -> Void)? public var displayFilterLimit: (() -> Void)? - public init(context: AccountContext, location: ChatListControllerLocation, chatListMode: ChatListNodeMode = .chatList, previewing: Bool, controlsHistoryPreload: Bool, isInlineMode: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { + public init(context: AccountContext, location: ChatListControllerLocation, chatListMode: ChatListNodeMode = .chatList(appendContacts: true), previewing: Bool, controlsHistoryPreload: Bool, isInlineMode: Bool, presentationData: PresentationData, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, filterBecameEmpty: @escaping (ChatListFilter?) -> Void, filterEmptyAction: @escaping (ChatListFilter?) -> Void, secondaryEmptyAction: @escaping () -> Void) { self.context = context self.location = location self.chatListMode = chatListMode diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 841213c27f..d9d620a424 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -3349,7 +3349,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let currentMutedIconImage = currentMutedIconImage { strongSelf.mutedIconNode.image = currentMutedIconImage strongSelf.mutedIconNode.isHidden = false - transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: titleFrame.maxY - currentMutedIconImage.size.height + 0.0 + UIScreenPixel), size: currentMutedIconImage.size)) + transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: floorToScreenPixels(titleFrame.midY - currentMutedIconImage.size.height / 2.0)), size: currentMutedIconImage.size)) nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0 } else { strongSelf.mutedIconNode.image = nil diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index 82ae5cd402..24506af387 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -20,7 +20,7 @@ import Postbox import ChatFolderLinkPreviewScreen public enum ChatListNodeMode { - case chatList + case chatList(appendContacts: Bool) case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool) case peerType(type: [ReplyMarkupButtonRequestPeerType], hasCreate: Bool) } @@ -1668,7 +1668,16 @@ public final class ChatListNode: ListView { } let currentPeerId: EnginePeer.Id = context.account.peerId - + + /*let contactList: Signal + if case let .chatList(appendContacts) = mode, appendContacts { + contactList = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Contacts.List(includePresences: true)) + |> map(Optional.init) + } else { + contactList = .single(nil) + } + let _ = contactList*/ + /*let emptyInitialView = ChatListNodeView( originalList: EngineChatList( diff --git a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift index 0266f7142a..33b44070db 100644 --- a/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift +++ b/submodules/Components/ViewControllerComponent/Sources/ViewControllerComponent.swift @@ -225,7 +225,7 @@ open class ViewControllerComponentContainer: ViewController { |> deliverOnMainQueue).start(next: { [weak self] presentationData in if let strongSelf = self { var theme = presentationData.theme - if case .modal = presentationMode, theme.list.blocksBackgroundColor.rgb == theme.list.plainBackgroundColor.rgb { + if case .modal = presentationMode { theme = theme.withModalBlocksBackground() } diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index e157558914..c523e844ec 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -870,10 +870,9 @@ public func createPollController(context: AccountContext, updatedPresentationDat ) |> map { presentationData, state, limitsConfiguration -> (ItemListControllerState, (ItemListNodeState, Any)) in var presentationData = presentationData - if presentationData.theme.list.blocksBackgroundColor.rgb == presentationData.theme.list.plainBackgroundColor.rgb { - let updatedTheme = presentationData.theme.withModalBlocksBackground() - presentationData = presentationData.withUpdated(theme: updatedTheme) - } + + let updatedTheme = presentationData.theme.withModalBlocksBackground() + presentationData = presentationData.withUpdated(theme: updatedTheme) var enabled = true if processPollText(state.text).isEmpty { diff --git a/submodules/ContactListUI/Sources/ContactsControllerNode.swift b/submodules/ContactListUI/Sources/ContactsControllerNode.swift index 387fb5b87d..c8c9914257 100644 --- a/submodules/ContactListUI/Sources/ContactsControllerNode.swift +++ b/submodules/ContactListUI/Sources/ContactsControllerNode.swift @@ -114,7 +114,7 @@ final class ContactsControllerNode: ASDisplayNode { strongSelf.presentationData = presentationData - if previousStrings !== presentationData.strings { + if previousStrings.baseLanguageCode != presentationData.strings.baseLanguageCode { strongSelf.stringsPromise.set(.single(presentationData.strings)) } diff --git a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift index a96c38e4d7..c338e765eb 100644 --- a/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift +++ b/submodules/ItemListUI/Sources/Items/ItemListTextItem.swift @@ -75,7 +75,8 @@ public class ItemListTextItem: ListViewItem, ItemListItem { } public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { - private let titleNode: TextNode + private let textNode: TextNode + private var linkHighlightingNode: LinkHighlightingNode? private let activateArea: AccessibilityAreaNode private var item: ItemListTextItem? @@ -85,17 +86,17 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { } public init() { - self.titleNode = TextNode() - self.titleNode.isUserInteractionEnabled = false - self.titleNode.contentMode = .left - self.titleNode.contentsScale = UIScreen.main.scale + self.textNode = TextNode() + self.textNode.isUserInteractionEnabled = false + self.textNode.contentMode = .left + self.textNode.contentsScale = UIScreen.main.scale self.activateArea = AccessibilityAreaNode() self.activateArea.accessibilityTraits = .staticText super.init(layerBacked: false, dynamicBounce: false) - self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) self.addSubnode(self.activateArea) } @@ -106,11 +107,16 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { recognizer.tapActionAtPoint = { _ in return .waitForSingleTap } + recognizer.highlight = { [weak self] point in + if let strongSelf = self { + strongSelf.updateTouchesAtPoint(point) + } + } self.view.addGestureRecognizer(recognizer) } public func asyncLayout() -> (_ item: ItemListTextItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { - let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeTitleLayout = TextNode.asyncLayout(self.textNode) return { item, params, neighbors in let leftInset: CGFloat = 15.0 @@ -158,7 +164,7 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { let _ = titleApply() - strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + params.leftInset, y: topInset), size: titleLayout.size) + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset + params.leftInset, y: topInset), size: titleLayout.size) } }) } @@ -178,9 +184,9 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { switch gesture { case .tap: - let titleFrame = self.titleNode.frame + let titleFrame = self.textNode.frame if let item = self.item, titleFrame.contains(location) { - if let (_, attributes) = self.titleNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) { + if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) { if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { item.linkAction?(.tap(url)) } @@ -194,4 +200,46 @@ public class ItemListTextItemNode: ListViewItemNode, ItemListItemNode { break } } + + private func updateTouchesAtPoint(_ point: CGPoint?) { + if let item = self.item { + var rects: [CGRect]? + if let point = point { + let textNodeFrame = self.textNode.frame + if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + let possibleNames: [String] = [ + TelegramTextAttributes.URL, + TelegramTextAttributes.PeerMention, + TelegramTextAttributes.PeerTextMention, + TelegramTextAttributes.BotCommand, + TelegramTextAttributes.Hashtag + ] + for name in possibleNames { + if let _ = attributes[NSAttributedString.Key(rawValue: name)] { + rects = self.textNode.attributeRects(name: name, at: index) + break + } + } + } + } + + if let rects = rects { + let linkHighlightingNode: LinkHighlightingNode + if let current = self.linkHighlightingNode { + linkHighlightingNode = current + } else { + linkHighlightingNode = LinkHighlightingNode(color: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.2)) + self.linkHighlightingNode = linkHighlightingNode + self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode) + } + linkHighlightingNode.frame = self.textNode.frame + linkHighlightingNode.updateRects(rects) + } else if let linkHighlightingNode = self.linkHighlightingNode { + self.linkHighlightingNode = nil + linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in + linkHighlightingNode?.removeFromSupernode() + }) + } + } + } } diff --git a/submodules/LegacyComponents/Sources/TGPaintUtils.m b/submodules/LegacyComponents/Sources/TGPaintUtils.m index c512277404..d8c3fabf31 100644 --- a/submodules/LegacyComponents/Sources/TGPaintUtils.m +++ b/submodules/LegacyComponents/Sources/TGPaintUtils.m @@ -80,7 +80,7 @@ UIImage *TGPaintCombineCroppedImages(UIImage *background, UIImage *foreground, b CGFloat pRatio = foreground.size.width / originalSize.width; CGSize rotatedContentSize = TGRotatedContentSize(foreground.size, cropRotation); - UIGraphicsBeginImageContextWithOptions(background.size, opaque, 0.0); + UIGraphicsBeginImageContextWithOptions(background.size, opaque, 1.0); CGContextRef context = UIGraphicsGetCurrentContext(); CGRect backgroundRect = CGRectMake(0, 0, background.size.width, background.size.height); diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 2141c5d944..eb9d6215d5 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -1317,7 +1317,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { self.navigationItem.titleView = self.titleView if case let .assets(_, isStandalone) = self.subject, isStandalone { - self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) + self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)) } else { if case let .assets(collection, _) = self.subject, collection != nil { self.navigationItem.leftBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.backPressed)) diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index 2e3299e879..ff47699cce 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -1063,6 +1063,10 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta let previousEditingPhoneIds = Atomic?>(value: nil) let signal = combineLatest(presentationData, statePromise.get(), contactData, hiddenAvatarPromise.get()) |> map { presentationData, state, peerAndContactData, hiddenAvatar -> (ItemListControllerState, (ItemListNodeState, Any)) in + var presentationData = presentationData + let updatedTheme = presentationData.theme.withModalBlocksBackground() + presentationData = presentationData.withUpdated(theme: updatedTheme) + var leftNavigationButton: ItemListNavigationButton? switch subject { case .vcard: diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index 9a69f600dd..673a53fb0f 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -196,7 +196,7 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE }).start() } -func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, peerId: PeerId, completion: @escaping () -> Void) { +public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, peerId: PeerId, completion: @escaping () -> Void) { let imageSignal: Signal switch wallpaper { case let .wallpaper(wallpaper, _): @@ -276,7 +276,7 @@ func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGall croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true) if mode.contains(.blur) { - croppedImage = blurredImage(croppedImage, radius: 45.0)! + croppedImage = blurredImage(croppedImage, radius: 30.0)! } let thumbnailDimensions = finalCropRect.size.fitted(CGSize(width: 320.0, height: 320.0)) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift index e4763e3c45..41b777cc59 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift @@ -35,8 +35,13 @@ enum ThemeAccentColorControllerMode { } final class ThemeAccentColorController: ViewController { + enum ResultMode { + case `default` + case peer(EnginePeer) + } private let context: AccountContext private let mode: ThemeAccentColorControllerMode + private let resultMode: ResultMode private let section: ThemeColorSection private let initialBackgroundColor: UIColor? private var presentationData: PresentationData @@ -57,9 +62,10 @@ final class ThemeAccentColorController: ViewController { var completion: (() -> Void)? - init(context: AccountContext, mode: ThemeAccentColorControllerMode) { + init(context: AccountContext, mode: ThemeAccentColorControllerMode, resultMode: ResultMode = .default) { self.context = context self.mode = mode + self.resultMode = resultMode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } let section: ThemeColorSection = .background @@ -143,7 +149,7 @@ final class ThemeAccentColorController: ViewController { initialWallpaper = self.presentationData.chatWallpaper } - self.displayNode = ThemeAccentColorControllerNode(context: self.context, mode: self.mode, theme: theme, wallpaper: initialWallpaper, dismiss: { [weak self] in + self.displayNode = ThemeAccentColorControllerNode(context: self.context, mode: self.mode, resultMode: self.resultMode, theme: theme, wallpaper: initialWallpaper, dismiss: { [weak self] in if let strongSelf = self { strongSelf.dismiss() } diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift index cdb56f665a..9ead4f5c37 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift @@ -224,7 +224,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate } } - init(context: AccountContext, mode: ThemeAccentColorControllerMode, theme: PresentationTheme, wallpaper: TelegramWallpaper, dismiss: @escaping () -> Void, apply: @escaping (ThemeColorState, UIColor?) -> Void, ready: Promise) { + init(context: AccountContext, mode: ThemeAccentColorControllerMode, resultMode: ThemeAccentColorController.ResultMode, theme: PresentationTheme, wallpaper: TelegramWallpaper, dismiss: @escaping () -> Void, apply: @escaping (ThemeColorState, UIColor?) -> Void, ready: Promise) { self.context = context self.mode = mode self.state = ThemeColorState() @@ -305,10 +305,13 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate let doneButtonType: WallpaperGalleryToolbarDoneButtonType if case .edit(_, _, _, _, _, true, _) = self.mode { doneButtonType = .proceed + } else if case .peer = resultMode { + doneButtonType = .setPeer } else { doneButtonType = .set } self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.theme, strings: self.presentationData.strings, doneButtonType: doneButtonType) + self.toolbarNode.setDoneIsSolid(true, transition: .immediate) self.maskNode = ASImageNode() self.maskNode.displaysAsynchronously = false diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift index 82cb6fad06..bc402517a9 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift @@ -102,17 +102,41 @@ private func availableColors(theme: PresentationTheme) -> [UInt32] { } } -final class ThemeColorsGridController: ViewController { +public final class ThemeColorsGridController: ViewController { + public enum Mode { + case `default` + case peer(EnginePeer) + + var galleryMode: WallpaperGalleryController.Mode { + switch self { + case .default: + return .default + case let .peer(peer): + return .peer(peer, false) + } + } + + var colorPickerMode: ThemeAccentColorController.ResultMode { + switch self { + case .default: + return .default + case let .peer(peer): + return .peer(peer) + } + } + } + private var controllerNode: ThemeColorsGridControllerNode { return self.displayNode as! ThemeColorsGridControllerNode } private let _ready = Promise() - override var ready: Promise { + public override var ready: Promise { return self._ready } private let context: AccountContext + let mode: Mode private var presentationData: PresentationData private var presentationDataDisposable: Disposable? @@ -121,8 +145,9 @@ final class ThemeColorsGridController: ViewController { private var previousContentOffset: GridNodeVisibleContentOffset? - init(context: AccountContext) { + public init(context: AccountContext, mode: Mode = .default) { self.context = context + self.mode = mode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) @@ -169,9 +194,9 @@ final class ThemeColorsGridController: ViewController { } } - override func loadDisplayNode() { - self.displayNode = ThemeColorsGridControllerNode(context: self.context, presentationData: self.presentationData, gradients: availableGradients(theme: self.presentationData.theme), colors: availableColors(theme: self.presentationData.theme), present: { [weak self] controller, arguments in - self?.present(controller, in: .window(.root), with: arguments, blockInteraction: true) + public override func loadDisplayNode() { + self.displayNode = ThemeColorsGridControllerNode(context: self.context, presentationData: self.presentationData, controller: self, gradients: availableGradients(theme: self.presentationData.theme), colors: availableColors(theme: self.presentationData.theme), push: { [weak self] controller in + self?.push(controller) }, pop: { [weak self] in if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { let _ = navigationController.popViewController(animated: true) @@ -193,8 +218,8 @@ final class ThemeColorsGridController: ViewController { } else { themeReference = settings.theme } - - let controller = ThemeAccentColorController(context: strongSelf.context, mode: .background(themeReference: themeReference)) + + let controller = ThemeAccentColorController(context: strongSelf.context, mode: .background(themeReference: themeReference), resultMode: strongSelf.mode.colorPickerMode) controller.completion = { [weak self] in if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController { var controllers = navigationController.viewControllers @@ -249,7 +274,7 @@ final class ThemeColorsGridController: 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/ThemeColorsGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift index b093d4428b..72d0a6ca95 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift @@ -63,11 +63,12 @@ private func preparedThemeColorsGridEntryTransition(context: AccountContext, fro return ThemeColorsGridEntryTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: nil, stationaryItems: stationaryItems, scrollToItem: scrollToItem) } -final class ThemeColorsGridControllerNode: ASDisplayNode { +final class ThemeColorsGridControllerNode: ASDisplayNode { private let context: AccountContext + private weak var controller: ThemeColorsGridController? private var presentationData: PresentationData private var controllerInteraction: ThemeColorsGridControllerInteraction? - private let present: (ViewController, Any?) -> Void + private let push: (ViewController) -> Void private let presentColorPicker: () -> Void let ready = ValuePromise() @@ -87,10 +88,11 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { private var disposable: Disposable? - init(context: AccountContext, presentationData: PresentationData, gradients: [[UInt32]], colors: [UInt32], present: @escaping (ViewController, Any?) -> Void, pop: @escaping () -> Void, presentColorPicker: @escaping () -> Void) { + init(context: AccountContext, presentationData: PresentationData, controller: ThemeColorsGridController, gradients: [[UInt32]], colors: [UInt32], push: @escaping (ViewController) -> Void, pop: @escaping () -> Void, presentColorPicker: @escaping () -> Void) { self.context = context + self.controller = controller self.presentationData = presentationData - self.present = present + self.push = push self.presentColorPicker = presentColorPicker self.gridNode = GridNode() @@ -126,16 +128,36 @@ final class ThemeColorsGridControllerNode: ASDisplayNode { let previousEntries = Atomic<[ThemeColorsGridControllerEntry]?>(value: nil) + 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 ThemeColorsGridController || controller is WallpaperGalleryController { + return false + } + return true + }) + navigationController.setViewControllers(controllers, animated: true) + } + } + let interaction = ThemeColorsGridControllerInteraction(openWallpaper: { [weak self] wallpaper in if let strongSelf = self { let entries = previousEntries.with { $0 } if let entries = entries, !entries.isEmpty { let wallpapers = entries.map { $0.wallpaper } - let controller = WallpaperGalleryController(context: context, source: .list(wallpapers: wallpapers, central: wallpaper, type: .colors)) - controller.apply = { _, _, _ in - pop() + let controller = WallpaperGalleryController(context: context, source: .list(wallpapers: wallpapers, central: wallpaper, type: .colors), mode: strongSelf.controller?.mode.galleryMode ?? .default) + controller.navigationPresentation = .modal + controller.apply = { [weak self] wallpaper, _, _ in + if let strongSelf = self, let mode = strongSelf.controller?.mode, case let .peer(peer) = mode, case let .wallpaper(wallpaperValue, _) = wallpaper { + let _ = (strongSelf.context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: wallpaperValue) + |> deliverOnMainQueue).start(completed: { + dismissControllers() + }) + } else { + pop() + } } - strongSelf.present(controller, nil) + strongSelf.push(controller) } } }) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift index c297370792..852059bd5f 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift @@ -17,20 +17,6 @@ import PresentationDataUtils import MediaPickerUI public final class ThemeGridController: ViewController { - public enum Mode: Equatable { - case `default` - case peer(EnginePeer.Id, TelegramWallpaper?) - - var galleryMode: WallpaperGalleryController.Mode { - switch self { - case .default: - return .default - case let .peer(peerId, _): - return .peer(peerId) - } - } - } - private var controllerNode: ThemeGridControllerNode { return self.displayNode as! ThemeGridControllerNode } @@ -41,7 +27,6 @@ public final class ThemeGridController: ViewController { } private let context: AccountContext - private let mode: Mode private var presentationData: PresentationData private let presentationDataPromise = Promise() @@ -60,9 +45,8 @@ public final class ThemeGridController: ViewController { private var previousContentOffset: GridNodeVisibleContentOffset? - public init(context: AccountContext, mode: Mode = .default) { + public init(context: AccountContext) { self.context = context - self.mode = mode self.presentationData = context.sharedContext.currentPresentationData.with { $0 } self.presentationDataPromise.set(.single(self.presentationData)) @@ -133,44 +117,25 @@ public final class ThemeGridController: ViewController { } public override func loadDisplayNode() { - let dismissControllers = { [weak self] in - if let self, let navigationController = self.navigationController as? NavigationController { - let controllers = navigationController.viewControllers.filter({ controller in - if controller is ThemeGridController || controller is WallpaperGalleryController || controller is MediaPickerScreen { - return false - } - return true - }) - navigationController.setViewControllers(controllers, animated: true) - } - } - self.displayNode = ThemeGridControllerNode(context: self.context, presentationData: self.presentationData, mode: self.mode, presentPreviewController: { [weak self] source in + 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, mode: strongSelf.mode.galleryMode) + let controller = WallpaperGalleryController(context: strongSelf.context, source: source) controller.apply = { [weak self, weak controller] wallpaper, options, cropRect in - if let strongSelf = self, case let .wallpaper(wallpaperValue, _) = wallpaper { - switch strongSelf.mode { - case .default: - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak self, weak controller] in - if let strongSelf = self { - strongSelf.deactivateSearch(animated: false) - strongSelf.controllerNode.scrollToTop(animated: false) + if let strongSelf = self { + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak self, weak controller] in + if let strongSelf = self { + strongSelf.deactivateSearch(animated: false) + strongSelf.controllerNode.scrollToTop(animated: false) + } + if let controller = controller { + switch wallpaper { + case .asset, .contextResult: + controller.dismiss(animated: true) + default: + break } - if let controller = controller { - switch wallpaper { - case .asset, .contextResult: - controller.dismiss(animated: true) - default: - break - } - } - }) - case let .peer(peerId, _): - let _ = (strongSelf.context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: wallpaperValue) - |> deliverOnMainQueue).start(completed: { - dismissControllers() - }) - } + } + }) } } self?.push(controller) @@ -182,21 +147,14 @@ public final class ThemeGridController: ViewController { guard let strongSelf = self else { return } - let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: strongSelf.mode.galleryMode) + let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset)) controller.apply = { [weak self, weak controller] wallpaper, options, cropRect in if let strongSelf = self, let controller = controller { - switch strongSelf.mode { - case .default: - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak controller] in - if let controller = controller { - controller.dismiss(forceAway: true) - } - }) - case let .peer(peerId, _): - uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, peerId: peerId, completion: { - dismissControllers() - }) - } + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak controller] in + if let controller = controller { + controller.dismiss(forceAway: true) + } + }) } } strongSelf.push(controller) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift index 46fe2a32ea..7c69c681d9 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridControllerNode.swift @@ -141,7 +141,6 @@ final class ThemeGridControllerNode: ASDisplayNode { private let context: AccountContext private var presentationData: PresentationData - private let mode: ThemeGridController.Mode private var controllerInteraction: ThemeGridControllerInteraction? private let presentPreviewController: (WallpaperListSource) -> Void @@ -193,9 +192,8 @@ final class ThemeGridControllerNode: ASDisplayNode { private var disposable: Disposable? - init(context: AccountContext, presentationData: PresentationData, mode: ThemeGridController.Mode, presentPreviewController: @escaping (WallpaperListSource) -> Void, presentGallery: @escaping () -> Void, presentColors: @escaping () -> Void, emptyStateUpdated: @escaping (Bool) -> Void, deleteWallpapers: @escaping ([TelegramWallpaper], @escaping () -> Void) -> Void, shareWallpapers: @escaping ([TelegramWallpaper]) -> Void, resetWallpapers: @escaping () -> Void, popViewController: @escaping () -> Void) { + 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) { self.context = context - self.mode = mode self.presentationData = presentationData self.presentPreviewController = presentPreviewController self.presentGallery = presentGallery @@ -265,6 +263,7 @@ final class ThemeGridControllerNode: ASDisplayNode { self.gridNode.addSubnode(self.descriptionItemNode) self.gridNode.addSubnode(self.resetItemNode) self.gridNode.addSubnode(self.resetDescriptionItemNode) + self.addSubnode(self.gridNode) let previousEntries = Atomic<[ThemeGridControllerEntry]?>(value: nil) @@ -340,19 +339,13 @@ final class ThemeGridControllerNode: ASDisplayNode { }) self.controllerInteraction = interaction - var selectFirst = true - if case .peer = self.mode { - selectFirst = false - } - let transition = combineLatest(self.wallpapersPromise.get(), deletedWallpaperIdsPromise.get(), context.sharedContext.presentationData) |> map { wallpapers, deletedWallpaperIds, presentationData -> (ThemeGridEntryTransition, Bool) in var entries: [ThemeGridControllerEntry] = [] - var index = 1 + var index: Int = 0 - if selectFirst { - entries.insert(ThemeGridControllerEntry(index: 0, wallpaper: presentationData.chatWallpaper, isEditable: false, isSelected: true), at: 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) { @@ -422,10 +415,6 @@ final class ThemeGridControllerNode: ASDisplayNode { } } - /*if !entries.isEmpty { - entries = [entries[0]] - }*/ - let previous = previousEntries.swap(entries) return (preparedThemeGridEntryTransition(context: context, from: previous ?? [], to: entries, interaction: interaction), previous == nil) } @@ -757,6 +746,7 @@ 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 self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift index 7f19484096..e0e357ec39 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift @@ -167,7 +167,7 @@ private func updatedFileWallpaper(id: Int64? = nil, accessHash: Int64? = nil, sl public class WallpaperGalleryController: ViewController { public enum Mode { case `default` - case peer(EnginePeer.Id) + case peer(EnginePeer, Bool) } private var galleryNode: GalleryControllerNode { return self.displayNode as! GalleryControllerNode @@ -350,7 +350,7 @@ public class WallpaperGalleryController: ViewController { var i: Int = 0 var updateItems: [GalleryPagerUpdateItem] = [] for entry in entries { - let item = GalleryPagerUpdateItem(index: i, previousIndex: i, item: WallpaperGalleryItem(context: self.context, index: updateItems.count, entry: entry, arguments: arguments, source: self.source)) + let item = GalleryPagerUpdateItem(index: i, previousIndex: i, item: WallpaperGalleryItem(context: self.context, index: updateItems.count, entry: entry, arguments: arguments, source: self.source, mode: self.mode)) updateItems.append(item) i += 1 } @@ -361,7 +361,7 @@ public class WallpaperGalleryController: ViewController { var updateItems: [GalleryPagerUpdateItem] = [] for i in 0 ..< self.entries.count { if i == index { - let item = GalleryPagerUpdateItem(index: index, previousIndex: index, item: WallpaperGalleryItem(context: self.context, index: index, entry: entry, arguments: arguments, source: self.source)) + let item = GalleryPagerUpdateItem(index: index, previousIndex: index, item: WallpaperGalleryItem(context: self.context, index: index, entry: entry, arguments: arguments, source: self.source, mode: self.mode)) updateItems.append(item) } } @@ -457,7 +457,6 @@ public class WallpaperGalleryController: ViewController { return } - switch entry { case let .wallpaper(wallpaper, _): var resource: MediaResource? @@ -496,7 +495,7 @@ public class WallpaperGalleryController: ViewController { } return current.withUpdatedThemeSpecificChatWallpapers(themeSpecificChatWallpapers) }) |> deliverOnMainQueue).start(completed: { - self?.dismiss(forceAway: true) + self?.dismiss(animated: true) }) switch strongSelf.source { @@ -955,7 +954,7 @@ public class WallpaperGalleryController: ViewController { colors = true } - self.galleryNode.pager.replaceItems(zip(0 ..< self.entries.count, self.entries).map({ WallpaperGalleryItem(context: self.context, index: $0, entry: $1, arguments: WallpaperGalleryItemArguments(isColorsList: colors), source: self.source) }), centralItemIndex: self.centralEntryIndex) + self.galleryNode.pager.replaceItems(zip(0 ..< self.entries.count, self.entries).map({ WallpaperGalleryItem(context: self.context, index: $0, entry: $1, arguments: WallpaperGalleryItemArguments(isColorsList: colors), source: self.source, mode: self.mode) }), centralItemIndex: self.centralEntryIndex) if let initialOptions = self.initialOptions, let itemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode { itemNode.options = initialOptions diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index 414bce6a22..a63e7ff887 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -18,6 +18,7 @@ import LocalMediaResources import WallpaperResources import AppBundle import WallpaperBackgroundNode +import TextFormat struct WallpaperGalleryItemArguments { let colorPreview: Bool @@ -42,24 +43,26 @@ class WallpaperGalleryItem: GalleryItem { let entry: WallpaperGalleryEntry let arguments: WallpaperGalleryItemArguments let source: WallpaperListSource + let mode: WallpaperGalleryController.Mode - init(context: AccountContext, index: Int, entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource) { + init(context: AccountContext, index: Int, entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource, mode: WallpaperGalleryController.Mode) { self.context = context self.index = index self.entry = entry self.arguments = arguments self.source = source + self.mode = mode } func node(synchronous: Bool) -> GalleryItemNode { let node = WallpaperGalleryItemNode(context: self.context) - node.setEntry(self.entry, arguments: self.arguments, source: self.source) + node.setEntry(self.entry, arguments: self.arguments, source: self.source, mode: self.mode) return node } func updateNode(node: GalleryItemNode, synchronous: Bool) { if let node = node as? WallpaperGalleryItemNode { - node.setEntry(self.entry, arguments: self.arguments, source: self.source) + node.setEntry(self.entry, arguments: self.arguments, source: self.source, mode: self.mode) } } @@ -84,6 +87,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { var entry: WallpaperGalleryEntry? var source: WallpaperListSource? + var mode: WallpaperGalleryController.Mode? private var colorPreview: Bool = false private var contentSize: CGSize? private var arguments = WallpaperGalleryItemArguments() @@ -263,10 +267,11 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.dismiss() } - func setEntry(_ entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource) { + func setEntry(_ entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource, mode: WallpaperGalleryController.Mode) { let previousArguments = self.arguments self.arguments = arguments self.source = source + self.mode = mode if self.arguments.colorPreview != previousArguments.colorPreview { if self.arguments.colorPreview { @@ -1063,6 +1068,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode { var topMessageText = "" var bottomMessageText = "" + var serviceMessageText: String? var currentWallpaper: TelegramWallpaper = self.presentationData.chatWallpaper if let entry = self.entry, case let .wallpaper(wallpaper, _) = entry { currentWallpaper = wallpaper @@ -1125,6 +1131,14 @@ final class WallpaperGalleryItemNode: GalleryItemNode { } } + if let mode = self.mode, case let .peer(peer, existing) = mode { + topMessageText = presentationData.strings.WallpaperPreview_ChatTopText + bottomMessageText = presentationData.strings.WallpaperPreview_ChatBottomText + if !existing { + serviceMessageText = presentationData.strings.WallpaperPreview_NotAppliedInfo(peer.compactDisplayTitle).string + } + } + let theme = self.presentationData.theme.withUpdated(preview: true) let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil) @@ -1133,6 +1147,14 @@ final class WallpaperGalleryItemNode: GalleryItemNode { let message2 = Message(stableId: 1, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66000, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: topMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil) items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message2], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.nativeNode, availableReactions: nil, isCentered: false)) + if let serviceMessageText { + let attributedText = convertMarkdownToAttributes(NSAttributedString(string: serviceMessageText)) + let entities = generateChatInputTextEntities(attributedText) + + let message3 = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66002, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[peerId], text: "", attributes: [], media: [TelegramMediaAction(action: .customText(text: attributedText.string, entities: entities))], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil) + items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message3], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.nativeNode, availableReactions: nil, isCentered: false)) + } + let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height) if let messageNodes = self.messageNodes { if self.validMessages != [topMessageText, bottomMessageText] { diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index 436536865b..4959dbfba0 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -350,7 +350,11 @@ private func usernameSetupControllerEntries(presentationData: PresentationData, let otherUsernames = peer.usernames.filter { !$0.flags.contains(.isEditable) } if case .bot = mode { - entries.append(.publicLinkInfo(presentationData.theme, "This username cannot be edited.")) + var infoText = presentationData.strings.Username_BotLinkHint + if otherUsernames.isEmpty { + infoText = presentationData.strings.Username_BotLinkHintExtended + } + entries.append(.publicLinkInfo(presentationData.theme, infoText)) } else { var infoText = presentationData.strings.Username_Help if otherUsernames.isEmpty { @@ -460,21 +464,25 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup let _ = (context.account.postbox.loadedPeerWithId(peerId) |> take(1) |> deliverOnMainQueue).start(next: { peer in - var currentAddressName: String = peer.addressName ?? "" - updateState { state in - if let current = state.editingPublicLinkText { - currentAddressName = current + if let user = peer as? TelegramUser, user.botInfo != nil { + context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://fragment.com/", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {}) + } else { + var currentAddressName: String = peer.addressName ?? "" + updateState { state in + if let current = state.editingPublicLinkText { + currentAddressName = current + } + return state } - return state - } - if !currentAddressName.isEmpty { - dismissInputImpl?() - let shareController = ShareController(context: context, subject: .url("https://t.me/\(currentAddressName)")) - shareController.actionCompleted = { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + if !currentAddressName.isEmpty { + dismissInputImpl?() + let shareController = ShareController(context: context, subject: .url("https://t.me/\(currentAddressName)")) + shareController.actionCompleted = { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) + } + presentControllerImpl?(shareController, nil) } - presentControllerImpl?(shareController, nil) } }) }, activateLink: { name in diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 89e9e5bbd8..b8ae91d681 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -160,7 +160,6 @@ private final class BadgeNode: ASDisplayNode { public final class SolidRoundedButtonNode: ASDisplayNode { private var theme: SolidRoundedButtonTheme - private var font: SolidRoundedButtonFont private var fontSize: CGFloat private let gloss: Bool @@ -204,6 +203,14 @@ public final class SolidRoundedButtonNode: ASDisplayNode { } } + public var font: SolidRoundedButtonFont { + didSet { + if let width = self.validLayout { + _ = self.updateLayout(width: width, transition: .immediate) + } + } + } + public var subtitle: String? { didSet { self.updateAccessibilityLabels() diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 9c61d7c61d..e8972b5cf6 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -111,7 +111,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe case let .messageActionSetChatWallPaper(wallpaper): return TelegramMediaAction(action: .setChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper))) case .messageActionSetSameChatWallPaper: - return nil + return TelegramMediaAction(action: .setSameChatWallpaper) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index 1375319350..4d7c7609cf 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -141,7 +141,8 @@ func _internal_setChatWallpaper(account: Account, peerId: PeerId, wallpaper: Tel inputSettings = inputWallpaperAndInputSettings.1 } - return account.network.request(Api.functions.messages.setChatWallPaper(flags: 0, peer: inputPeer, wallpaper: inputWallpaper ?? .inputWallPaperNoFile(id: 0), settings: inputSettings ?? apiWallpaperSettings(WallpaperSettings()), id: nil)) + let flags: Int32 = 1 << 0 + return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: inputWallpaper ?? .inputWallPaperNoFile(id: 0), settings: inputSettings ?? apiWallpaperSettings(WallpaperSettings()), id: nil)) |> `catch` { error in return .complete() } @@ -159,7 +160,20 @@ public enum SetExistingChatWallpaperError { func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId) -> Signal { return account.postbox.transaction { transaction -> Peer? in - return transaction.getPeer(messageId.peerId) + if let peer = transaction.getPeer(messageId.peerId), let message = transaction.getMessage(messageId) { + if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .setChatWallpaper(wallpaper) = action.action { + transaction.updatePeerCachedData(peerIds: Set([peer.id]), update: { _, current in + if let current = current as? CachedUserData { + return current.withUpdatedWallpaper(wallpaper) + } else { + return current + } + }) + } + return peer + } else { + return nil + } } |> castError(SetExistingChatWallpaperError.self) |> mapToSignal { peer -> Signal in diff --git a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift index a50dcdb2a8..42c5da6266 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationTheme.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationTheme.swift @@ -1603,7 +1603,11 @@ public final class PresentationTheme: Equatable { } public func withModalBlocksBackground() -> PresentationTheme { - let list = self.list.withUpdated(blocksBackgroundColor: self.list.modalBlocksBackgroundColor, itemBlocksBackgroundColor: self.list.itemModalBlocksBackgroundColor) - return PresentationTheme(name: self.name, index: self.index, referenceTheme: self.referenceTheme, overallDarkAppearance: self.overallDarkAppearance, intro: self.intro, passcode: self.passcode, rootController: self.rootController, list: list, chatList: self.chatList, chat: self.chat, actionSheet: self.actionSheet, contextMenu: self.contextMenu, inAppNotification: self.inAppNotification, chart: self.chart, preview: self.preview) + if self.list.blocksBackgroundColor.rgb == self.list.plainBackgroundColor.rgb { + let list = self.list.withUpdated(blocksBackgroundColor: self.list.modalBlocksBackgroundColor, itemBlocksBackgroundColor: self.list.itemModalBlocksBackgroundColor) + return PresentationTheme(name: self.name, index: self.index, referenceTheme: self.referenceTheme, overallDarkAppearance: self.overallDarkAppearance, intro: self.intro, passcode: self.passcode, rootController: self.rootController, list: list, chatList: self.chatList, chat: self.chat, actionSheet: self.actionSheet, contextMenu: self.contextMenu, inAppNotification: self.inAppNotification, chart: self.chart, preview: self.preview) + } else { + return self + } } } diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 25b189dc89..0e4f25ab67 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -1029,25 +1029,52 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { } |> distinctUntilChanged - let resultSignal = signal - |> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + let resultSignal = combineLatest( + signal, + hasPremium + ) + |> mapToSignal { keywords, hasPremium -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in var allEmoticons: [String: String] = [:] for keyword in keywords { for emoticon in keyword.emoticons { allEmoticons[emoticon] = keyword.keyword } } - - return combineLatest( - hasPremium, - context.engine.stickers.searchEmoji(emojiString: Array(allEmoticons.keys)) - ) - |> mapToSignal { hasPremium, foundEmoji -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in + let remoteSignal: Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> + if hasPremium { + remoteSignal = context.engine.stickers.searchEmoji(emojiString: Array(allEmoticons.keys)) + } else { + remoteSignal = .single(([], true)) + } + return remoteSignal + |> mapToSignal { foundEmoji -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in if foundEmoji.items.isEmpty && !foundEmoji.isFinalResult { return .complete() } var items: [EmojiPagerContentComponent.Item] = [] + let appendUnicodeEmoji = { + for (_, list) in EmojiPagerContentComponent.staticEmojiMapping { + for emojiString in list { + if allEmoticons[emojiString] != nil { + let item = EmojiPagerContentComponent.Item( + animationData: nil, + content: .staticEmoji(emojiString), + itemFile: nil, + subgroupId: nil, + icon: .none, + tintMode: .none + ) + items.append(item) + } + } + } + } + + if !hasPremium { + appendUnicodeEmoji() + } + var existingIds = Set() for itemFile in foundEmoji.items { if existingIds.contains(itemFile.fileId) { @@ -1061,12 +1088,17 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { let item = EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), - itemFile: itemFile, subgroupId: nil, + itemFile: itemFile, + subgroupId: nil, icon: .none, tintMode: animationData.isTemplate ? .primary : .none ) items.append(item) } + + if hasPremium { + appendUnicodeEmoji() + } return .single([EmojiPagerContentComponent.ItemGroup( supergroupId: "search", diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 6bb3cfc864..0a9b72b465 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -29,26 +29,6 @@ private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bund private let featuredBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeAdd"), color: .white) private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) -private let staticEmojiMapping: [(EmojiPagerContentComponent.StaticEmojiSegment, [String])] = { - guard let path = getAppBundle().path(forResource: "emoji1016", ofType: "txt") else { - return [] - } - guard let string = try? String(contentsOf: URL(fileURLWithPath: path)) else { - return [] - } - - var result: [(EmojiPagerContentComponent.StaticEmojiSegment, [String])] = [] - - let orderedSegments = EmojiPagerContentComponent.StaticEmojiSegment.allCases - - let segments = string.components(separatedBy: "\n\n") - for i in 0 ..< min(segments.count, orderedSegments.count) { - let list = segments[i].components(separatedBy: " ") - result.append((orderedSegments[i], list)) - } - - return result -}() private final class WarpView: UIView { private final class WarpPartView: UIView { @@ -2226,6 +2206,27 @@ public protocol EmojiContentPeekBehavior: AnyObject { } public final class EmojiPagerContentComponent: Component { + public static let staticEmojiMapping: [(EmojiPagerContentComponent.StaticEmojiSegment, [String])] = { + guard let path = getAppBundle().path(forResource: "emoji1016", ofType: "txt") else { + return [] + } + guard let string = try? String(contentsOf: URL(fileURLWithPath: path)) else { + return [] + } + + var result: [(EmojiPagerContentComponent.StaticEmojiSegment, [String])] = [] + + let orderedSegments = EmojiPagerContentComponent.StaticEmojiSegment.allCases + + let segments = string.components(separatedBy: "\n\n") + for i in 0 ..< min(segments.count, orderedSegments.count) { + let list = segments[i].components(separatedBy: " ") + result.append((orderedSegments[i], list)) + } + + return result + }() + public typealias EnvironmentType = (EntityKeyboardChildEnvironment, PagerComponentChildEnvironment) public final class ContentAnimation { diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index 30cc6cfffc..a1e0ef24b3 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -273,10 +273,9 @@ func attachmentFileController(context: AccountContext, updatedPresentationData: ) |> map { presentationData, recentDocuments, state -> (ItemListControllerState, (ItemListNodeState, Any)) in var presentationData = presentationData - if presentationData.theme.list.blocksBackgroundColor.rgb == presentationData.theme.list.plainBackgroundColor.rgb { - let updatedTheme = presentationData.theme.withModalBlocksBackground() - presentationData = presentationData.withUpdated(theme: updatedTheme) - } + + let updatedTheme = presentationData.theme.withModalBlocksBackground() + presentationData = presentationData.withUpdated(theme: updatedTheme) let previousRecentDocuments = previousRecentDocuments.swap(recentDocuments) let crossfade = previousRecentDocuments == nil && recentDocuments != nil diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 15c86331c0..0a5023201f 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -839,10 +839,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.presentThemeSelection() return true case let .setChatWallpaper(wallpaper): + guard message.effectivelyIncoming(strongSelf.context.account.peerId), let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { + return true + } strongSelf.chatDisplayNode.dismissInput() - let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil)) + let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil), mode: .peer(EnginePeer(peer), true)) + wallpaperPreviewController.apply = { wallpaper, options, _ in + let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id) + |> deliverOnMainQueue).start(completed: { [weak wallpaperPreviewController] in + wallpaperPreviewController?.dismiss() + }) + } strongSelf.push(wallpaperPreviewController) return true + case .setSameChatWallpaper: + for attribute in message.attributes { + if let attribute = attribute as? ReplyMessageAttribute { + strongSelf.controllerInteraction?.navigateToMessage(message.id, attribute.messageId) + return true + } + } case let .giftPremium(_, _, duration, _, _): strongSelf.chatDisplayNode.dismissInput() let fromPeerId: PeerId = message.author?.id == strongSelf.context.account.peerId ? strongSelf.context.account.peerId : message.id.peerId @@ -16928,7 +16944,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G Queue.mainQueue().async { unblockingPeer.set(false) if let strongSelf = self, restartBot { - let _ = enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: [.message(text: "/start", attributes: [], inlineStickers: [:], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start() + strongSelf.startBot(strongSelf.presentationInterfaceState.botStartPayload) } } })).start()) @@ -18464,10 +18480,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = (combineLatest(queue: Queue.mainQueue(), self.chatThemeEmoticonPromise.get(), animatedEmojiStickers) |> take(1)).start(next: { [weak self] themeEmoticon, animatedEmojiStickers in - guard let strongSelf = self else { + guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { return } - + + let selectedEmoticon: String? = themeEmoticon let controller = ChatThemeScreen( @@ -18489,8 +18506,47 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let themeController = strongSelf.themeScreen { strongSelf.themeScreen = nil themeController.dimTapped() + } + 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 { + return false + } + return true + }) + navigationController.setViewControllers(controllers, animated: true) + } } - let controller = ThemeGridController(context: strongSelf.context, mode: .peer(peerId, strongSelf.presentationData.chatWallpaper)) + + let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, true)) + controller.navigationPresentation = .modal + controller.customSelection = { [weak self] asset in + guard let strongSelf = self else { + return + } + let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false)) + controller.navigationPresentation = .modal + controller.apply = { [weak self] wallpaper, options, cropRect in + if let strongSelf = self { + uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, peerId: peerId, completion: { + dismissControllers() + }) + } + } + strongSelf.push(controller) + } + strongSelf.push(controller) + }, + changeColor: { + guard let strongSelf = self else { + return + } + if let themeController = strongSelf.themeScreen { + strongSelf.themeScreen = nil + themeController.dimTapped() + } + let controller = ThemeColorsGridController(context: context, mode: .peer(EnginePeer(peer))) controller.navigationPresentation = .modal strongSelf.push(controller) }, diff --git a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift index 80a24f3387..88cc205b4f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -187,7 +187,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_Wallpaper_View, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) - let backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 145.0 + (fromYou ? 0.0 : 37.0)) + let backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 140.0 + (fromYou ? 0.0 : 42.0)) return (backgroundSize.width, { boundingWidth in return (backgroundSize, { [weak self] animation, synchronousLoads, _ in diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index b928764dfe..a695150d1c 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1394,8 +1394,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { if case .scheduledMessages = interfaceState.subject { } else { - if let chatHistoryState = interfaceState.chatHistoryState, case .loaded(true) = chatHistoryState { - if let user = interfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil { + if let user = interfaceState.renderedPeer?.peer as? TelegramUser, user.botInfo != nil { + if let chatHistoryState = interfaceState.chatHistoryState, case .loaded(true) = chatHistoryState { + displayBotStartButton = true + } else if interfaceState.peerIsBlocked { displayBotStartButton = true } } diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index a2686e1b0c..c021a42b2a 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -556,6 +556,7 @@ final class ChatThemeScreen: ViewController { private let peerName: String private let previewTheme: (String?, Bool?) -> Void fileprivate let changeWallpaper: () -> Void + fileprivate let changeColor: () -> Void private let completion: (String?) -> Void private var presentationData: PresentationData @@ -579,6 +580,7 @@ final class ChatThemeScreen: ViewController { peerName: String, previewTheme: @escaping (String?, Bool?) -> Void, changeWallpaper: @escaping () -> Void, + changeColor: @escaping () -> Void, completion: @escaping (String?) -> Void ) { self.context = context @@ -588,6 +590,7 @@ final class ChatThemeScreen: ViewController { self.peerName = peerName self.previewTheme = previewTheme self.changeWallpaper = changeWallpaper + self.changeColor = changeColor self.completion = completion super.init(navigationBarPresentationData: nil) @@ -714,6 +717,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega private let wrappingScrollNode: ASScrollNode private let contentContainerNode: ASDisplayNode private let topContentContainerNode: SparseNode + private let buttonsContentContainerNode: SparseNode private let effectNode: ASDisplayNode private let backgroundNode: ASDisplayNode private let contentBackgroundNode: ASDisplayNode @@ -724,7 +728,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega private let animationContainerNode: ASDisplayNode private var animationNode: AnimationNode private let doneButton: SolidRoundedButtonNode - private let wallpaperButton: HighlightableButtonNode + private let colorButton: HighlightableButtonNode private let listNode: ListView private var entries: [ThemeSettingsThemeEntry]? @@ -781,6 +785,9 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.topContentContainerNode = SparseNode() self.topContentContainerNode.isOpaque = false + self.buttonsContentContainerNode = SparseNode() + self.buttonsContentContainerNode.isOpaque = false + self.backgroundNode = ASDisplayNode() self.backgroundNode.clipsToBounds = true self.backgroundNode.cornerRadius = 16.0 @@ -817,10 +824,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.animationNode.isUserInteractionEnabled = false self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) - self.doneButton.title = initiallySelectedEmoticon == nil ? self.presentationData.strings.Conversation_Theme_DontSetTheme : self.presentationData.strings.Conversation_Theme_Apply - self.wallpaperButton = HighlightableButtonNode() - self.wallpaperButton.setTitle("Set Background from Gallery", with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) + self.colorButton = HighlightableButtonNode() self.listNode = ListView() self.listNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0) @@ -830,6 +835,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.backgroundColor = nil self.isOpaque = false + self.updateButtons() + self.addSubnode(self.dimNode) self.wrappingScrollNode.view.delegate = self @@ -838,13 +845,14 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.wrappingScrollNode.addSubnode(self.backgroundNode) self.wrappingScrollNode.addSubnode(self.contentContainerNode) self.wrappingScrollNode.addSubnode(self.topContentContainerNode) + self.wrappingScrollNode.addSubnode(self.buttonsContentContainerNode) self.backgroundNode.addSubnode(self.effectNode) self.backgroundNode.addSubnode(self.contentBackgroundNode) self.contentContainerNode.addSubnode(self.titleNode) self.contentContainerNode.addSubnode(self.textNode) - self.contentContainerNode.addSubnode(self.doneButton) - self.contentContainerNode.addSubnode(self.wallpaperButton) + self.buttonsContentContainerNode.addSubnode(self.doneButton) + self.buttonsContentContainerNode.addSubnode(self.colorButton) self.topContentContainerNode.addSubnode(self.animationContainerNode) self.animationContainerNode.addSubnode(self.animationNode) @@ -860,7 +868,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega strongSelf.completion?(strongSelf.selectedEmoticon) } } - self.wallpaperButton.addTarget(self, action: #selector(self.wallpaperButtonPressed), forControlEvents: .touchUpInside) + self.colorButton.addTarget(self, action: #selector(self.colorButtonPressed), forControlEvents: .touchUpInside) self.disposable.set(combineLatest(queue: Queue.mainQueue(), self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager), self.selectedEmoticonPromise.get(), self.isDarkAppearancePromise.get()).start(next: { [weak self] themes, selectedEmoticon, isDarkAppearance in guard let strongSelf = self else { @@ -881,24 +889,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega let action: (String?) -> Void = { [weak self] emoticon in if let strongSelf = self, strongSelf.selectedEmoticon != emoticon { - strongSelf.animateCrossfade(animateIcon: true) - - strongSelf.previewTheme?(emoticon, strongSelf.isDarkAppearance) - strongSelf.selectedEmoticon = emoticon - let _ = ensureThemeVisible(listNode: strongSelf.listNode, emoticon: emoticon, animated: true) - - let doneButtonTitle: String - if emoticon == nil { - doneButtonTitle = strongSelf.initiallySelectedEmoticon == nil ? strongSelf.presentationData.strings.Conversation_Theme_DontSetTheme : strongSelf.presentationData.strings.Conversation_Theme_Reset - } else { - doneButtonTitle = strongSelf.presentationData.strings.Conversation_Theme_Apply - } - strongSelf.doneButton.title = doneButtonTitle - - strongSelf.themeSelectionsCount += 1 - if strongSelf.themeSelectionsCount == 2 { - strongSelf.maybePresentPreviewTooltip() - } + strongSelf.setEmoticon(emoticon) } } let previousEntries = strongSelf.entries ?? [] @@ -987,6 +978,51 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega }) } + private func setEmoticon(_ emoticon: String?) { + self.animateCrossfade(animateIcon: true) + + self.previewTheme?(emoticon, self.isDarkAppearance) + self.selectedEmoticon = emoticon + let _ = ensureThemeVisible(listNode: self.listNode, emoticon: emoticon, animated: true) + + self.updateButtons() + + self.themeSelectionsCount += 1 + if self.themeSelectionsCount == 2 { + self.maybePresentPreviewTooltip() + } + } + + private func updateButtons() { + let doneButtonTitle: String + let otherButtonTitle: String + var accentButtonTheme = true + if self.selectedEmoticon == self.initiallySelectedEmoticon { + doneButtonTitle = self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper + otherButtonTitle = self.presentationData.strings.Conversation_Theme_SetColorWallpaper + accentButtonTheme = false + } else if self.selectedEmoticon == nil && self.initiallySelectedEmoticon != nil { + doneButtonTitle = self.presentationData.strings.Conversation_Theme_Reset + otherButtonTitle = self.presentationData.strings.Conversation_Theme_OtherOptions + } else { + doneButtonTitle = self.presentationData.strings.Conversation_Theme_Apply + otherButtonTitle = self.presentationData.strings.Conversation_Theme_OtherOptions + } + + let buttonTheme: SolidRoundedButtonTheme + if accentButtonTheme { + buttonTheme = SolidRoundedButtonTheme(theme: self.presentationData.theme) + } else { + buttonTheme = SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.actionSheet.itemBackgroundColor, foregroundColor: self.presentationData.theme.actionSheet.controlAccentColor) + } + self.doneButton.title = doneButtonTitle + self.doneButton.font = accentButtonTheme ? .bold : .regular + self.doneButton.updateTheme(buttonTheme) + + + self.colorButton.setTitle(otherButtonTitle, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) + } + func updatePresentationData(_ presentationData: PresentationData) { guard !self.animatedOut else { return @@ -1003,6 +1039,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal) self.doneButton.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) + self.updateButtons() if self.animationNode.isPlaying { if let animationNode = self.animationNode.makeCopy(colors: iconColors(theme: self.presentationData.theme), progress: 0.2) { @@ -1037,8 +1074,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega self.cancel?() } - @objc func wallpaperButtonPressed() { - self.controller?.changeWallpaper() + @objc func colorButtonPressed() { + self.controller?.changeColor() } func dimTapped() { @@ -1258,11 +1295,12 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - 50.0 - insets.bottom - 6.0, width: contentFrame.width, height: doneButtonHeight)) - let wallpaperButtonSize = self.wallpaperButton.measure(CGSize(width: contentFrame.width - buttonInset * 2.0, height: .greatestFiniteMagnitude)) - transition.updateFrame(node: self.wallpaperButton, frame: CGRect(origin: CGPoint(x: floor((contentFrame.width - wallpaperButtonSize.width) / 2.0), y: contentHeight - wallpaperButtonSize.height - insets.bottom - 6.0 - 9.0), size: wallpaperButtonSize)) + let colorButtonSize = self.colorButton.measure(CGSize(width: contentFrame.width - buttonInset * 2.0, height: .greatestFiniteMagnitude)) + transition.updateFrame(node: self.colorButton, frame: CGRect(origin: CGPoint(x: floor((contentFrame.width - colorButtonSize.width) / 2.0), y: contentHeight - colorButtonSize.height - insets.bottom - 6.0 - 9.0), size: colorButtonSize)) transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) transition.updateFrame(node: self.topContentContainerNode, frame: contentContainerFrame) + transition.updateFrame(node: self.buttonsContentContainerNode, frame: contentContainerFrame) var listInsets = UIEdgeInsets() listInsets.top += layout.safeInsets.left + 12.0 diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift index 0bd2cc6f99..2ee3fccb9a 100644 --- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift @@ -398,7 +398,7 @@ private func fetchCachedBlurredWallpaperRepresentation(resource: MediaResource, let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))" let url = URL(fileURLWithPath: path) - if let colorImage = blurredImage(image, radius: 45.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + if let colorImage = blurredImage(image, radius: 30.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) let colorQuality: Float = 0.5 @@ -447,7 +447,7 @@ private func fetchCachedBlurredWallpaperRepresentation(account: Account, resourc let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))" let url = URL(fileURLWithPath: path) - if let colorImage = blurredImage(image, radius: 45.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { + if let colorImage = blurredImage(image, radius: 30.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) { CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary) let colorQuality: Float = 0.5 diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenCommentItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenCommentItem.swift index d7eef8a352..eae761c6c2 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenCommentItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenCommentItem.swift @@ -5,12 +5,18 @@ import TextFormat import Markdown final class PeerInfoScreenCommentItem: PeerInfoScreenItem { + enum LinkAction { + case tap(String) + } + let id: AnyHashable let text: String + let linkAction: ((LinkAction) -> Void)? - init(id: AnyHashable, text: String) { + init(id: AnyHashable, text: String, linkAction: ((LinkAction) -> Void)? = nil) { self.id = id self.text = text + self.linkAction = linkAction } func node() -> PeerInfoScreenItemNode { @@ -20,9 +26,11 @@ final class PeerInfoScreenCommentItem: PeerInfoScreenItem { private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode { private let textNode: ImmediateTextNode + private var linkHighlightingNode: LinkHighlightingNode? private let activateArea: AccessibilityAreaNode private var item: PeerInfoScreenCommentItem? + private var presentationData: PresentationData? override init() { self.textNode = ImmediateTextNode() @@ -38,12 +46,28 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode { self.addSubnode(self.activateArea) } + override func didLoad() { + super.didLoad() + + let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:))) + recognizer.tapActionAtPoint = { _ in + return .waitForSingleTap + } + recognizer.highlight = { [weak self] point in + if let strongSelf = self { + strongSelf.updateTouchesAtPoint(point) + } + } + self.view.addGestureRecognizer(recognizer) + } + override func update(width: CGFloat, safeInsets: UIEdgeInsets, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, hasCorners: Bool, transition: ContainedViewLayoutTransition) -> CGFloat { guard let item = item as? PeerInfoScreenCommentItem else { return 10.0 } self.item = item + self.presentationData = presentationData let sideInset: CGFloat = 16.0 + safeInsets.left let verticalInset: CGFloat = 7.0 @@ -72,4 +96,69 @@ private final class PeerInfoScreenCommentItemNode: PeerInfoScreenItemNode { return height } + + @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { + switch gesture { + case .tap: + let titleFrame = self.textNode.frame + if let item = self.item, titleFrame.contains(location) { + if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: location.x - titleFrame.minX, y: location.y - titleFrame.minY)) { + if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String { + item.linkAction?(.tap(url)) + } + } + } + default: + break + } + } + default: + break + } + } + + private func updateTouchesAtPoint(_ point: CGPoint?) { + if let _ = self.item, let presentationData = self.presentationData { + var rects: [CGRect]? + if let point = point { + let textNodeFrame = self.textNode.frame + if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) { + let possibleNames: [String] = [ + TelegramTextAttributes.URL, + TelegramTextAttributes.PeerMention, + TelegramTextAttributes.PeerTextMention, + TelegramTextAttributes.BotCommand, + TelegramTextAttributes.Hashtag + ] + for name in possibleNames { + if let _ = attributes[NSAttributedString.Key(rawValue: name)] { + rects = self.textNode.attributeRects(name: name, at: index) + break + } + } + } + } + + if let rects = rects { + let linkHighlightingNode: LinkHighlightingNode + if let current = self.linkHighlightingNode { + linkHighlightingNode = current + } else { + linkHighlightingNode = LinkHighlightingNode(color: presentationData.theme.list.itemAccentColor.withAlphaComponent(0.2)) + self.linkHighlightingNode = linkHighlightingNode + self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode) + } + linkHighlightingNode.frame = self.textNode.frame + linkHighlightingNode.updateRects(rects) + } else if let linkHighlightingNode = self.linkHighlightingNode { + self.linkHighlightingNode = nil + linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in + linkHighlightingNode?.removeFromSupernode() + }) + } + } + } } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 8045eab853..3ae82617db 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1405,16 +1405,18 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL interaction.editingOpenPublicLinkSetup() })) - items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemIntro, text: "Edit Intro", icon: UIImage(bundleImageName: "Peer Info/BotIntro"), action: { + items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemIntro, text: presentationData.strings.PeerInfo_Bot_EditIntro, icon: UIImage(bundleImageName: "Peer Info/BotIntro"), action: { interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-intro", behavior: .interactive))) })) - items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemCommands, text: "Edit Commands", icon: UIImage(bundleImageName: "Peer Info/BotCommands"), action: { + items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemCommands, text: presentationData.strings.PeerInfo_Bot_EditCommands, icon: UIImage(bundleImageName: "Peer Info/BotCommands"), action: { interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: "\(user.addressName ?? "")-commands", behavior: .interactive))) })) - items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemBotSettings, text: "Change Bot Settings", icon: UIImage(bundleImageName: "Peer Info/BotSettings"), action: { + items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemBotSettings, text: presentationData.strings.PeerInfo_Bot_ChangeSettings, icon: UIImage(bundleImageName: "Peer Info/BotSettings"), action: { interaction.openPeerMention("botfather", .withBotStartPayload(ChatControllerInitialBotStart(payload: user.addressName ?? "", behavior: .interactive))) })) - items[.peerSettings]!.append(PeerInfoScreenCommentItem(id: ItemBotInfo, text: "Use [@BotFather]() to manage this bot.")) + items[.peerSettings]!.append(PeerInfoScreenCommentItem(id: ItemBotInfo, text: presentationData.strings.PeerInfo_Bot_BotFatherInfo, linkAction: { _ in + interaction.openPeerMention("botfather", .default) + })) } else if !user.flags.contains(.isSupport) { let compactName = EnginePeer(user).compactDisplayTitle items[.peerDataSettings]!.append(PeerInfoScreenActionItem(id: ItemSuggest, text: presentationData.strings.UserInfo_SuggestPhoto(compactName).string, color: .accent, icon: UIImage(bundleImageName: "Peer Info/SuggestAvatar"), action: { diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift index 7e149abeb2..3b6c123ad8 100644 --- a/submodules/WallpaperResources/Sources/WallpaperResources.swift +++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift @@ -300,6 +300,7 @@ public func wallpaperImage(account: Account, accountManager: AccountManager