From 93b84ebf24187d09a796f9263b183efb8330e330 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 3 May 2022 20:04:31 +0400 Subject: [PATCH] Various improvements --- .../Telegram-iOS/en.lproj/Localizable.strings | 17 + submodules/ChatListUI/BUILD | 1 + .../ChatListUI/Sources/ChatContextMenus.swift | 28 +- .../Sources/Node/ChatListNode.swift | 19 +- .../Sources/SolidRoundedButtonComponent.swift | 15 + submodules/ContextUI/BUILD | 1 + .../ContextUI/Sources/ContextActionNode.swift | 17 +- .../ContextUI/Sources/ContextController.swift | 5 + .../ContextControllerActionsStackNode.swift | 28 +- .../Sources/ChannelVisibilityController.swift | 50 +- .../Sources/IncreaseLimitFooterItem.swift | 141 ++++ .../Sources/IncreaseLimitHeaderItem.swift | 201 +++++ .../Sources/OldChannelsController.swift | 182 +---- submodules/PremiumUI/BUILD | 5 + .../PremiumUI/Sources/LimitScreen.swift | 754 ++++++++++++++++++ .../Sources/ReactionCarouselNode.swift | 87 +- .../Sources/SolidRoundedButtonNode.swift | 2 +- .../Sources/ReactionSelectionNode.swift | 45 +- .../Sources/SolidRoundedButtonNode.swift | 158 +++- .../State/ManagedConfigurationUpdates.swift | 4 +- .../SyncCore_LimitsConfiguration.swift | 8 +- .../Resources/PresentationResourcesChat.swift | 18 +- .../PresentationResourcesChatList.swift | 8 +- .../Premium/Chat.imageset/Contents.json | 12 + .../Premium/Chat.imageset/badgechat_24.pdf | 76 ++ .../Premium/ContextX2.imageset/Contents.json | 11 + .../Premium/Folder.imageset/Contents.json | 12 + .../Folder.imageset/badgefolder_24.pdf | 108 +++ .../Premium/Group.imageset/Contents.json | 12 + .../Premium/Group.imageset/badgegroup_24.pdf | 96 +++ .../Premium/Link.imageset/Contents.json | 12 + .../Premium/Link.imageset/badgelink_24.pdf | 101 +++ .../Premium/Pin.imageset/Contents.json | 12 + .../Premium/Pin.imageset/badgepin_24.pdf | 108 +++ .../Premium/Tmp.imageset/Contents.json | 21 + .../Premium/Tmp.imageset/Tmp.png | Bin 0 -> 25736 bytes .../Premium/Tmp2.imageset/Contents.json | 21 + .../Premium/Tmp2.imageset/Tmp2.png | Bin 0 -> 4896 bytes .../Premium/X2.imageset/Contents.json | 12 + .../Premium/X2.imageset/x2.pdf | 129 +++ .../TelegramUI/Sources/ChatController.swift | 5 +- .../ChatInterfaceStateContextMenus.swift | 36 +- .../Sources/ChatMediaInputNode.swift | 2 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 8 +- .../Sources/TelegramRootController.swift | 2 - .../Sources/UndoOverlayController.swift | 2 +- .../Sources/UndoOverlayControllerNode.swift | 16 +- 47 files changed, 2320 insertions(+), 288 deletions(-) create mode 100644 submodules/PeerInfoUI/Sources/IncreaseLimitFooterItem.swift create mode 100644 submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift create mode 100644 submodules/PremiumUI/Sources/LimitScreen.swift create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Chat.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Chat.imageset/badgechat_24.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/ContextX2.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/badgefolder_24.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/badgegroup_24.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/badgelink_24.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/badgepin_24.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Tmp.png create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/Tmp2.imageset/Tmp2.png create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/X2.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Premium/X2.imageset/x2.pdf diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 6eeb296390..39290bfcbb 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7547,3 +7547,20 @@ Sorry for the inconvenience."; "Chat.MultipleTypingPair" = "%@ and %@"; "Chat.MultipleTypingMore" = "%@ and %@ others"; + +"DialogList.ExtendedPinLimitError" = "Sorry, you can pin more than **%1$@** chats to the top. Unpin some of the currently pinned ones or subscribe to **Telegram Premium** to double the limit to **%2$@** chats."; +"DialogList.ExtendedPinLimitIncrease" = "Increase Limit"; + +"Group.Username.RemoveExistingUsernamesTitle" = "Too Many Public Links"; +"Group.Username.RemoveExistingUsernamesOrExtendInfo" = "You have reserved too many public links. Try revoking a link from an older group or channel, or upgrade to **Telegram Premium** to double the limit to **%@** public links."; +"Group.Username.IncreaseLimit" = "Increase Limit"; + +"OldChannels.TooManyCommunitiesTitle" = "Too Many Communities"; +"OldChannels.TooManyCommunitiesText" = "You are a member of **%@** groups and channels. Please leave some before joining a new one or upgrade to **Telegram Premium** to double the limit to **%@** groups and channels."; +"OldChannels.IncreaseLimit" = "Increase Limit"; +"OldChannels.LeaveCommunities_1" = "Leave %@ Community"; +"OldChannels.LeaveCommunities_any" = "Leave %@ Communities"; + +"Stickers.FaveLimitReachedInfo" = "Sorry, you can't add more than **%@** stickers to favorites. Replace an older saved sticker or subscribe to **Telegram Premium** to double the limit to **%@** favorites stickers."; +"Stickers.FaveLimitReplaceOlder" = "Replace Older Sticker"; +"Stickers.FaveLimitIncrease" = "Increase Limit"; diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index fae5b54a5f..7c56109f2e 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -67,6 +67,7 @@ swift_library( "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", "//submodules/Components/ProgressIndicatorComponent:ProgressIndicatorComponent", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/PremiumUI:PremiumUI", ], visibility = [ "//visibility:public", diff --git a/submodules/ChatListUI/Sources/ChatContextMenus.swift b/submodules/ChatListUI/Sources/ChatContextMenus.swift index 7752f8e0c5..1dfa364c8b 100644 --- a/submodules/ChatListUI/Sources/ChatContextMenus.swift +++ b/submodules/ChatListUI/Sources/ChatContextMenus.swift @@ -311,16 +311,36 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch } if isPinned || chatListFilter == nil || peerId.namespace != Namespaces.Peer.SecretChat { - items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in + items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin" : "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { c, f in let _ = (context.engine.peers.toggleItemPinned(location: location, itemId: .peer(peerId)) |> deliverOnMainQueue).start(next: { result in switch result { case .done: - break + f(.default) case .limitExceeded: - break + var subItems: [ContextMenuItem] = [] + + subItems.append(.action(ContextMenuActionItem(text: strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) + }, action: { c, _ in + c.popItems() + }))) + subItems.append(.separator) + + subItems.append(.action(ContextMenuActionItem(text: strings.DialogList_ExtendedPinLimitError("5", "10").string, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in + return nil + }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + + subItems.append(.action(ContextMenuActionItem(text: strings.DialogList_ExtendedPinLimitIncrease, icon: { _ in + return nil + }, action: { _, f in + f(.default) + + }))) + + c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) } - f(.default) + }) }))) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift index c88e6b7822..6132643570 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift @@ -13,6 +13,7 @@ import ContextUI import ItemListUI import SearchUI import ChatListSearchItemHeader +import PremiumUI public enum ChatListNodeMode { case chatList @@ -840,14 +841,16 @@ public final class ChatListNode: ListView { switch result { case .done: break - case let .limitExceeded(maxCount): - let text: String - if chatListFilter != nil { - text = strongSelf.currentState.presentationData.strings.DialogList_UnknownPinLimitError - } else { - text = strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").string - } - strongSelf.presentAlert?(text) + case .limitExceeded: + let controller = LimitScreen(context: strongSelf.context, subject: .pins) + strongSelf.present?(controller) +// let text: String +// if chatListFilter != nil { +// text = strongSelf.currentState.presentationData.strings.DialogList_UnknownPinLimitError +// } else { +// text = strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(maxCount)").string +// } +// strongSelf.presentAlert?(text) } } }) diff --git a/submodules/Components/SolidRoundedButtonComponent/Sources/SolidRoundedButtonComponent.swift b/submodules/Components/SolidRoundedButtonComponent/Sources/SolidRoundedButtonComponent.swift index 7fe9f3bb59..333fb5eae8 100644 --- a/submodules/Components/SolidRoundedButtonComponent/Sources/SolidRoundedButtonComponent.swift +++ b/submodules/Components/SolidRoundedButtonComponent/Sources/SolidRoundedButtonComponent.swift @@ -3,6 +3,7 @@ import UIKit import ComponentFlow import Display import SolidRoundedButtonNode +import AppBundle public final class SolidRoundedButtonComponent: Component { public typealias Theme = SolidRoundedButtonTheme @@ -15,6 +16,8 @@ public final class SolidRoundedButtonComponent: Component { public let height: CGFloat public let cornerRadius: CGFloat public let gloss: Bool + public let iconName: String? + public let iconPosition: SolidRoundedButtonIconPosition public let action: () -> Void public init( @@ -26,6 +29,8 @@ public final class SolidRoundedButtonComponent: Component { height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false, + iconName: String? = nil, + iconPosition: SolidRoundedButtonIconPosition = .left, action: @escaping () -> Void ) { self.title = title @@ -36,6 +41,8 @@ public final class SolidRoundedButtonComponent: Component { self.height = height self.cornerRadius = cornerRadius self.gloss = gloss + self.iconName = iconName + self.iconPosition = iconPosition self.action = action } @@ -64,6 +71,12 @@ public final class SolidRoundedButtonComponent: Component { if lhs.gloss != rhs.gloss { return false } + if lhs.iconName != rhs.iconName { + return false + } + if lhs.iconPosition != rhs.iconPosition { + return false + } return true } @@ -84,6 +97,8 @@ public final class SolidRoundedButtonComponent: Component { cornerRadius: component.cornerRadius, gloss: component.gloss ) + button.iconPosition = component.iconPosition + button.icon = component.iconName.flatMap { UIImage(bundleImageName: $0) } self.button = button self.addSubview(button) diff --git a/submodules/ContextUI/BUILD b/submodules/ContextUI/BUILD index 54f8c87581..ec5a372e7e 100644 --- a/submodules/ContextUI/BUILD +++ b/submodules/ContextUI/BUILD @@ -18,6 +18,7 @@ swift_library( "//submodules/AppBundle:AppBundle", "//submodules/AccountContext:AccountContext", "//submodules/ReactionSelectionNode:ReactionSelectionNode", + "//submodules/Markdown:Markdown", ], visibility = [ "//visibility:public", diff --git a/submodules/ContextUI/Sources/ContextActionNode.swift b/submodules/ContextUI/Sources/ContextActionNode.swift index 9fcacd707a..6d4881cafd 100644 --- a/submodules/ContextUI/Sources/ContextActionNode.swift +++ b/submodules/ContextUI/Sources/ContextActionNode.swift @@ -3,6 +3,7 @@ import AsyncDisplayKit import Display import TelegramPresentationData import SwiftSignalKit +import Markdown public enum ContextActionSibling { case none @@ -53,6 +54,9 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol { let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize) let smallTextFont = Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)) + let boldTextFont = Font.semibold(presentationData.listsFontSize.baseDisplaySize) + let smallBoldTextFont = Font.semibold(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)) + self.backgroundNode = ASDisplayNode() self.backgroundNode.isAccessibilityElement = false self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor @@ -76,18 +80,29 @@ public final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol { } let titleFont: UIFont + let titleBoldFont: UIFont switch action.textFont { case .regular: titleFont = textFont + titleBoldFont = boldTextFont case .small: titleFont = smallTextFont + titleBoldFont = smallBoldTextFont case let .custom(customFont): titleFont = customFont + titleBoldFont = customFont } let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) - self.textNode.attributedText = NSAttributedString(string: action.text, font: titleFont, textColor: textColor) + if action.parseMarkdown { + let attributedText = parseMarkdownIntoAttributedString(action.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: textColor), bold: MarkdownAttributeSet(font: titleBoldFont, textColor: textColor), link: MarkdownAttributeSet(font: titleFont, textColor: textColor), linkAttribute: { _ in + return nil + })) + self.textNode.attributedText = attributedText + } else { + self.textNode.attributedText = NSAttributedString(string: action.text, font: titleFont, textColor: textColor) + } switch action.textLayout { case .singleLine: diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index bd72e883a4..ffb2f11e73 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -92,6 +92,7 @@ public final class ContextMenuActionItem { public let textColor: ContextMenuActionItemTextColor public let textFont: ContextMenuActionItemFont public let textLayout: ContextMenuActionItemTextLayout + public let parseMarkdown: Bool public let badge: ContextMenuActionBadge? public let icon: (PresentationTheme) -> UIImage? public let iconSource: ContextMenuActionItemIconSource? @@ -103,6 +104,7 @@ public final class ContextMenuActionItem { textColor: ContextMenuActionItemTextColor = .primary, textLayout: ContextMenuActionItemTextLayout = .twoLinesMax, textFont: ContextMenuActionItemFont = .regular, + parseMarkdown: Bool = false, badge: ContextMenuActionBadge? = nil, icon: @escaping (PresentationTheme) -> UIImage?, iconSource: ContextMenuActionItemIconSource? = nil, @@ -114,6 +116,7 @@ public final class ContextMenuActionItem { textColor: textColor, textLayout: textLayout, textFont: textFont, + parseMarkdown: parseMarkdown, badge: badge, icon: icon, iconSource: iconSource, @@ -131,6 +134,7 @@ public final class ContextMenuActionItem { textColor: ContextMenuActionItemTextColor = .primary, textLayout: ContextMenuActionItemTextLayout = .twoLinesMax, textFont: ContextMenuActionItemFont = .regular, + parseMarkdown: Bool = false, badge: ContextMenuActionBadge? = nil, icon: @escaping (PresentationTheme) -> UIImage?, iconSource: ContextMenuActionItemIconSource? = nil, @@ -141,6 +145,7 @@ public final class ContextMenuActionItem { self.textColor = textColor self.textFont = textFont self.textLayout = textLayout + self.parseMarkdown = parseMarkdown self.badge = badge self.icon = icon self.iconSource = iconSource diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 49a5be128b..c69127b8c1 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -8,6 +8,7 @@ import TelegramCore import SwiftSignalKit import AccountContext import ReactionSelectionNode +import Markdown public protocol ContextControllerActionsStackItemNode: ASDisplayNode { func update( @@ -170,17 +171,22 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin subtitle = subtitleValue case .multiline: self.titleLabelNode.maximumNumberOfLines = 0 + self.titleLabelNode.lineSpacing = 0.1 } let titleFont: UIFont + let titleBoldFont: UIFont switch self.item.textFont { case let .custom(font): titleFont = font + titleBoldFont = font case .small: let smallTextFont = Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)) titleFont = smallTextFont + titleBoldFont = Font.semibold(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0)) case .regular: titleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize) + titleBoldFont = Font.semibold(presentationData.listsFontSize.baseDisplaySize) } let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0) @@ -196,11 +202,23 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin titleColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4) } - self.titleLabelNode.attributedText = NSAttributedString( - string: self.item.text, - font: titleFont, - textColor: titleColor - ) + if self.item.parseMarkdown { + let attributedText = parseMarkdownIntoAttributedString( + self.item.text, + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: titleFont, textColor: titleColor), + bold: MarkdownAttributeSet(font: titleBoldFont, textColor: titleColor), + link: MarkdownAttributeSet(font: titleFont, textColor: titleColor), + linkAttribute: { _ in return nil } + ) + ) + self.titleLabelNode.attributedText = attributedText + } else { + self.titleLabelNode.attributedText = NSAttributedString( + string: self.item.text, + font: titleFont, + textColor: titleColor) + } self.subtitleNode.attributedText = subtitle.flatMap { subtitle in return NSAttributedString( diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index ff2fe98453..b05755a7a1 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -59,6 +59,7 @@ private final class ChannelVisibilityControllerArguments { private enum ChannelVisibilitySection: Int32 { case type + case limitInfo case link case linkActions case joinToSend @@ -87,6 +88,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case publicLinkHeader(PresentationTheme, String) case publicLinkAvailability(PresentationTheme, String, Bool) + case linksLimitInfo(PresentationTheme, String, String, Int) case editablePublicLink(PresentationTheme, PresentationStrings, String, String) case privateLinkHeader(PresentationTheme, String) case privateLink(PresentationTheme, ExportedInvitation?, [EnginePeer], Int32, Bool) @@ -115,6 +117,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { switch self { case .typeHeader, .typePublic, .typePrivate, .typeInfo: return ChannelVisibilitySection.type.rawValue + case .linksLimitInfo: + return ChannelVisibilitySection.limitInfo.rawValue case .publicLinkHeader, .publicLinkAvailability, .privateLinkHeader, .privateLink, .editablePublicLink, .privateLinkInfo, .publicLinkInfo, .publicLinkStatus: return ChannelVisibilitySection.link.rawValue case .privateLinkManage, .privateLinkManageInfo: @@ -144,22 +148,24 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { return 4 case .publicLinkAvailability: return 5 - case .privateLinkHeader: + case .linksLimitInfo: return 6 - case .privateLink: + case .privateLinkHeader: return 7 - case .editablePublicLink: + case .privateLink: return 8 - case .privateLinkInfo: + case .editablePublicLink: return 9 - case .publicLinkStatus: + case .privateLinkInfo: return 10 - case .publicLinkInfo: + case .publicLinkStatus: return 11 - case .existingLinksInfo: + case .publicLinkInfo: return 12 + case .existingLinksInfo: + return 13 case let .existingLinkPeerItem(index, _, _, _, _, _, _, _): - return 13 + index + return 14 + index case .privateLinkManage: return 1000 case .privateLinkManageInfo: @@ -221,6 +227,12 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { } else { return false } + case let .linksLimitInfo(lhsTheme, lhsTitle, lhsText, lhsLimit): + if case let .linksLimitInfo(rhsTheme, rhsTitle, rhsText, rhsLimit) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsText == rhsText, lhsLimit == rhsLimit { + return true + } else { + return false + } case let .privateLinkHeader(lhsTheme, lhsTitle): if case let .privateLinkHeader(rhsTheme, rhsTitle) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle { return true @@ -381,6 +393,8 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { let attr = NSMutableAttributedString(string: text, textColor: value ? theme.list.freeTextColor : theme.list.freeTextErrorColor) attr.addAttribute(.font, value: Font.regular(13), range: NSMakeRange(0, attr.length)) return ItemListActivityTextItem(displayActivity: value, presentationData: presentationData, text: attr, sectionId: self.section) + case let .linksLimitInfo(theme, title, text, limit): + return IncreaseLimitHeaderItem(theme: theme, icon: .link, count: limit, title: title, text: text, sectionId: self.section) case let .privateLinkHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .privateLink(_, invite, peers, importersCount, displayImporters): @@ -715,7 +729,8 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa if displayAvailability { if let publicChannelsToRevoke = publicChannelsToRevoke { - entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false)) + entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesTitle, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(20)").string, 10)) + var index: Int32 = 0 for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in var lhsDate: Int32 = 0 @@ -803,7 +818,7 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa case .privateChannel: let invite = (view.cachedData as? CachedChannelData)?.exportedInvitation entries.append(.privateLinkHeader(presentationData.theme, presentationData.strings.InviteLink_InviteLink.uppercased())) - entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup)) + entries.append(.privateLink(presentationData.theme, invite, importers?.importers.prefix(3).compactMap { $0.peer.peer.flatMap(EnginePeer.init) } ?? [], importers?.count ?? 0, mode != .initialSetup)) if isGroup { entries.append(.privateLinkInfo(presentationData.theme, presentationData.strings.Group_Username_CreatePrivateLinkHelp)) } else { @@ -885,6 +900,8 @@ private func channelVisibilityControllerEntries(presentationData: PresentationDa if displayAvailability { if let publicChannelsToRevoke = publicChannelsToRevoke { + entries.append(.linksLimitInfo(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesTitle, presentationData.strings.Group_Username_RemoveExistingUsernamesOrExtendInfo("\(1000)").string, 500)) + entries.append(.publicLinkAvailability(presentationData.theme, presentationData.strings.Group_Username_RemoveExistingUsernamesInfo, false)) var index: Int32 = 0 for peer in publicChannelsToRevoke.sorted(by: { lhs, rhs in @@ -1338,6 +1355,8 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta |> map { presentationData, state, view, publicChannelsToRevoke, importersContext, importers -> (ItemListControllerState, (ItemListNodeState, Any)) in let peer = peerViewMainPeer(view) + var footerItem: ItemListControllerFooterItem? + var rightNavigationButton: ItemListNavigationButton? if let peer = peer as? TelegramChannel { var doneEnabled = true @@ -1548,6 +1567,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta } var crossfade: Bool = false + var animateChanges: Bool = false if let cachedData = view.cachedData as? CachedChannelData { let invitation = cachedData.exportedInvitation let previousInvitation = previousInvitation.swap(invitation) @@ -1580,6 +1600,14 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta if selectedType == .publicChannel, let hadNamesToRevoke = hadNamesToRevoke, !crossfade { crossfade = hadNamesToRevoke != hasNamesToRevoke } + + if hasNamesToRevoke && selectedType == .publicChannel { + footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: presentationData.strings.Group_Username_IncreaseLimit, colorful: true, action: {}) + } + + if let hadNamesToRevoke = hadNamesToRevoke { + animateChanges = hadNamesToRevoke != hasNamesToRevoke + } } let title: String @@ -1601,7 +1629,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, focusItemTag: focusItemTag, crossfadeState: crossfade, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, focusItemTag: focusItemTag, footerItem: footerItem, crossfadeState: crossfade, animateChanges: animateChanges) return (controllerState, (listState, arguments)) } |> afterDisposed { diff --git a/submodules/PeerInfoUI/Sources/IncreaseLimitFooterItem.swift b/submodules/PeerInfoUI/Sources/IncreaseLimitFooterItem.swift new file mode 100644 index 0000000000..940c50071d --- /dev/null +++ b/submodules/PeerInfoUI/Sources/IncreaseLimitFooterItem.swift @@ -0,0 +1,141 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Display +import TelegramPresentationData +import ItemListUI +import PresentationDataUtils +import SolidRoundedButtonNode +import AppBundle + +final class IncreaseLimitFooterItem: ItemListControllerFooterItem { + let theme: PresentationTheme + let title: String + let colorful: Bool + let action: () -> Void + + init(theme: PresentationTheme, title: String, colorful: Bool, action: @escaping () -> Void) { + self.theme = theme + self.title = title + self.colorful = colorful + self.action = action + } + + func isEqual(to: ItemListControllerFooterItem) -> Bool { + if let item = to as? IncreaseLimitFooterItem { + return self.theme === item.theme && self.title == item.title + } else { + return false + } + } + + func node(current: ItemListControllerFooterItemNode?) -> ItemListControllerFooterItemNode { + if let current = current as? IncreaseLimitFooterItemNode { + current.item = self + return current + } else { + return IncreaseLimitFooterItemNode(item: self) + } + } +} + +final class IncreaseLimitFooterItemNode: ItemListControllerFooterItemNode { + private let backgroundNode: NavigationBackgroundNode + private let separatorNode: ASDisplayNode + private let buttonNode: SolidRoundedButtonNode + + private var validLayout: ContainerViewLayout? + + var item: IncreaseLimitFooterItem { + didSet { + self.updateItem() + if let layout = self.validLayout { + let _ = self.updateLayout(layout: layout, transition: .immediate) + } + } + } + + init(item: IncreaseLimitFooterItem) { + self.item = item + + self.backgroundNode = NavigationBackgroundNode(color: item.theme.rootController.tabBar.backgroundColor) + self.separatorNode = ASDisplayNode() + + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0) + self.buttonNode.iconPosition = .right + self.buttonNode.icon = UIImage(bundleImageName: "Premium/X2") + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.separatorNode) + self.addSubnode(self.buttonNode) + + self.updateItem() + } + + private func updateItem() { + self.backgroundNode.updateColor(color: self.item.theme.rootController.tabBar.backgroundColor, transition: .immediate) + self.separatorNode.backgroundColor = self.item.theme.rootController.tabBar.separatorColor + + let backgroundColor = self.item.theme.list.itemCheckColors.fillColor + let backgroundColors: [UIColor] + if self.item.colorful { + backgroundColors = [UIColor(rgb: 0x407af0), UIColor(rgb: 0x9551e8), UIColor(rgb: 0xbf499a), UIColor(rgb: 0xf17b30)] + self.buttonNode.icon = UIImage(bundleImageName: "Premium/X2") + } else { + backgroundColors = [] + self.buttonNode.icon = nil + } + + self.buttonNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: backgroundColor, backgroundColors: backgroundColors, foregroundColor: self.item.theme.list.itemCheckColors.foregroundColor), animated: true) + self.buttonNode.title = self.item.title + + self.buttonNode.pressed = { [weak self] in + self?.item.action() + } + } + + override func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) { + transition.updateAlpha(node: self.backgroundNode, alpha: alpha) + transition.updateAlpha(node: self.separatorNode, alpha: alpha) + } + + override func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat { + self.validLayout = layout + + let buttonInset: CGFloat = 16.0 + let buttonWidth = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - buttonInset * 2.0 + let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition) + let inset: CGFloat = 9.0 + + let insets = layout.insets(options: [.input]) + + var panelHeight: CGFloat = buttonHeight + inset * 2.0 + let totalPanelHeight: CGFloat + if let inputHeight = layout.inputHeight, inputHeight > 0.0 { + totalPanelHeight = panelHeight + insets.bottom + } else { + panelHeight += insets.bottom + totalPanelHeight = panelHeight + } + + let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - totalPanelHeight), size: CGSize(width: layout.size.width, height: panelHeight)) + transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: panelFrame.minY + inset), size: CGSize(width: buttonWidth, height: buttonHeight))) + + transition.updateFrame(node: self.backgroundNode, frame: panelFrame) + self.backgroundNode.update(size: panelFrame.size, transition: transition) + + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: panelFrame.origin, size: CGSize(width: panelFrame.width, height: UIScreenPixel))) + + return panelHeight + } + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + if self.backgroundNode.frame.contains(point) { + return true + } else { + return false + } + } +} diff --git a/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift b/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift new file mode 100644 index 0000000000..ad8c3b62d1 --- /dev/null +++ b/submodules/PeerInfoUI/Sources/IncreaseLimitHeaderItem.swift @@ -0,0 +1,201 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import TelegramPresentationData +import ItemListUI +import PresentationDataUtils +import Markdown + +class IncreaseLimitHeaderItem: ListViewItem, ItemListItem { + enum Icon { + case group + case link + } + + let theme: PresentationTheme + let icon: Icon + let count: Int + let title: String + let text: String + let sectionId: ItemListSectionId + + init(theme: PresentationTheme, icon: Icon, count: Int, title: String, text: String, sectionId: ItemListSectionId) { + self.theme = theme + self.icon = icon + self.count = count + self.title = title + self.text = text + self.sectionId = sectionId + } + + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { + async { + let node = IncreaseLimitHeaderItemNode() + let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + + node.contentSize = layout.contentSize + node.insets = layout.insets + + Queue.mainQueue().async { + completion(node, { + return (nil, { _ in apply() }) + }) + } + } + } + + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) { + Queue.mainQueue().async { + guard let nodeValue = node() as? IncreaseLimitHeaderItemNode else { + assertionFailure() + return + } + + let makeLayout = nodeValue.asyncLayout() + + async { + let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem)) + Queue.mainQueue().async { + completion(layout, { _ in + apply() + }) + } + } + } + } +} + +private let titleFont = Font.semibold(17.0) +private let textFont = Font.regular(14.0) +private let boldTextFont = Font.semibold(13.0) + +class IncreaseLimitHeaderItemNode: ListViewItemNode { + private var backgroundNode: ASImageNode + private var iconNode: ASImageNode + private var countNode: TextNode + private let titleNode: TextNode + private let textNode: TextNode + + private var item: IncreaseLimitHeaderItem? + + 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.backgroundNode = ASImageNode() + self.backgroundNode.clipsToBounds = true + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.image = generateGradientImage(size: CGSize(width: 100.0, height: 47.0), colors: [UIColor(rgb: 0xa44ece), UIColor(rgb: 0xff7924)], locations: [0.0, 1.0], direction: .horizontal) + self.backgroundNode.cornerRadius = 23.5 + + self.iconNode = ASImageNode() + self.iconNode.displaysAsynchronously = false + + self.countNode = TextNode() + self.countNode.isUserInteractionEnabled = false + self.countNode.contentMode = .left + self.countNode.contentsScale = UIScreen.main.scale + + super.init(layerBacked: false, dynamicBounce: false) + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.iconNode) + self.addSubnode(self.countNode) + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + } + + override func didLoad() { + super.didLoad() + + if #available(iOS 13.0, *) { + self.backgroundNode.layer.cornerCurve = .continuous + } + } + + func asyncLayout() -> (_ item: IncreaseLimitHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) { + let makeCountLayout = TextNode.asyncLayout(self.countNode) + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) + let makeTextLayout = TextNode.asyncLayout(self.textNode) + + return { item, params, neighbors in + let leftInset: CGFloat = 32.0 + params.leftInset + let topInset: CGFloat = 2.0 + + let badgeHeight: CGFloat = 47.0 + let titleSpacing: CGFloat = 19.0 + let textSpacing: CGFloat = 15.0 + let bottomInset: CGFloat = 2.0 + + let countAttributedText = NSAttributedString(string: "\(item.count)", font: Font.with(size: 24.0, design: .round, weight: .semibold, traits: []), textColor: .white) + let (countLayout, countApply) = makeCountLayout(TextNodeLayoutArguments(attributedString: countAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let titleAttributedText = NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor) + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let textColor = item.theme.list.freeTextColor + let attributedText = parseMarkdownIntoAttributedString(item.text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: titleFont, textColor: textColor), linkAttribute: { _ in + return nil + })) + + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) + + let contentSize = CGSize(width: params.width, height: topInset + badgeHeight + titleSpacing + titleLayout.size.height + textSpacing + textLayout.size.height + bottomInset) + let insets = itemListNeighborsGroupedInsets(neighbors, params) + + let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + + return (layout, { [weak self] in + if let strongSelf = self { + strongSelf.item = item + strongSelf.accessibilityLabel = attributedText.string + + if strongSelf.iconNode.image == nil { + let image: UIImage? + switch item.icon { + case .group: + image = UIImage(bundleImageName: "Premium/Group") + case .link: + image = UIImage(bundleImageName: "Premium/Link") + } + strongSelf.iconNode.image = generateTintedImage(image: image, color: .white) + } + + let countBackgroundWidth: CGFloat = countLayout.size.width + 67.0 + let countBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - countBackgroundWidth) / 2.0), y: topInset), size: CGSize(width: countBackgroundWidth, height: badgeHeight)) + strongSelf.backgroundNode.frame = countBackgroundFrame + + let _ = countApply() + strongSelf.countNode.frame = CGRect(origin: CGPoint(x: countBackgroundFrame.maxX - countLayout.size.width - 15.0, y: countBackgroundFrame.minY + floorToScreenPixels((countBackgroundFrame.height - countLayout.size.height) / 2.0)), size: countLayout.size) + + if let image = strongSelf.iconNode.image { + strongSelf.iconNode.frame = CGRect(origin: CGPoint(x: countBackgroundFrame.minX + 18.0, y: countBackgroundFrame.minY + floorToScreenPixels((countBackgroundFrame.height - image.size.height) / 2.0)), size: image.size) + } + + let _ = titleApply() + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - titleLayout.size.width) / 2.0), y: countBackgroundFrame.maxY + titleSpacing), size: titleLayout.size) + + let _ = textApply() + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - textLayout.size.width) / 2.0), y: countBackgroundFrame.maxY + titleSpacing + titleLayout.size.height + textSpacing), size: textLayout.size) + } + }) + } + } + + 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) { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false) + } +} diff --git a/submodules/PeerInfoUI/Sources/OldChannelsController.swift b/submodules/PeerInfoUI/Sources/OldChannelsController.swift index f349c25671..1a0e33d27f 100644 --- a/submodules/PeerInfoUI/Sources/OldChannelsController.swift +++ b/submodules/PeerInfoUI/Sources/OldChannelsController.swift @@ -47,9 +47,9 @@ func localizedOldChannelDate(peer: InactiveChannel, strings: PresentationStrings if let channel = peer.peer as? TelegramChannel, case .group = channel.info { if let participantsCount = peer.participantsCount, participantsCount != 0 { - string = strings.OldChannels_GroupFormat(participantsCount) + string + string = strings.OldChannels_GroupFormat(participantsCount) + ", " + string } else { - string = strings.OldChannels_GroupEmptyFormat + string + string = strings.OldChannels_GroupEmptyFormat + ", " + string } } else { string = strings.OldChannels_ChannelFormat + string @@ -83,7 +83,7 @@ private enum OldChannelsEntryId: Hashable { } private enum OldChannelsEntry: ItemListNodeEntry { - case info(String, String) + case info(Int, String, String) case peersHeader(String) case peer(Int, InactiveChannel, Bool) @@ -109,8 +109,8 @@ private enum OldChannelsEntry: ItemListNodeEntry { static func ==(lhs: OldChannelsEntry, rhs: OldChannelsEntry) -> Bool { switch lhs { - case let .info(title, text): - if case .info(title, text) = rhs { + case let .info(count, title, text): + if case .info(count, title, text) = rhs { return true } else { return false @@ -167,8 +167,8 @@ private enum OldChannelsEntry: ItemListNodeEntry { func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { let arguments = arguments as! OldChannelsItemArguments switch self { - case let .info(title, text): - return ItemListInfoItem(presentationData: presentationData, title: title, text: .plain(text), style: .blocks, sectionId: self.section, closeAction: nil) + case let .info(count, title, text): + return IncreaseLimitHeaderItem(theme: presentationData.theme, icon: .group, count: count, title: title, text: text, sectionId: self.section) case let .peersHeader(title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .peer(_, peer, selected): @@ -184,19 +184,10 @@ private struct OldChannelsState: Equatable { var isSearching: Bool = false } -private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] { +private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, limit: Int, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] { var entries: [OldChannelsEntry] = [] - - let noticeText: String - switch intent { - case .join: - noticeText = presentationData.strings.OldChannels_NoticeText - case .create: - noticeText = presentationData.strings.OldChannels_NoticeCreateText - case .upgrade: - noticeText = presentationData.strings.OldChannels_NoticeUpgradeText - } - entries.append(.info(presentationData.strings.OldChannels_NoticeTitle, noticeText)) + + entries.append(.info(limit, presentationData.strings.OldChannels_TooManyCommunitiesTitle, presentationData.strings.OldChannels_TooManyCommunitiesText("\(limit)", "\(limit * 2)").string)) if let peers = peers, !peers.isEmpty { entries.append(.peersHeader(presentationData.strings.OldChannels_ChannelsHeader)) @@ -209,127 +200,6 @@ private func oldChannelsEntries(presentationData: PresentationData, state: OldCh return entries } -private final class OldChannelsActionPanelNode: ASDisplayNode { - private let separatorNode: ASDisplayNode - let buttonNode: SolidRoundedButtonNode - - init(presentationData: ItemListPresentationData, leaveAction: @escaping () -> Void) { - self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor - self.buttonNode = SolidRoundedButtonNode(title: "", icon: nil, theme: SolidRoundedButtonTheme(theme: presentationData.theme), height: 50.0, cornerRadius: 11.0, gloss: false) - - super.init() - - self.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor - - self.addSubnode(self.separatorNode) - self.addSubnode(self.buttonNode) - - self.buttonNode.pressed = { - leaveAction() - } - } - - func updatePresentationData(_ presentationData: ItemListPresentationData) { - self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor - self.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor - } - - func updateLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat { - let sideInset: CGFloat = 16.0 - let verticalInset: CGFloat = 16.0 - let buttonHeight: CGFloat = 50.0 - - let insets = layout.insets(options: [.input]) - - transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: UIScreenPixel))) - - let _ = self.buttonNode.updateLayout(width: layout.size.width - sideInset * 2.0, transition: transition) - transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalInset), size: CGSize(width: layout.size.width, height: buttonHeight))) - - return buttonHeight + verticalInset * 2.0 + insets.bottom - } -} - -private final class OldChannelsControllerImpl: ItemListController { - private let panelNode: OldChannelsActionPanelNode - - private var displayPanel: Bool = false - private var validLayout: ContainerViewLayout? - - private var presentationData: ItemListPresentationData - private var presentationDataDisposable: Disposable? - - var leaveAction: (() -> Void)? - - override init(presentationData: ItemListPresentationData, updatedPresentationData: Signal, state: Signal<(ItemListControllerState, (ItemListNodeState, ItemGenerationArguments)), NoError>, tabBarItem: Signal?) { - self.presentationData = presentationData - - var leaveActionImpl: (() -> Void)? - self.panelNode = OldChannelsActionPanelNode(presentationData: presentationData, leaveAction: { - leaveActionImpl?() - }) - - super.init(presentationData: presentationData, updatedPresentationData: updatedPresentationData, state: state, tabBarItem: tabBarItem) - - self.presentationDataDisposable = (updatedPresentationData - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - guard let strongSelf = self else { - return - } - strongSelf.presentationData = presentationData - strongSelf.panelNode.updatePresentationData(presentationData) - }) - - leaveActionImpl = { [weak self] in - self?.leaveAction?() - } - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDataDisposable?.dispose() - } - - override var navigationBarRequiresEntireLayoutUpdate: Bool { - return false - } - - override func loadDisplayNode() { - super.loadDisplayNode() - - self.displayNode.addSubnode(self.panelNode) - } - - override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.validLayout = layout - - let panelHeight = self.panelNode.updateLayout(layout, transition: transition) - - var additionalInsets = UIEdgeInsets() - additionalInsets.bottom = max(layout.intrinsicInsets.bottom, panelHeight) - - self.additionalInsets = additionalInsets - - super.containerLayoutUpdated(layout, transition: transition) - - transition.updateFrame(node: self.panelNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.displayPanel ? (layout.size.height - panelHeight) : layout.size.height), size: CGSize(width: layout.size.width, height: panelHeight)), beginWithCurrentState: true) - } - - func updatePanelPeerCount(_ value: Int) { - self.panelNode.buttonNode.title = self.presentationData.strings.OldChannels_Leave(Int32(value)) - - if self.displayPanel != (value != 0) { - self.displayPanel = (value != 0) - if let layout = self.validLayout { - self.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring)) - } - } - } -} public enum OldChannelsControllerIntent { case join @@ -344,20 +214,19 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa let updateState: ((OldChannelsState) -> OldChannelsState) -> Void = { f in statePromise.set(stateValue.modify { f($0) }) } - - var updateSelectedPeersImpl: ((Int) -> Void)? - + var dismissImpl: (() -> Void)? var setDisplayNavigationBarImpl: ((Bool) -> Void)? var ensurePeerVisibleImpl: ((PeerId) -> Void)? + var leaveActionImpl: (() -> Void)? + let actionsDisposable = DisposableSet() let arguments = OldChannelsItemArguments( context: context, togglePeer: { peerId, ensureVisible in - var selectedPeerCount = 0 var didSelect = false updateState { state in var state = state @@ -367,10 +236,8 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa state.selectedPeers.insert(peerId) didSelect = true } - selectedPeerCount = state.selectedPeers.count return state } - updateSelectedPeersImpl?(selectedPeerCount) if didSelect && ensureVisible { ensurePeerVisibleImpl?(peerId) } @@ -436,7 +303,20 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme) } - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, peers: peers, intent: intent), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up), crossfadeState: peersAreEmptyUpdated, animateChanges: false) + let buttonText: String + let colorful: Bool + if state.selectedPeers.count > 0 { + buttonText = presentationData.strings.OldChannels_LeaveCommunities(Int32(state.selectedPeers.count)) + colorful = false + } else { + buttonText = presentationData.strings.OldChannels_IncreaseLimit + colorful = true + } + let footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: buttonText, colorful: colorful, action: { + leaveActionImpl?() + }) + + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: oldChannelsEntries(presentationData: presentationData, state: state, limit: 500, peers: peers, intent: intent), style: .blocks, emptyStateItem: emptyStateItem, searchItem: searchItem, footerItem: footerItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up), crossfadeState: peersAreEmptyUpdated, animateChanges: false) return (controllerState, (listState, arguments)) } @@ -444,14 +324,10 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa actionsDisposable.dispose() } - let controller = OldChannelsControllerImpl(context: context, state: signal) + let controller = ItemListController(context: context, state: signal) controller.navigationPresentation = .modal - updateSelectedPeersImpl = { [weak controller] value in - controller?.updatePanelPeerCount(value) - } - - controller.leaveAction = { + leaveActionImpl = { let state = stateValue.with { $0 } let _ = (peersPromise.get() |> take(1) diff --git a/submodules/PremiumUI/BUILD b/submodules/PremiumUI/BUILD index 6748e54348..dafcf76cd0 100644 --- a/submodules/PremiumUI/BUILD +++ b/submodules/PremiumUI/BUILD @@ -25,6 +25,11 @@ swift_library( "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", "//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/ReactionSelectionNode:ReactionSelectionNode", + "//submodules/ComponentFlow:ComponentFlow", + "//submodules/Components/ViewControllerComponent:ViewControllerComponent", + "//submodules/Components/MultilineTextComponent:MultilineTextComponent", + "//submodules/Components/BundleIconComponent:BundleIconComponent", + "//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/PremiumUI/Sources/LimitScreen.swift b/submodules/PremiumUI/Sources/LimitScreen.swift new file mode 100644 index 0000000000..c21329c43e --- /dev/null +++ b/submodules/PremiumUI/Sources/LimitScreen.swift @@ -0,0 +1,754 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SwiftSignalKit +import AccountContext +import TelegramPresentationData +import PresentationDataUtils +import ComponentFlow +import ViewControllerComponent +import MultilineTextComponent +import BundleIconComponent +import SolidRoundedButtonComponent +import Markdown + +private final class LimitScreenComponent: CombinedComponent { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let subject: LimitScreen.Subject + let proceed: () -> Void + + init(context: AccountContext, subject: LimitScreen.Subject, proceed: @escaping () -> Void) { + self.context = context + self.subject = subject + self.proceed = proceed + } + + static func ==(lhs: LimitScreenComponent, rhs: LimitScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.subject != rhs.subject { + return false + } + return true + } + + final class State: ComponentState { + private let context: AccountContext + + init(context: AccountContext) { + self.context = context + + super.init() + } + } + + func makeState() -> State { + return State(context: self.context) + } + + static var body: Body { + let icon = Child(BundleIconComponent.self) + let title = Child(MultilineTextComponent.self) + let text = Child(MultilineTextComponent.self) + + let button = Child(SolidRoundedButtonComponent.self) + let cancel = Child(Button.self) + + return { context in + let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + let component = context.component + let theme = environment.theme + let strings = environment.strings + + let topInset: CGFloat = 34.0 + 38.0 + let sideInset: CGFloat = 16.0 + environment.safeInsets.left + let textSideInset: CGFloat = 24.0 + environment.safeInsets.left + + let icon = icon.update( + component: BundleIconComponent( + name: "Premium/Tmp", + tintColor: nil + ), + availableSize: CGSize(width: context.availableSize.width, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + + let title = title.update( + component: MultilineTextComponent( + text: NSAttributedString(string: "Limit Reached", font: Font.semibold(17.0), textColor: theme.actionSheet.primaryTextColor, paragraphAlignment: .center), + horizontalAlignment: .center, + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude), + transition: .immediate + ) + + let textFont = Font.regular(15.0) + let boldTextFont = Font.semibold(15.0) + + let textColor = theme.actionSheet.secondaryTextColor + let string: String + switch component.subject { + case .chatsInFolder: + string = "" + case .folders: + string = "" + case .pins: + string = strings.DialogList_ExtendedPinLimitError("\(5)", "\(10)").string + } + let attributedText = parseMarkdownIntoAttributedString(string, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: textFont, textColor: textColor), linkAttribute: { _ in + return nil + })) + + let text = text.update( + component: MultilineTextComponent( + text: attributedText, + horizontalAlignment: .center, + maximumNumberOfLines: 0, + lineSpacing: 0.1 + ), + availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height), + transition: .immediate + ) + + let button = button.update( + component: SolidRoundedButtonComponent( + title: "Increase Limit", + theme: SolidRoundedButtonComponent.Theme( + backgroundColor: .black, + backgroundColors: [UIColor(rgb: 0x407af0), UIColor(rgb: 0x9551e8), UIColor(rgb: 0xbf499a), UIColor(rgb: 0xf17b30)], + foregroundColor: .white + ), + font: .bold, + fontSize: 17.0, + height: 50.0, + cornerRadius: 10.0, + gloss: false, + iconName: "Premium/X2", + iconPosition: .right, + action: { [weak component] in + guard let component = component else { + return + } + component.proceed() + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0), + transition: context.transition + ) + + let cancel = cancel.update(component: Button( + content: AnyComponent(Text(text: strings.Common_Cancel, font: Font.regular(17.0), color: theme.actionSheet.controlAccentColor)), + action: { + + } + ), + availableSize: context.availableSize, + transition: context.transition) + + let width = context.availableSize.width + + context.add(icon + .position(CGPoint(x: width / 2.0, y: 57.0)) + ) + + context.add(title + .position(CGPoint(x: width / 2.0, y: topInset + 39.0)) + ) + context.add(text + .position(CGPoint(x: width / 2.0, y: topInset + 101.0)) + ) + + let buttonFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + 76.0 + text.size.height + 27.0), size: button.size) + context.add(button + .position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY)) + ) + + context.add(cancel + .position(CGPoint(x: width / 2.0, y: topInset + 76.0 + text.size.height + 20.0 + button.size.height + 40.0)) + ) + + let contentSize = CGSize(width: context.availableSize.width, height: topInset + title.size.height + text.size.height) + + return contentSize + } + } +} + +public class LimitScreen: ViewController { + final class Node: ViewControllerTracingNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { + private var presentationData: PresentationData + private weak var controller: LimitScreen? + + private let component: AnyComponent + private let theme: PresentationTheme? + + let dim: ASDisplayNode + let wrappingView: UIView + let containerView: UIView + let scrollView: UIScrollView + let hostView: ComponentHostView + + private(set) var isExpanded = false + private var panGestureRecognizer: UIPanGestureRecognizer? + private var panGestureArguments: (topInset: CGFloat, offset: CGFloat, scrollView: UIScrollView?, listNode: ListView?)? + + private var currentIsVisible: Bool = false + private var currentLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? + + fileprivate var temporaryDismiss = false + + init(context: AccountContext, controller: LimitScreen, component: AnyComponent, theme: PresentationTheme?) { + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + + self.controller = controller + + self.component = component + self.theme = theme + + self.dim = ASDisplayNode() + self.dim.alpha = 0.0 + self.dim.backgroundColor = UIColor(white: 0.0, alpha: 0.25) + + self.wrappingView = UIView() + self.containerView = UIView() + self.scrollView = UIScrollView() + self.hostView = ComponentHostView() + + super.init() + + self.scrollView.delegate = self + self.scrollView.showsVerticalScrollIndicator = false + + self.containerView.clipsToBounds = true + self.containerView.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor + + self.addSubnode(self.dim) + + self.view.addSubview(self.wrappingView) + self.wrappingView.addSubview(self.containerView) + self.containerView.addSubview(self.scrollView) + self.scrollView.addSubview(self.hostView) + } + + override func didLoad() { + super.didLoad() + + let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + panRecognizer.delegate = self + panRecognizer.delaysTouchesBegan = false + panRecognizer.cancelsTouchesInView = true + self.panGestureRecognizer = panRecognizer + self.wrappingView.addGestureRecognizer(panRecognizer) + + self.dim.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) + + self.controller?.navigationBar?.updateBackgroundAlpha(0.0, transition: .immediate) + } + + @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.controller?.dismiss(animated: true) + } + } + + override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if let (layout, _) = self.currentLayout { + if case .regular = layout.metrics.widthClass { + return false + } + } + return true + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let contentOffset = self.scrollView.contentOffset.y + self.controller?.navigationBar?.updateBackgroundAlpha(min(30.0, contentOffset) / 30.0, transition: .immediate) + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer { + return true + } + return false + } + + private var isDismissing = false + func animateIn() { + ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) + + let targetPosition = self.containerView.center + let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height) + + self.containerView.center = startPosition + let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) + transition.animateView(allowUserInteraction: true, { + self.containerView.center = targetPosition + }, completion: { _ in + }) + } + + func animateOut(completion: @escaping () -> Void = {}) { + self.isDismissing = true + + let positionTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) + positionTransition.updatePosition(layer: self.containerView.layer, position: CGPoint(x: self.containerView.center.x, y: self.bounds.height + self.containerView.bounds.height / 2.0), completion: { [weak self] _ in + self?.controller?.dismiss(animated: false, completion: completion) + }) + let alphaTransition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .easeInOut) + alphaTransition.updateAlpha(node: self.dim, alpha: 0.0) + + if !self.temporaryDismiss { + self.controller?.updateModalStyleOverlayTransitionFactor(0.0, transition: positionTransition) + } + } + + func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: Transition) { + self.currentLayout = (layout, navigationHeight) + + if let controller = self.controller, let navigationBar = controller.navigationBar, navigationBar.view.superview !== self.wrappingView { + self.containerView.addSubview(navigationBar.view) + } + + self.dim.frame = CGRect(origin: CGPoint(x: 0.0, y: -layout.size.height), size: CGSize(width: layout.size.width, height: layout.size.height * 3.0)) + + var effectiveExpanded = self.isExpanded + if case .regular = layout.metrics.widthClass { + effectiveExpanded = true + } + + let isLandscape = layout.orientation == .landscape + let edgeTopInset = isLandscape ? 0.0 : self.defaultTopInset + let topInset: CGFloat + if let (panInitialTopInset, panOffset, _, _) = self.panGestureArguments { + if effectiveExpanded { + topInset = min(edgeTopInset, panInitialTopInset + max(0.0, panOffset)) + } else { + topInset = max(0.0, panInitialTopInset + min(0.0, panOffset)) + } + } else { + topInset = effectiveExpanded ? 0.0 : edgeTopInset + } + transition.setFrame(view: self.wrappingView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset), size: layout.size), completion: nil) + + let modalProgress = isLandscape ? 0.0 : (1.0 - topInset / self.defaultTopInset) + self.controller?.updateModalStyleOverlayTransitionFactor(modalProgress, transition: transition.containedViewLayoutTransition) + + let clipFrame: CGRect + if layout.metrics.widthClass == .compact { + self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.25) + if isLandscape { + self.containerView.layer.cornerRadius = 0.0 + } else { + self.containerView.layer.cornerRadius = 10.0 + } + + if #available(iOS 11.0, *) { + if layout.safeInsets.bottom.isZero { + self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] + } else { + self.containerView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] + } + } + + if isLandscape { + clipFrame = CGRect(origin: CGPoint(), size: layout.size) + } else { + let coveredByModalTransition: CGFloat = 0.0 + var containerTopInset: CGFloat = 10.0 + if let statusBarHeight = layout.statusBarHeight { + containerTopInset += statusBarHeight + } + + let unscaledFrame = CGRect(origin: CGPoint(x: 0.0, y: containerTopInset - coveredByModalTransition * 10.0), size: CGSize(width: layout.size.width, height: layout.size.height - containerTopInset)) + let maxScale: CGFloat = (layout.size.width - 16.0 * 2.0) / layout.size.width + let containerScale = 1.0 * (1.0 - coveredByModalTransition) + maxScale * coveredByModalTransition + let maxScaledTopInset: CGFloat = containerTopInset - 10.0 + let scaledTopInset: CGFloat = containerTopInset * (1.0 - coveredByModalTransition) + maxScaledTopInset * coveredByModalTransition + let containerFrame = unscaledFrame.offsetBy(dx: 0.0, dy: scaledTopInset - (unscaledFrame.midY - containerScale * unscaledFrame.height / 2.0)) + + clipFrame = CGRect(x: containerFrame.minX, y: containerFrame.minY, width: containerFrame.width, height: containerFrame.height) + } + } else { + self.dim.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4) + self.containerView.layer.cornerRadius = 10.0 + + let verticalInset: CGFloat = 44.0 + + let maxSide = max(layout.size.width, layout.size.height) + let minSide = min(layout.size.width, layout.size.height) + let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0) + clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) + } + + transition.setFrame(view: self.containerView, frame: clipFrame) + transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: clipFrame.size), completion: nil) + + let environment = ViewControllerComponentContainer.Environment( + statusBarHeight: 0.0, + navigationHeight: navigationHeight, + safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), + isVisible: self.currentIsVisible, + theme: self.theme ?? self.presentationData.theme, + strings: self.presentationData.strings, + controller: { [weak self] in + return self?.controller + } + ) + var contentSize = self.hostView.update( + transition: transition, + component: self.component, + environment: { + environment + }, + forceUpdate: true, + containerSize: CGSize(width: clipFrame.size.width, height: 10000.0) + ) + contentSize.height = max(layout.size.height - navigationHeight, contentSize.height) + transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: contentSize), completion: nil) + + self.scrollView.contentSize = contentSize + } + + private var didPlayAppearAnimation = false + func updateIsVisible(isVisible: Bool) { + if self.currentIsVisible == isVisible { + return + } + self.currentIsVisible = isVisible + + guard let currentLayout = self.currentLayout else { + return + } + self.containerLayoutUpdated(layout: currentLayout.layout, navigationHeight: currentLayout.navigationHeight, transition: .immediate) + + if !self.didPlayAppearAnimation { + self.didPlayAppearAnimation = true + self.animateIn() + } + } + + private var defaultTopInset: CGFloat { + return 390.0 +// guard let (layout, _) = self.currentLayout else{ +// return 210.0 +// } +// if case .compact = layout.metrics.widthClass { +// var factor: CGFloat = 0.2488 +// if layout.size.width <= 320.0 { +// factor = 0.15 +// } +// return floor(max(layout.size.width, layout.size.height) * factor) +// } else { +// return 210.0 +// } + } + + private func findScrollView(view: UIView?) -> (UIScrollView, ListView?)? { + if let view = view { + if let view = view as? UIScrollView { + return (view, nil) + } + if let node = view.asyncdisplaykit_node as? ListView { + return (node.scroller, node) + } + return findScrollView(view: view.superview) + } else { + return nil + } + } + + @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { + guard let (layout, navigationHeight) = self.currentLayout else { + return + } + + let isLandscape = layout.orientation == .landscape + let edgeTopInset = isLandscape ? 0.0 : defaultTopInset + + switch recognizer.state { + case .began: + let point = recognizer.location(in: self.view) + let currentHitView = self.hitTest(point, with: nil) + + var scrollViewAndListNode = self.findScrollView(view: currentHitView) + if scrollViewAndListNode?.0.frame.height == self.frame.width { + scrollViewAndListNode = nil + } + let scrollView = scrollViewAndListNode?.0 + let listNode = scrollViewAndListNode?.1 + + let topInset: CGFloat + if self.isExpanded { + topInset = 0.0 + } else { + topInset = edgeTopInset + } + + self.panGestureArguments = (topInset, 0.0, scrollView, listNode) + case .changed: + guard let (topInset, panOffset, scrollView, listNode) = self.panGestureArguments else { + return + } + let visibleContentOffset = listNode?.visibleContentOffset() + let contentOffset = scrollView?.contentOffset.y ?? 0.0 + + var translation = recognizer.translation(in: self.view).y + + var currentOffset = topInset + translation + + let epsilon = 1.0 + if case let .known(value) = visibleContentOffset, value <= epsilon { + if let scrollView = scrollView { + scrollView.bounces = false + scrollView.setContentOffset(CGPoint(x: 0.0, y: 0.0), animated: false) + } + } else if let scrollView = scrollView, contentOffset <= -scrollView.contentInset.top + epsilon { + scrollView.bounces = false + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) + } else if let scrollView = scrollView { + translation = panOffset + currentOffset = topInset + translation + if self.isExpanded { + recognizer.setTranslation(CGPoint(), in: self.view) + } else if currentOffset > 0.0 { + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) + } + } + + self.panGestureArguments = (topInset, translation, scrollView, listNode) + + if !self.isExpanded { + if currentOffset > 0.0, let scrollView = scrollView { + scrollView.panGestureRecognizer.setTranslation(CGPoint(), in: scrollView) + } + } + + var bounds = self.bounds + if self.isExpanded { + bounds.origin.y = -max(0.0, translation - edgeTopInset) + } else { + bounds.origin.y = -translation + } + bounds.origin.y = min(0.0, bounds.origin.y) + self.bounds = bounds + + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate) + case .ended: + guard let (currentTopInset, panOffset, scrollView, listNode) = self.panGestureArguments else { + return + } + self.panGestureArguments = nil + + let visibleContentOffset = listNode?.visibleContentOffset() + let contentOffset = scrollView?.contentOffset.y ?? 0.0 + + let translation = recognizer.translation(in: self.view).y + var velocity = recognizer.velocity(in: self.view) + + if self.isExpanded { + if case let .known(value) = visibleContentOffset, value > 0.1 { + velocity = CGPoint() + } else if case .unknown = visibleContentOffset { + velocity = CGPoint() + } else if contentOffset > 0.1 { + velocity = CGPoint() + } + } + + var bounds = self.bounds + if self.isExpanded { + bounds.origin.y = -max(0.0, translation - edgeTopInset) + } else { + bounds.origin.y = -translation + } + bounds.origin.y = min(0.0, bounds.origin.y) + + scrollView?.bounces = true + + let offset = currentTopInset + panOffset + let topInset: CGFloat = edgeTopInset + + var dismissing = false + if bounds.minY < -60 || (bounds.minY < 0.0 && velocity.y > 300.0) || (self.isExpanded && bounds.minY.isZero && velocity.y > 1800.0) { + self.controller?.dismiss(animated: true, completion: nil) + dismissing = true + } else if self.isExpanded { + if velocity.y > 300.0 || offset > topInset / 2.0 { + self.isExpanded = false + if let listNode = listNode { + listNode.scroller.setContentOffset(CGPoint(), animated: false) + } else if let scrollView = scrollView { + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) + } + + let distance = topInset - offset + let initialVelocity: CGFloat = distance.isZero ? 0.0 : abs(velocity.y / distance) + let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) + + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) + } else { + self.isExpanded = true + + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) + } + } else if (velocity.y < -300.0 || offset < topInset / 2.0) { + if velocity.y > -2200.0 && velocity.y < -300.0, let listNode = listNode { + DispatchQueue.main.async { + listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + } + + let initialVelocity: CGFloat = offset.isZero ? 0.0 : abs(velocity.y / offset) + let transition = ContainedViewLayoutTransition.animated(duration: 0.45, curve: .customSpring(damping: 124.0, initialVelocity: initialVelocity)) + self.isExpanded = true + + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) + } else { + if let listNode = listNode { + listNode.scroller.setContentOffset(CGPoint(), animated: false) + } else if let scrollView = scrollView { + scrollView.setContentOffset(CGPoint(x: 0.0, y: -scrollView.contentInset.top), animated: false) + } + + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) + } + + if !dismissing { + var bounds = self.bounds + let previousBounds = bounds + bounds.origin.y = 0.0 + self.bounds = bounds + self.layer.animateBounds(from: previousBounds, to: self.bounds, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue) + } + case .cancelled: + self.panGestureArguments = nil + + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(.animated(duration: 0.3, curve: .easeInOut))) + default: + break + } + } + + func update(isExpanded: Bool, transition: ContainedViewLayoutTransition) { + guard isExpanded != self.isExpanded else { + return + } + self.isExpanded = isExpanded + + guard let (layout, navigationHeight) = self.currentLayout else { + return + } + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) + } + } + + var node: Node { + return self.displayNode as! Node + } + + private let context: AccountContext + private let theme: PresentationTheme? + private let component: AnyComponent + private var isInitiallyExpanded = false + + private var currentLayout: ContainerViewLayout? + + public var pushController: (ViewController) -> Void = { _ in } + public var presentController: (ViewController) -> Void = { _ in } + + public enum Subject { + case folders + case chatsInFolder + case pins + } + + public convenience init(context: AccountContext, subject: Subject) { + self.init(context: context, component: LimitScreenComponent(context: context, subject: subject, proceed: {})) + } + + private init(context: AccountContext, component: C, theme: PresentationTheme? = nil) where C.EnvironmentType == ViewControllerComponentContainer.Environment { + self.context = context + self.component = AnyComponent(component) + self.theme = nil + + super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: context.sharedContext.currentPresentationData.with { $0 })) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func cancelPressed() { + self.dismiss(animated: true, completion: nil) + } + + override open func loadDisplayNode() { + self.displayNode = Node(context: self.context, controller: self, component: self.component, theme: self.theme) + self.displayNodeDidLoad() + } + + public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + self.view.endEditing(true) + if flag { + self.node.animateOut(completion: { + super.dismiss(animated: false, completion: {}) + completion?() + }) + } else { + super.dismiss(animated: false, completion: {}) + completion?() + } + } + + override open func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.node.updateIsVisible(isVisible: true) + } + + override open func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + self.node.updateIsVisible(isVisible: false) + } + + override public func updateNavigationBarLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + var navigationLayout = self.navigationLayout(layout: layout) + var navigationFrame = navigationLayout.navigationFrame + + var layout = layout + if case .regular = layout.metrics.widthClass { + let verticalInset: CGFloat = 44.0 + let maxSide = max(layout.size.width, layout.size.height) + let minSide = min(layout.size.width, layout.size.height) + let containerSize = CGSize(width: min(layout.size.width - 20.0, floor(maxSide / 2.0)), height: min(layout.size.height, minSide) - verticalInset * 2.0) + let clipFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: floor((layout.size.height - containerSize.height) / 2.0)), size: containerSize) + navigationFrame.size.width = clipFrame.width + layout.size = clipFrame.size + } + + navigationFrame.size.height = 56.0 + navigationLayout.navigationFrame = navigationFrame + navigationLayout.defaultContentHeight = 56.0 + + layout.statusBarHeight = nil + + self.applyNavigationBarLayout(layout, navigationLayout: navigationLayout, additionalBackgroundHeight: 0.0, transition: transition) + } + + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.currentLayout = layout + super.containerLayoutUpdated(layout, transition: transition) + + let navigationHeight: CGFloat = 56.0 + + self.node.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: Transition(transition)) + } +} diff --git a/submodules/PremiumUI/Sources/ReactionCarouselNode.swift b/submodules/PremiumUI/Sources/ReactionCarouselNode.swift index 97de4839b5..cfec7560c9 100644 --- a/submodules/PremiumUI/Sources/ReactionCarouselNode.swift +++ b/submodules/PremiumUI/Sources/ReactionCarouselNode.swift @@ -14,6 +14,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { private let context: AccountContext private let theme: PresentationTheme private let reactions: [AvailableReactions.Reaction] + private var itemContainerNodes: [ASDisplayNode] = [] private var itemNodes: [ReactionNode] = [] private let scrollNode: ASScrollNode private let tapNode: ASDisplayNode @@ -21,9 +22,14 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { private var standaloneReactionAnimation: StandaloneReactionAnimation? private var animator: DisplayLinkAnimator? private var currentPosition: CGFloat = 0.0 + private var currentIndex: Int = 0 private var validLayout: CGSize? + private var playingIndices = Set() + + private let positionDelta: Double + init(context: AccountContext, theme: PresentationTheme, reactions: [AvailableReactions.Reaction]) { self.context = context self.theme = theme @@ -32,6 +38,8 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { self.scrollNode = ASScrollNode() self.tapNode = ASDisplayNode() + self.positionDelta = 1.0 / CGFloat(self.reactions.count) + super.init() self.addSubnode(self.scrollNode) @@ -51,12 +59,12 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { } @objc private func reactionTapped(_ gestureRecognizer: UITapGestureRecognizer) { - guard self.animator == nil, self.standaloneReactionAnimation == nil, self.scrollStartPosition == nil else { + guard self.animator == nil, self.scrollStartPosition == nil else { return } let point = gestureRecognizer.location(in: self.view) - guard let index = self.itemNodes.firstIndex(where: { $0.frame.contains(point) }) else { + guard let index = self.itemContainerNodes.firstIndex(where: { $0.frame.contains(point) }) else { return } @@ -77,7 +85,8 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { guard index >= 0 && index < self.itemNodes.count else { return } - let delta = 1.0 / CGFloat(self.itemNodes.count) + self.currentIndex = index + let delta = self.positionDelta let startPosition = self.currentPosition let newPosition = delta * CGFloat(index) @@ -129,6 +138,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { guard let aroundAnimation = reaction.aroundAnimation else { continue } + let containerNode = ASDisplayNode() let itemNode = ReactionNode(context: self.context, theme: self.theme, item: ReactionItem( reaction: ReactionItem.Reaction(rawValue: reaction.value), appearAnimation: reaction.appearAnimation, @@ -138,9 +148,11 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { applicationAnimation: aroundAnimation, largeApplicationAnimation: reaction.effectAnimation ), hasAppearAnimation: false) - itemNode.isUserInteractionEnabled = false - self.addSubnode(itemNode) + containerNode.isUserInteractionEnabled = false + containerNode.addSubnode(itemNode) + self.addSubnode(containerNode) + self.itemContainerNodes.append(containerNode) self.itemNodes.append(itemNode) } } @@ -154,22 +166,17 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { } func playReaction() { - guard self.standaloneReactionAnimation == nil else { - return - } - - let delta = 1.0 / CGFloat(self.itemNodes.count) + let delta = self.positionDelta let index = max(0, min(self.itemNodes.count - 1, Int(round(self.currentPosition / delta)))) - let reaction = self.reactions[index] - let targetView = self.itemNodes[index].view - - let standaloneReactionAnimation = StandaloneReactionAnimation() - self.standaloneReactionAnimation = standaloneReactionAnimation - guard let supernode = self.supernode else { + guard !self.playingIndices.contains(index) else { return } + let reaction = self.reactions[index] + let targetContainerNode = self.itemContainerNodes[index] + let targetView = self.itemNodes[index].view + guard let centerAnimation = reaction.centerAnimation else { return } @@ -177,10 +184,15 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { return } - self.scrollNode.view.isScrollEnabled = false + self.playingIndices.insert(index) - supernode.addSubnode(standaloneReactionAnimation) - standaloneReactionAnimation.frame = supernode.bounds + targetContainerNode.view.superview?.bringSubviewToFront(targetContainerNode.view) + + let standaloneReactionAnimation = StandaloneReactionAnimation() + self.standaloneReactionAnimation = standaloneReactionAnimation + + targetContainerNode.addSubnode(standaloneReactionAnimation) + standaloneReactionAnimation.frame = targetContainerNode.bounds standaloneReactionAnimation.animateReactionSelection( context: self.context, theme: self.theme, reaction: ReactionItem( reaction: ReactionItem.Reaction(rawValue: reaction.value), @@ -200,7 +212,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { completion: { [weak standaloneReactionAnimation, weak self] in standaloneReactionAnimation?.removeFromSupernode() self?.standaloneReactionAnimation = nil - self?.scrollNode.view.isScrollEnabled = true + self?.playingIndices.remove(index) } ) } @@ -211,7 +223,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { self.scrollStartPosition = (scrollView.contentOffset.x, self.currentPosition) } } - + func scrollViewDidScroll(_ scrollView: UIScrollView) { guard !self.ignoreContentOffsetChange, let (startContentOffset, startPosition) = self.scrollStartPosition else { return @@ -227,6 +239,14 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { updatedPosition += 1.0 } self.currentPosition = updatedPosition + + let indexDelta = self.positionDelta + let index = max(0, min(self.itemNodes.count - 1, Int(round(self.currentPosition / indexDelta)))) + if index != self.currentIndex { + self.currentIndex = index + print(index) + } + if let size = self.validLayout { self.updateLayout(size: size, transition: .immediate) } @@ -237,7 +257,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { return } - let delta = 1.0 / CGFloat(self.itemNodes.count) + let delta = self.positionDelta let scrollDelta = targetContentOffset.pointee.x - startContentOffset let positionDelta = scrollDelta * -0.001 let positionCounts = round(positionDelta / delta) @@ -251,7 +271,7 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { if !decelerate { self.resetScrollPosition() - let delta = 1.0 / CGFloat(self.itemNodes.count) + let delta = self.positionDelta let index = max(0, min(self.itemNodes.count - 1, Int(round(self.currentPosition / delta)))) self.scrollTo(index, playReaction: true, duration: 0.2) } @@ -272,12 +292,14 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { self.resetScrollPosition() } - let delta = 1.0 / CGFloat(self.itemNodes.count) + let delta = self.positionDelta let areaSize = CGSize(width: floor(size.width * 0.7), height: size.height * 0.5) - - var i = 0 - for itemNode in self.itemNodes { + + for i in 0 ..< self.itemNodes.count { + let itemNode = self.itemNodes[i] + let containerNode = self.itemContainerNodes[i] + var angle = CGFloat.pi * 0.5 + CGFloat(i) * delta * CGFloat.pi * 2.0 - self.currentPosition * CGFloat.pi * 2.0 if angle < 0.0 { angle = CGFloat.pi * 2.0 + angle @@ -303,12 +325,13 @@ final class ReactionCarouselNode: ASDisplayNode, UIScrollViewDelegate { ) let itemFrame = CGRect(origin: CGPoint(x: size.width * 0.5 + point.x * areaSize.width * 0.5 - itemSize.width * 0.5, y: size.height * 0.5 + point.y * areaSize.height * 0.5 - itemSize.height * 0.5), size: itemSize) - itemNode.bounds = CGRect(origin: CGPoint(), size: itemFrame.size) - itemNode.position = itemFrame.center - itemNode.updateLayout(size: itemFrame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: transition) - transition.updateTransformScale(node: itemNode, scale: 1.0 - distance * 0.45) + containerNode.bounds = CGRect(origin: CGPoint(), size: itemFrame.size) + containerNode.position = itemFrame.center + transition.updateTransformScale(node: containerNode, scale: 1.0 - distance * 0.45) + + itemNode.frame = CGRect(origin: CGPoint(), size: itemFrame.size) + itemNode.updateLayout(size: itemFrame.size, isExpanded: false, largeExpanded: false, isPreviewing: false, transition: transition) - i += 1 } } } diff --git a/submodules/PresentationDataUtils/Sources/SolidRoundedButtonNode.swift b/submodules/PresentationDataUtils/Sources/SolidRoundedButtonNode.swift index 73ebbf3357..d708784757 100644 --- a/submodules/PresentationDataUtils/Sources/SolidRoundedButtonNode.swift +++ b/submodules/PresentationDataUtils/Sources/SolidRoundedButtonNode.swift @@ -5,6 +5,6 @@ import TelegramPresentationData public extension SolidRoundedButtonTheme { convenience init(theme: PresentationTheme) { - self.init(backgroundColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor) + self.init(backgroundColor: theme.list.itemCheckColors.fillColor, backgroundColors: [], foregroundColor: theme.list.itemCheckColors.foregroundColor) } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index 28ff9eb170..623b5b8085 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -299,6 +299,37 @@ public final class ReactionNode: ASDisplayNode, ReactionItemNode { } } +private func generatePremiumReactionIcon() -> UIImage? { + return generateImage(CGSize(width: 32.0, height: 32.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + if let backgroundImage = UIImage(bundleImageName: "Premium/BackgroundIcon"), let foregroundImage = UIImage(bundleImageName: "Premium/ForegroundIcon") { + context.saveGState() + if let cgImage = backgroundImage.cgImage { + context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage) + } + + let colorsArray: [CGColor] = [ + UIColor(rgb: 0xa34ecf).cgColor, + UIColor(rgb: 0xa34ecf).cgColor, + UIColor(rgb: 0xff7923).cgColor, + UIColor(rgb: 0xff7923).cgColor + ] + var locations: [CGFloat] = [0.0, 0.15, 0.85, 1.0] + let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)! + + context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions()) + + context.restoreGState() + + if let cgImage = foregroundImage.cgImage { + context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage) + } + context.setFillColor(UIColor.white.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + } + }) +} + final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode { var isExtracted: Bool = false @@ -309,23 +340,11 @@ final class PremiumReactionsNode: ASDisplayNode, ReactionItemNode { self.imageNode.contentMode = .center self.imageNode.displaysAsynchronously = false self.imageNode.isUserInteractionEnabled = false - self.imageNode.alpha = 0.5 + self.imageNode.image = generatePremiumReactionIcon() super.init() self.addSubnode(self.imageNode) - - if theme.overallDarkAppearance { - self.imageNode.image = generateTintedImage(image: UIImage(bundleImageName: "Premium/BackgroundIcon"), color: .white) - } else { - self.imageNode.image = UIImage(bundleImageName: "Premium/BackgroundIcon") - } - } - - override func didLoad() { - super.didLoad() - - self.imageNode.layer.compositingFilter = "softLightBlendMode" } func appear(animated: Bool) { diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 9e832cd22e..eaa8137460 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -19,12 +19,12 @@ private func generateIndefiniteActivityIndicatorImage(color: UIColor, diameter: public final class SolidRoundedButtonTheme: Equatable { public let backgroundColor: UIColor - public let gradientBackgroundColor: UIColor? + public let backgroundColors: [UIColor] public let foregroundColor: UIColor - public init(backgroundColor: UIColor, gradientBackgroundColor: UIColor? = nil, foregroundColor: UIColor) { + public init(backgroundColor: UIColor, backgroundColors: [UIColor] = [], foregroundColor: UIColor) { self.backgroundColor = backgroundColor - self.gradientBackgroundColor = gradientBackgroundColor + self.backgroundColors = backgroundColors self.foregroundColor = foregroundColor } @@ -32,7 +32,7 @@ public final class SolidRoundedButtonTheme: Equatable { if lhs.backgroundColor != rhs.backgroundColor { return false } - if lhs.gradientBackgroundColor != rhs.gradientBackgroundColor { + if lhs.backgroundColors != rhs.backgroundColors { return false } if lhs.foregroundColor != rhs.foregroundColor { @@ -47,13 +47,18 @@ public enum SolidRoundedButtonFont { case regular } +public enum SolidRoundedButtonIconPosition { + case left + case right +} + public final class SolidRoundedButtonNode: ASDisplayNode { private var theme: SolidRoundedButtonTheme private var font: SolidRoundedButtonFont private var fontSize: CGFloat private let gloss: Bool - private let buttonBackgroundNode: ASDisplayNode + private let buttonBackgroundNode: ASImageNode private var shimmerView: ShimmerEffectForegroundView? private var borderView: UIView? @@ -101,6 +106,14 @@ public final class SolidRoundedButtonNode: ASDisplayNode { } } } + + public var iconPosition: SolidRoundedButtonIconPosition = .left { + didSet { + if let width = self.validLayout { + _ = self.updateLayout(width: width, transition: .immediate) + } + } + } public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) { self.theme = theme @@ -111,9 +124,21 @@ public final class SolidRoundedButtonNode: ASDisplayNode { self.title = title self.gloss = gloss - self.buttonBackgroundNode = ASDisplayNode() + self.buttonBackgroundNode = ASImageNode() + self.buttonBackgroundNode.displaysAsynchronously = false self.buttonBackgroundNode.clipsToBounds = true - self.buttonBackgroundNode.backgroundColor = theme.backgroundColor + if theme.backgroundColors.count > 1 { + self.buttonBackgroundNode.backgroundColor = nil + + var locations: [CGFloat] = [] + let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1) + for i in 0 ..< theme.backgroundColors.count { + locations.append(delta * CGFloat(i)) + } + self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: height), colors: theme.backgroundColors, locations: locations, direction: .horizontal) + } else { + self.buttonBackgroundNode.backgroundColor = theme.backgroundColor + } self.buttonBackgroundNode.cornerRadius = cornerRadius self.buttonNode = HighlightTrackingButtonNode() @@ -230,13 +255,34 @@ public final class SolidRoundedButtonNode: ASDisplayNode { borderShimmerView.layer.compositingFilter = compositingFilter } - public func updateTheme(_ theme: SolidRoundedButtonTheme) { + public func updateTheme(_ theme: SolidRoundedButtonTheme, animated: Bool = false) { guard theme !== self.theme else { return } self.theme = theme - self.buttonBackgroundNode.backgroundColor = theme.backgroundColor + if animated { + if let snapshotView = self.buttonBackgroundNode.view.snapshotView(afterScreenUpdates: false) { + self.view.insertSubview(snapshotView, aboveSubview: self.buttonBackgroundNode.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + if theme.backgroundColors.count > 1 { + self.buttonBackgroundNode.backgroundColor = nil + + var locations: [CGFloat] = [] + let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1) + for i in 0 ..< theme.backgroundColors.count { + locations.append(delta * CGFloat(i)) + } + self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal) + } else { + self.buttonBackgroundNode.backgroundColor = theme.backgroundColor + self.buttonBackgroundNode.image = nil + } + self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor) @@ -284,6 +330,9 @@ public final class SolidRoundedButtonNode: ASDisplayNode { let iconSize = self.iconNode.image?.size ?? CGSize() let titleSize = self.titleNode.updateLayout(buttonSize) + let spacingOffset: CGFloat = 9.0 + let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset + let iconSpacing: CGFloat = self.iconSpacing var contentWidth: CGFloat = titleSize.width @@ -291,15 +340,25 @@ public final class SolidRoundedButtonNode: ASDisplayNode { contentWidth += iconSize.width + iconSpacing } var nextContentOrigin = floor((buttonFrame.width - contentWidth) / 2.0) - transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize)) - if !iconSize.width.isZero { - nextContentOrigin += iconSize.width + iconSpacing + + let iconFrame: CGRect + let titleFrame: CGRect + switch self.iconPosition { + case .left: + iconFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize) + if !iconSize.width.isZero { + nextContentOrigin += iconSize.width + iconSpacing + } + titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize) + case .right: + titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize) + if !iconSize.width.isZero { + nextContentOrigin += titleFrame.width + iconSpacing + } + iconFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize) } - let spacingOffset: CGFloat = 9.0 - let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset - - let titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize) + transition.updateFrame(node: self.iconNode, frame: iconFrame) transition.updateFrame(node: self.titleNode, frame: titleFrame) if self.subtitle != self.subtitleNode.attributedText?.string { @@ -374,7 +433,7 @@ public final class SolidRoundedButtonView: UIView { private var font: SolidRoundedButtonFont private var fontSize: CGFloat - private let buttonBackgroundNode: UIView + private let buttonBackgroundNode: UIImageView private let buttonGlossView: SolidRoundedButtonGlossView? private let buttonNode: HighlightTrackingButton private let titleNode: ImmediateTextView @@ -418,6 +477,14 @@ public final class SolidRoundedButtonView: UIView { } } + public var iconPosition: SolidRoundedButtonIconPosition = .left { + didSet { + if let width = self.validLayout { + _ = self.updateLayout(width: width, transition: .immediate) + } + } + } + public init(title: String? = nil, icon: UIImage? = nil, theme: SolidRoundedButtonTheme, font: SolidRoundedButtonFont = .bold, fontSize: CGFloat = 17.0, height: CGFloat = 48.0, cornerRadius: CGFloat = 24.0, gloss: Bool = false) { self.theme = theme self.font = font @@ -426,11 +493,23 @@ public final class SolidRoundedButtonView: UIView { self.buttonCornerRadius = cornerRadius self.title = title - self.buttonBackgroundNode = UIView() + self.buttonBackgroundNode = UIImageView() self.buttonBackgroundNode.clipsToBounds = true - self.buttonBackgroundNode.backgroundColor = theme.backgroundColor self.buttonBackgroundNode.layer.cornerRadius = cornerRadius + if theme.backgroundColors.count > 1 { + self.buttonBackgroundNode.backgroundColor = nil + + var locations: [CGFloat] = [] + let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1) + for i in 0 ..< theme.backgroundColors.count { + locations.append(delta * CGFloat(i)) + } + self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: height), colors: theme.backgroundColors, locations: locations, direction: .horizontal) + } else { + self.buttonBackgroundNode.backgroundColor = theme.backgroundColor + } + if gloss { self.buttonGlossView = SolidRoundedButtonGlossView(color: theme.foregroundColor, cornerRadius: cornerRadius) } else { @@ -545,7 +624,20 @@ public final class SolidRoundedButtonView: UIView { } self.theme = theme - self.buttonBackgroundNode.backgroundColor = theme.backgroundColor + if theme.backgroundColors.count > 1 { + self.buttonBackgroundNode.backgroundColor = nil + + var locations: [CGFloat] = [] + let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1) + for i in 0 ..< theme.backgroundColors.count { + locations.append(delta * CGFloat(i)) + } + self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal) + } else { + self.buttonBackgroundNode.backgroundColor = theme.backgroundColor + self.buttonBackgroundNode.image = nil + } + self.buttonGlossView?.color = theme.foregroundColor self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor) self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor) @@ -579,6 +671,8 @@ public final class SolidRoundedButtonView: UIView { let iconSize = self.iconNode.image?.size ?? CGSize() let titleSize = self.titleNode.updateLayout(buttonSize) + let spacingOffset: CGFloat = 9.0 + let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset let iconSpacing: CGFloat = self.iconSpacing var contentWidth: CGFloat = titleSize.width @@ -586,15 +680,25 @@ public final class SolidRoundedButtonView: UIView { contentWidth += iconSize.width + iconSpacing } var nextContentOrigin = floor((buttonFrame.width - contentWidth) / 2.0) - transition.updateFrame(view: self.iconNode, frame: CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize)) - if !iconSize.width.isZero { - nextContentOrigin += iconSize.width + iconSpacing + + let iconFrame: CGRect + let titleFrame: CGRect + switch self.iconPosition { + case .left: + iconFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize) + if !iconSize.width.isZero { + nextContentOrigin += iconSize.width + iconSpacing + } + titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize) + case .right: + titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize) + if !iconSize.width.isZero { + nextContentOrigin += titleFrame.width + iconSpacing + } + iconFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: floor((buttonFrame.height - iconSize.height) / 2.0)), size: iconSize) } - let spacingOffset: CGFloat = 9.0 - let verticalInset: CGFloat = self.subtitle == nil ? floor((buttonFrame.height - titleSize.height) / 2.0) : floor((buttonFrame.height - titleSize.height) / 2.0) - spacingOffset - - let titleFrame = CGRect(origin: CGPoint(x: buttonFrame.minX + nextContentOrigin, y: buttonFrame.minY + verticalInset), size: titleSize) + transition.updateFrame(view: self.iconNode, frame: iconFrame) transition.updateFrame(view: self.titleNode, frame: titleFrame) if self.subtitle != self.subtitleNode.attributedText?.string { diff --git a/submodules/TelegramCore/Sources/State/ManagedConfigurationUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedConfigurationUpdates.swift index bbe40b75f0..1010cab2d0 100644 --- a/submodules/TelegramCore/Sources/State/ManagedConfigurationUpdates.swift +++ b/submodules/TelegramCore/Sources/State/ManagedConfigurationUpdates.swift @@ -12,7 +12,7 @@ func managedConfigurationUpdates(accountManager: AccountManager mapToSignal { result -> Signal in return postbox.transaction { transaction -> Signal in switch result { - case let .config(flags, _, _, _, _, dcOptions, _, chatSizeMax, megagroupSizeMax, forwardedCountMax, _, _, _, _, _, _, _, _, savedGifsLimit, editTimeLimit, revokeTimeLimit, revokePmTimeLimit, _, stickersRecentLimit, _, _, _, pinnedDialogsCountMax, pinnedInfolderCountMax, _, _, _, _, _, autoupdateUrlPrefix, gifSearchUsername, venueSearchUsername, imgSearchUsername, _, captionLengthMax, _, webfileDcId, suggestedLangCode, langPackVersion, baseLangPackVersion): + case let .config(flags, _, _, _, _, dcOptions, _, chatSizeMax, megagroupSizeMax, forwardedCountMax, _, _, _, _, _, _, _, _, savedGifsLimit, editTimeLimit, revokeTimeLimit, revokePmTimeLimit, _, stickersRecentLimit, stickersFavedLimit, _, _, pinnedDialogsCountMax, pinnedInfolderCountMax, _, _, _, _, _, autoupdateUrlPrefix, gifSearchUsername, venueSearchUsername, imgSearchUsername, _, captionLengthMax, _, webfileDcId, suggestedLangCode, langPackVersion, baseLangPackVersion): var addressList: [Int: [MTDatacenterAddress]] = [:] for option in dcOptions { switch option { @@ -59,7 +59,7 @@ func managedConfigurationUpdates(accountManager: AccountManager> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.000000 0.864380 cm +0.000000 0.000000 0.000000 scn +20.000000 11.044711 m +20.000000 16.065481 15.522848 20.135620 10.000000 20.135620 c +4.477152 20.135620 0.000000 16.065481 0.000000 11.044711 c +0.000000 8.181209 1.337573 5.834022 3.613619 4.167677 c +3.904685 3.954580 4.172771 2.770550 3.523984 1.775995 c +2.875197 0.781441 2.066323 0.326941 2.471971 0.156790 c +2.722059 0.051889 4.199766 -0.000002 5.266314 0.598131 c +6.791368 1.453400 7.217727 2.304844 7.545889 2.229574 c +8.331102 2.049473 9.153261 1.953802 10.000000 1.953802 c +15.522848 1.953802 20.000000 6.023941 20.000000 11.044711 c +h +f +n +Q + +endstream +endobj + +3 0 obj + 668 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000000758 00000 n +0000000780 00000 n +0000000953 00000 n +0000001027 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1086 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/ContextX2.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/ContextX2.imageset/Contents.json new file mode 100644 index 0000000000..6fe332016d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/ContextX2.imageset/Contents.json @@ -0,0 +1,11 @@ +{ + "images" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/Contents.json new file mode 100644 index 0000000000..ef196420e9 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "badgefolder_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/badgefolder_24.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/badgefolder_24.pdf new file mode 100644 index 0000000000..3a148d6e05 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Folder.imageset/badgefolder_24.pdf @@ -0,0 +1,108 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.500000 3.500000 cm +0.000000 0.000000 0.000000 scn +0.326980 15.361972 m +0.000000 14.720236 0.000000 13.880157 0.000000 12.199999 c +0.000000 12.130000 l +0.000000 11.849974 0.000000 11.709961 0.054497 11.603004 c +0.102433 11.508924 0.178924 11.432433 0.273005 11.384497 c +0.379961 11.330000 0.519973 11.330000 0.799999 11.330000 c +18.266001 11.330000 l +18.483961 11.330000 18.592939 11.330000 18.679672 11.363398 c +18.811571 11.414187 18.915813 11.518429 18.966602 11.650328 c +19.000000 11.737060 19.000000 11.846040 19.000000 12.064000 c +19.000000 12.935841 19.000000 13.371761 18.866409 13.718690 c +18.663250 14.246285 18.246286 14.663251 17.718691 14.866409 c +17.371761 15.000000 16.935841 15.000000 16.064001 15.000000 c +10.414214 15.000000 l +10.245598 15.000000 10.161290 15.000000 10.080059 15.004409 c +9.397568 15.041451 8.748186 15.310432 8.239400 15.766834 c +8.178847 15.821153 8.119237 15.880764 8.000020 15.999980 c +8.000000 16.000000 l +7.880771 16.119228 7.821156 16.178844 7.760600 16.233166 c +7.251813 16.689568 6.602432 16.958549 5.919941 16.995592 c +5.838710 17.000000 5.754402 17.000000 5.585786 17.000000 c +4.800000 17.000000 l +3.119843 17.000000 2.279764 17.000000 1.638029 16.673019 c +1.073542 16.385399 0.614601 15.926457 0.326980 15.361972 c +h +0.163490 9.180986 m +0.000000 8.860118 0.000000 8.440079 0.000000 7.600000 c +0.000000 6.400000 l +0.000000 4.159790 0.000000 3.039685 0.435974 2.184038 c +0.819467 1.431390 1.431390 0.819468 2.184038 0.435974 c +3.039685 0.000000 4.159790 0.000000 6.399999 0.000000 c +12.599999 0.000000 l +14.840210 0.000000 15.960315 0.000000 16.815962 0.435974 c +17.568609 0.819468 18.180532 1.431390 18.564026 2.184038 c +19.000000 3.039685 19.000000 4.159790 19.000000 6.400001 c +19.000000 7.600000 l +19.000000 8.440079 19.000000 8.860118 18.836510 9.180986 c +18.692699 9.463228 18.463228 9.692699 18.180986 9.836510 c +17.860119 10.000000 17.440079 10.000000 16.600000 10.000000 c +2.400000 10.000000 l +1.559921 10.000000 1.139882 10.000000 0.819014 9.836510 c +0.536771 9.692699 0.307300 9.463228 0.163490 9.180986 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2139 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002229 00000 n +0000002252 00000 n +0000002425 00000 n +0000002499 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2558 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/Contents.json new file mode 100644 index 0000000000..f638604085 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "badgegroup_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/badgegroup_24.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/badgegroup_24.pdf new file mode 100644 index 0000000000..1d7c6be790 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Group.imageset/badgegroup_24.pdf @@ -0,0 +1,96 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 1.000000 3.500000 cm +0.000000 0.000000 0.000000 scn +7.798214 10.500000 m +9.731211 10.500000 11.298214 12.067003 11.298214 14.000000 c +11.298214 15.932997 9.731211 17.500000 7.798214 17.500000 c +5.865218 17.500000 4.298214 15.932997 4.298214 14.000000 c +4.298214 12.067003 5.865218 10.500000 7.798214 10.500000 c +h +15.800003 10.500000 m +17.456858 10.500000 18.800003 11.843145 18.800003 13.500000 c +18.800003 15.156855 17.456858 16.500000 15.800003 16.500000 c +14.143148 16.500000 12.800003 15.156855 12.800003 13.500000 c +12.800003 11.843145 14.143148 10.500000 15.800003 10.500000 c +h +12.332236 6.972357 m +13.807444 6.168993 14.733925 4.998592 15.315786 3.845357 c +15.642039 3.198733 15.667471 2.562775 15.476417 1.999998 c +15.084723 0.846209 13.783087 0.000000 12.298215 0.000000 c +3.298214 0.000000 l +1.089075 0.000000 -0.714483 1.873044 0.280643 3.845357 c +1.315064 5.895553 3.438652 8.000000 7.798214 8.000000 c +9.529486 8.000000 10.908134 7.668119 12.005980 7.139552 c +12.117610 7.085807 12.226336 7.030027 12.332236 6.972357 c +h +13.528140 7.807037 m +14.969814 6.871095 15.903218 5.633628 16.503208 4.444468 c +16.923948 3.610569 17.012365 2.769079 16.852047 1.999998 c +19.298889 1.999998 l +20.955744 1.999998 22.306608 3.401897 21.573879 4.887923 c +20.792742 6.472124 19.169998 8.117645 15.798890 8.117645 c +14.925295 8.117645 14.169111 8.007141 13.514557 7.815837 c +13.528140 7.807037 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1470 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001560 00000 n +0000001583 00000 n +0000001756 00000 n +0000001830 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +1889 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/Contents.json new file mode 100644 index 0000000000..bb753b82fe --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "badgelink_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/badgelink_24.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/badgelink_24.pdf new file mode 100644 index 0000000000..5bf6b1068a --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Link.imageset/badgelink_24.pdf @@ -0,0 +1,101 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 1.760010 0.617157 cm +0.000000 0.000000 0.000000 scn +11.862618 18.488392 m +13.375206 20.000980 15.827595 20.000980 17.340183 18.488392 c +18.852770 16.975805 18.852770 14.523417 17.340183 13.010829 c +15.042923 10.713570 l +13.530336 9.200982 11.077947 9.200982 9.565359 10.713570 c +9.363936 10.914992 9.189997 11.132182 9.042994 11.360771 c +8.744267 11.825293 8.125531 11.959696 7.661010 11.660969 c +7.196488 11.362242 7.062085 10.743505 7.360812 10.278984 c +7.584530 9.931103 7.848157 9.602345 8.151146 9.299356 c +10.444782 7.005720 14.163501 7.005720 16.457138 9.299356 c +18.754395 11.596616 l +21.048031 13.890251 21.048031 17.608971 18.754395 19.902607 c +16.460758 22.196241 12.742041 22.196241 10.448404 19.902607 c +8.151146 17.605347 l +7.760622 17.214823 7.760622 16.581657 8.151146 16.191133 c +8.541670 15.800610 9.174835 15.800610 9.565359 16.191133 c +11.862618 18.488392 l +h +8.612004 4.281246 m +7.099417 2.768658 4.647028 2.768658 3.134441 4.281246 c +1.621853 5.793833 1.621853 8.246222 3.134441 9.758809 c +5.431700 12.056068 l +6.944287 13.568655 9.396676 13.568655 10.909264 12.056068 c +11.110686 11.854645 11.284627 11.637455 11.431628 11.408867 c +11.730356 10.944345 12.349092 10.809942 12.813613 11.108670 c +13.278135 11.407397 13.412538 12.026133 13.113811 12.490655 c +12.890092 12.838536 12.626466 13.167293 12.323477 13.470282 c +10.029840 15.763918 6.311122 15.763918 4.017486 13.470282 c +1.720227 11.173022 l +-0.573409 8.879387 -0.573409 5.160667 1.720227 2.867031 c +4.013863 0.573397 7.732582 0.573397 10.026217 2.867031 c +12.323477 5.164291 l +12.714001 5.554815 12.714001 6.187981 12.323477 6.578505 c +11.932953 6.969028 11.299788 6.969028 10.909264 6.578505 c +8.612004 4.281246 l +h +f* +n +Q + +endstream +endobj + +3 0 obj + 1772 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000001862 00000 n +0000001885 00000 n +0000002058 00000 n +0000002132 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2191 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/Contents.json new file mode 100644 index 0000000000..99fcfa8ddf --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "badgepin_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/badgepin_24.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/badgepin_24.pdf new file mode 100644 index 0000000000..8b3e329b6b --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Pin.imageset/badgepin_24.pdf @@ -0,0 +1,108 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 2.398682 1.870453 cm +0.000000 0.000000 0.000000 scn +10.791250 19.146320 m +10.723915 18.838600 10.918332 18.158140 11.307166 16.797222 c +11.307172 16.797207 l +11.385918 16.521591 11.425291 16.383781 11.434118 16.251194 c +11.453884 15.954321 11.367108 15.660133 11.189390 15.421513 c +11.110017 15.314939 11.002151 15.220556 10.786422 15.031794 c +9.300981 13.732033 l +9.153894 13.603332 9.080351 13.538980 9.000536 13.488085 c +8.840524 13.386050 8.659379 13.321789 8.470838 13.300173 c +8.376793 13.289391 8.279138 13.293007 8.083828 13.300241 c +5.466913 13.397163 l +5.466902 13.397163 l +3.994872 13.451683 3.258856 13.478943 2.872828 13.296442 c +2.081267 12.922220 1.688092 12.020587 1.952483 11.185896 c +2.081421 10.778834 2.602221 10.258035 3.643819 9.216436 c +3.643824 9.216431 l +5.596159 7.264095 l +0.292893 1.960831 l +-0.097631 1.570305 -0.097631 0.937141 0.292893 0.546616 c +0.683418 0.156092 1.316582 0.156092 1.707107 0.546616 c +7.010372 5.849882 l +8.962662 3.897593 l +10.004263 2.855991 10.525064 2.335190 10.932127 2.206251 c +11.766818 1.941860 12.668451 2.335035 13.042673 3.126596 c +13.225174 3.512627 13.197914 4.248645 13.143394 5.720681 c +13.046472 8.337596 l +13.046472 8.337616 l +13.039238 8.532912 13.035621 8.630565 13.046403 8.724607 c +13.068019 8.913148 13.132281 9.094293 13.234317 9.254305 c +13.285213 9.334120 13.349563 9.407663 13.478264 9.554749 c +14.778025 11.040191 l +14.778039 11.040205 l +14.966792 11.255926 15.061172 11.363788 15.167744 11.443159 c +15.406365 11.620877 15.700552 11.707653 15.997424 11.687887 c +16.130014 11.679060 16.267828 11.639685 16.543453 11.560935 c +17.904371 11.172101 18.584831 10.977684 18.892551 11.045019 c +19.603607 11.200610 20.031862 11.928437 19.822556 12.625574 c +19.731974 12.927271 19.231562 13.427683 18.230738 14.428506 c +14.174737 18.484509 l +14.174723 18.484522 l +13.173909 19.485336 12.673501 19.985743 12.371805 20.076324 c +11.674668 20.285631 10.946841 19.857376 10.791250 19.146320 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2030 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 24.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002120 00000 n +0000002143 00000 n +0000002316 00000 n +0000002390 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +2449 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Contents.json new file mode 100644 index 0000000000..96acb63cc3 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Tmp.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Tmp.png b/submodules/TelegramUI/Images.xcassets/Premium/Tmp.imageset/Tmp.png new file mode 100644 index 0000000000000000000000000000000000000000..460cf4ce24ad7adcb9a681c67a2a6e81fccaad5f GIT binary patch literal 25736 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 LNkvXXu0mjfyyWZE#+KcVA4dLOX=HbRXQdryy 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 +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.000000 0.000000 0.000000 scn +0.435974 15.815962 m +0.000000 14.960315 0.000000 13.840210 0.000000 11.600000 c +0.000000 6.400001 l +0.000000 4.159790 0.000000 3.039685 0.435974 2.184038 c +0.819467 1.431389 1.431390 0.819468 2.184038 0.435974 c +3.039685 0.000000 4.159790 0.000000 6.400000 0.000000 c +17.600000 0.000000 l +19.840210 0.000000 20.960316 0.000000 21.815962 0.435974 c +22.568611 0.819468 23.180532 1.431389 23.564026 2.184038 c +24.000000 3.039685 24.000000 4.159790 24.000000 6.400000 c +24.000000 11.600000 l +24.000000 13.840210 24.000000 14.960315 23.564026 15.815962 c +23.180532 16.568611 22.568611 17.180532 21.815962 17.564026 c +20.960316 18.000000 19.840210 18.000000 17.600000 18.000000 c +6.400001 18.000000 l +4.159790 18.000000 3.039685 18.000000 2.184038 17.564026 c +1.431390 17.180532 0.819467 16.568611 0.435974 15.815962 c +h +5.505859 4.375977 m +5.246094 4.000000 5.027344 3.870117 4.637695 3.870117 c +4.090820 3.870117 3.660156 4.266602 3.660156 4.765625 c +3.660156 5.018555 3.742188 5.237305 3.940430 5.510742 c +6.401367 8.942383 l +6.401367 8.997070 l +4.022461 12.237305 l +3.796875 12.538086 3.721680 12.763672 3.721680 13.030273 c +3.721680 13.590820 4.166016 14.000977 4.767578 14.000977 c +5.157227 14.000977 5.430664 13.830078 5.697266 13.426758 c +7.809570 10.309570 l +7.864258 10.309570 l +10.044922 13.495117 l +10.284180 13.850586 10.489258 14.000977 10.885742 14.000977 c +11.425781 14.000977 11.890625 13.611328 11.890625 13.119141 c +11.890625 12.852539 11.815430 12.633789 11.617188 12.380859 c +9.053711 8.956055 l +9.053711 8.908203 l +11.541992 5.551758 l +11.733398 5.305664 11.815430 5.073242 11.815430 4.799805 c +11.815430 4.259766 11.398438 3.870117 10.824219 3.870117 c +10.448242 3.870117 10.195312 4.020508 9.928711 4.382812 c +7.727539 7.452148 l +7.672852 7.452148 l +5.505859 4.375977 l +h +19.690430 4.000000 m +14.228516 4.000000 l +13.579102 4.000000 13.257812 4.362305 13.257812 4.881836 c +13.257812 5.271484 13.408203 5.531250 13.825195 5.914062 c +16.764648 8.723633 l +17.981445 9.892578 18.295898 10.371094 18.295898 11.082031 c +18.295898 11.895508 17.666992 12.476562 16.785156 12.476562 c +15.992188 12.476562 15.445312 12.080078 15.089844 11.287109 c +14.857422 10.876953 14.618164 10.685547 14.173828 10.685547 c +13.620117 10.685547 13.291992 11.013672 13.291992 11.526367 c +13.291992 11.683594 13.319336 11.827148 13.367188 11.970703 c +13.702148 13.064453 14.939453 14.083008 16.805664 14.083008 c +18.897461 14.083008 20.305664 12.907227 20.305664 11.211914 c +20.305664 10.008789 19.717773 9.256836 18.104492 7.732422 c +15.971680 5.681641 l +15.971680 5.640625 l +19.690430 5.640625 l +20.223633 5.640625 20.544922 5.319336 20.544922 4.820312 c +20.544922 4.334961 20.223633 4.000000 19.690430 4.000000 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2835 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 24.000000 18.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002925 00000 n +0000002948 00000 n +0000003121 00000 n +0000003195 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3254 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 08d4527f84..31d3baff12 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -3358,7 +3358,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(false))).start(next: { [weak self] responded in if let strongSelf = self { if !responded { - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, text: strongSelf.presentationData.strings.Conversation_InteractiveEmojiSyncTip(EnginePeer(peer).compactDisplayTitle).string), elevatedLayout: false, action: { _ in return false }), in: .current) + strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, title: nil, text: strongSelf.presentationData.strings.Conversation_InteractiveEmojiSyncTip(EnginePeer(peer).compactDisplayTitle).string), elevatedLayout: false, action: { _ in return false }), in: .current) let _ = ApplicationSpecificNotice.incrementInteractiveEmojiSyncTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start() } @@ -7945,7 +7945,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G |> switchToLatest |> deliverOnMainQueue).start(next: { [weak self] added in if let strongSelf = self { - strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, text: added ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites), elevatedLayout: true, action: { _ in return false }), with: nil) +// strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: added ? "The Limit of 5 Stickers Reached" : nil, text: added ? "An older sticker was replaced with this one. You can [increase the limit]() to 10 stickers." : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites), elevatedLayout: true, action: { _ in return false }), with: nil) + strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: nil, text: added ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites), elevatedLayout: true, action: { _ in return false }), with: nil) } }) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 95423a59db..3e7a12e904 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -618,9 +618,39 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState if let starStatus = data.starStatus { actions.append(.action(ContextMenuActionItem(text: starStatus ? chatPresentationInterfaceState.strings.Stickers_RemoveFromFavorites : chatPresentationInterfaceState.strings.Stickers_AddToFavorites, icon: { theme in return generateTintedImage(image: starStatus ? UIImage(bundleImageName: "Chat/Context Menu/Unfave") : UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.actionSheet.primaryTextColor) - }, action: { _, f in - interfaceInteraction.toggleMessageStickerStarred(messages[0].id) - f(.default) + }, action: { c, f in +// interfaceInteraction.toggleMessageStickerStarred(messages[0].id) + + var subItems: [ContextMenuItem] = [] + + subItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) + }, action: { c, _ in + c.popItems() + }))) + subItems.append(.separator) + + subItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Stickers_FaveLimitReachedInfo("5", "10").string, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in + return nil + }, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?))) + + subItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Stickers_FaveLimitReplaceOlder, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Replace"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + interfaceInteraction.toggleMessageStickerStarred(messages[0].id) + }))) + + subItems.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Stickers_FaveLimitIncrease, icon: { _ in + return UIImage(bundleImageName: "Premium/Tmp2") + }, action: { _, f in + f(.default) + + }))) + + c.pushItems(items: .single(ContextController.Items(content: .list(subItems)))) +// +// f(.default) }))) } diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index 2113f92279..5a6c14d730 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -1087,7 +1087,7 @@ final class ChatMediaInputNode: ChatInputNode { return false } }) - strongSelf.controllerInteraction.presentController(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + strongSelf.controllerInteraction.presentController(controller, nil) }, getItemIsPreviewed: { item in return getItemIsPreviewedImpl?(item) ?? false }, openSearch: { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index c905ebf4b8..7759e02e7f 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2306,10 +2306,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage) let colorsArray: [CGColor] = [ - UIColor(rgb: 0x418eff).cgColor, - UIColor(rgb: 0x418eff).cgColor, - UIColor(rgb: 0xfc7ebd).cgColor, - UIColor(rgb: 0xfc7ebd).cgColor + UIColor(rgb: 0xa34ecf).cgColor, + UIColor(rgb: 0xa34ecf).cgColor, + UIColor(rgb: 0xff7923).cgColor, + UIColor(rgb: 0xff7923).cgColor ] var locations: [CGFloat] = [0.0, 0.35, 0.65, 1.0] let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)! diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 7d5c6ae3d4..78c73bde49 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -16,8 +16,6 @@ import DatePickerNode import DebugSettingsUI import TabBarUI -import TelegramCallsUI - public final class TelegramRootController: NavigationController { private let context: AccountContext diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index b3e52fd0a5..b6b2cb8a69 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -34,7 +34,7 @@ public enum UndoOverlayContent { case voiceChatRecording(text: String) case voiceChatFlag(text: String) case voiceChatCanSpeak(text: String) - case sticker(context: AccountContext, file: TelegramMediaFile, text: String) + case sticker(context: AccountContext, file: TelegramMediaFile, title: String?, text: String) case copy(text: String) case mediaSaved(text: String) case paymentSent(currencyValue: String, itemTitle: String) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index dd6df51216..87281dba91 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -408,7 +408,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural) + let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { _ in return nil }), textAlignment: .natural) self.textNode.attributedText = attributedText self.textNode.maximumNumberOfLines = 2 displayUndo = undo @@ -626,7 +627,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { displayUndo = false self.originalRemainingSeconds = 3 - case let .sticker(context, file, text): + case let .sticker(context, file, title, text): self.avatarNode = nil self.iconNode = nil self.iconCheckNode = nil @@ -680,10 +681,19 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { updatedImageSignal = .single({ _ in return nil }) updatedFetchSignal = .complete() } + + if let title = title { + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white) + } else { + self.titleNode.attributedText = nil + } let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) - let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural) + let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor) + let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in + return ("URL", contents) + }), textAlignment: .natural) self.textNode.attributedText = attributedText self.textNode.maximumNumberOfLines = 2