diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index b5a503f9f5..5c42773db3 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13132,3 +13132,9 @@ Sorry for the inconvenience."; "TopApps.Info.Title" = "Top Mini Apps"; "TopApps.Info.Text" = "This catalogue ranks mini apps based on their daily revenue, measured in Stars. To be listed, developers must set their main mini app in [@botfather]() (as described [here](https://core.telegram.org/bots/webapps#launching-the-main-mini-app)), have over **1,000** daily users, and earn a daily revenue above **1,000** Stars, based on weekly average."; "TopApps.Info.Done" = "Understood"; + +"Stars.Intro.Transaction.TelegramBotApi.Title" = "Paid Limit Extension"; +"Stars.Intro.Transaction.TelegramBotApi.Subtitle" = "Bot API"; + +"Stars.Transaction.TelegramBotApi.Title" = "Paid Limit Extension"; +"Stars.Transaction.TelegramBotApi.Subtitle" = "Bot API"; diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 21d5f1d820..e25e234b27 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -870,7 +870,7 @@ public struct ChatInputQueryCommandsResult: Equatable { public enum ChatPresentationInputQueryResult: Equatable { case stickers([FoundStickerItem]) - case hashtags([String]) + case hashtags([String], String) case mentions([EnginePeer]) case commands(ChatInputQueryCommandsResult) case emojis([(String, TelegramMediaFile?, String)], NSRange) @@ -884,9 +884,9 @@ public enum ChatPresentationInputQueryResult: Equatable { } else { return false } - case let .hashtags(lhsResults): - if case let .hashtags(rhsResults) = rhs { - return lhsResults == rhsResults + case let .hashtags(lhsResults, lhsQuery): + if case let .hashtags(rhsResults, rhsQuery) = rhs { + return lhsResults == rhsResults && lhsQuery == rhsQuery } else { return false } diff --git a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift index 5675dd5a46..5528cec2cb 100644 --- a/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserAddressListComponent.swift @@ -352,7 +352,8 @@ final class BrowserAddressListComponent: Component { highlighting: .default, updateIsHighlighted: { view, _ in - }) + } + ) ), environment: {}, containerSize: itemFrame.size diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 852175365b..07ff564746 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2495,7 +2495,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { } let firstRangeOrigin = chatListSearchResult.text.distance(from: chatListSearchResult.text.startIndex, to: firstRange.lowerBound) - if firstRangeOrigin > 24 { + if firstRangeOrigin > 24 && !chatListSearchResult.searchQuery.hasPrefix("#") { var leftOrigin: Int = 0 (composedString.string as NSString).enumerateSubstrings(in: NSMakeRange(0, firstRangeOrigin), options: [.byWords, .reverse]) { (str, range1, _, _) in let distanceFromEnd = firstRangeOrigin - range1.location diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 4ec3efdd87..6b49b01437 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -273,6 +273,24 @@ public extension ContainedViewLayoutTransition { } } + func updateFrameAdditive(layer: CALayer, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if layer.frame.equalTo(frame) && !force { + completion?(true) + } else { + switch self { + case .immediate: + layer.frame = frame + if let completion = completion { + completion(true) + } + case .animated: + let previousFrame = layer.frame + layer.frame = frame + self.animatePositionAdditive(layer: layer, offset: CGPoint(x: previousFrame.minX - frame.minX, y: previousFrame.minY - frame.minY)) + } + } + } + func updateFrameAdditive(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { if node.frame.equalTo(frame) && !force { completion?(true) diff --git a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift index 1c8ed37359..f7dbc7fac1 100644 --- a/submodules/StatisticsUI/Sources/StarsTransactionItem.swift +++ b/submodules/StatisticsUI/Sources/StarsTransactionItem.swift @@ -262,6 +262,9 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode { case .ads: itemTitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Title itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Subtitle + case .apiLimitExtension: + itemTitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramBotApi_Title + itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramBotApi_Subtitle case .unsupported: itemTitle = item.presentationData.strings.Stars_Intro_Transaction_Unsupported_Title itemSubtitle = nil diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 2ffc81d679..2b5a7bb6d8 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -902,8 +902,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1401868056] = { return Api.StarsSubscription.parse_starsSubscription($0) } dict[88173912] = { return Api.StarsSubscriptionPricing.parse_starsSubscriptionPricing($0) } dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($0) } - dict[178185410] = { return Api.StarsTransaction.parse_starsTransaction($0) } + dict[-1216644148] = { return Api.StarsTransaction.parse_starsTransaction($0) } dict[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) } + dict[-110658899] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAPI($0) } dict[1617438738] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAds($0) } dict[-1269320843] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAppStore($0) } dict[-382740222] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerFragment($0) } diff --git a/submodules/TelegramApi/Sources/Api23.swift b/submodules/TelegramApi/Sources/Api23.swift index 00f070a3ef..179c33dc08 100644 --- a/submodules/TelegramApi/Sources/Api23.swift +++ b/submodules/TelegramApi/Sources/Api23.swift @@ -1010,13 +1010,13 @@ public extension Api { } public extension Api { enum StarsTransaction: TypeConstructorDescription { - case starsTransaction(flags: Int32, id: String, stars: Int64, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?) + case starsTransaction(flags: Int32, id: String, stars: Int64, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipDate: Int32?, floodskipNumber: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift): + case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipDate, let floodskipNumber): if boxed { - buffer.appendInt32(178185410) + buffer.appendInt32(-1216644148) } serializeInt32(flags, buffer: buffer, boxed: false) serializeString(id, buffer: buffer, boxed: false) @@ -1038,14 +1038,16 @@ public extension Api { if Int(flags) & Int(1 << 12) != 0 {serializeInt32(subscriptionPeriod!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {serializeInt32(giveawayPostId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 14) != 0 {stargift!.serialize(buffer, true)} + if Int(flags) & Int(1 << 15) != 0 {serializeInt32(floodskipDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 15) != 0 {serializeInt32(floodskipNumber!, buffer: buffer, boxed: false)} break } } public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift): - return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any), ("subscriptionPeriod", subscriptionPeriod as Any), ("giveawayPostId", giveawayPostId as Any), ("stargift", stargift as Any)]) + case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipDate, let floodskipNumber): + return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any), ("subscriptionPeriod", subscriptionPeriod as Any), ("giveawayPostId", giveawayPostId as Any), ("stargift", stargift as Any), ("floodskipDate", floodskipDate as Any), ("floodskipNumber", floodskipNumber as Any)]) } } @@ -1090,6 +1092,10 @@ public extension Api { if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() { _16 = Api.parse(reader, signature: signature) as? Api.StarGift } } + var _17: Int32? + if Int(_1!) & Int(1 << 15) != 0 {_17 = reader.readInt32() } + var _18: Int32? + if Int(_1!) & Int(1 << 15) != 0 {_18 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -1106,8 +1112,10 @@ public extension Api { let _c14 = (Int(_1!) & Int(1 << 12) == 0) || _14 != nil let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil let _c16 = (Int(_1!) & Int(1 << 14) == 0) || _16 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 { - return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16) + let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 15) == 0) || _18 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 { + return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipDate: _17, floodskipNumber: _18) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index a9a831d8b5..950f9bcbf7 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -1,6 +1,7 @@ public extension Api { enum StarsTransactionPeer: TypeConstructorDescription { case starsTransactionPeer(peer: Api.Peer) + case starsTransactionPeerAPI case starsTransactionPeerAds case starsTransactionPeerAppStore case starsTransactionPeerFragment @@ -15,6 +16,12 @@ public extension Api { buffer.appendInt32(-670195363) } peer.serialize(buffer, true) + break + case .starsTransactionPeerAPI: + if boxed { + buffer.appendInt32(-110658899) + } + break case .starsTransactionPeerAds: if boxed { @@ -59,6 +66,8 @@ public extension Api { switch self { case .starsTransactionPeer(let peer): return ("starsTransactionPeer", [("peer", peer as Any)]) + case .starsTransactionPeerAPI: + return ("starsTransactionPeerAPI", []) case .starsTransactionPeerAds: return ("starsTransactionPeerAds", []) case .starsTransactionPeerAppStore: @@ -87,6 +96,9 @@ public extension Api { return nil } } + public static func parse_starsTransactionPeerAPI(_ reader: BufferReader) -> StarsTransactionPeer? { + return Api.StarsTransactionPeer.starsTransactionPeerAPI + } public static func parse_starsTransactionPeerAds(_ reader: BufferReader) -> StarsTransactionPeer? { return Api.StarsTransactionPeer.starsTransactionPeerAds } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 3e0ed0eab8..82a91ebb7b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -490,7 +490,10 @@ private final class StarsContextImpl { private extension StarsContext.State.Transaction { init?(apiTransaction: Api.StarsTransaction, peerId: EnginePeer.Id?, transaction: Transaction) { switch apiTransaction { - case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod, giveawayPostId, starGift): + case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod, giveawayPostId, starGift, floodskipDate, floodskipNumber): + let _ = floodskipDate + let _ = floodskipNumber + let parsedPeer: StarsContext.State.Transaction.Peer var paidMessageId: MessageId? var giveawayMessageId: MessageId? @@ -506,6 +509,8 @@ private extension StarsContext.State.Transaction { parsedPeer = .premiumBot case .starsTransactionPeerAds: parsedPeer = .ads + case .starsTransactionPeerAPI: + parsedPeer = .apiLimitExtension case .starsTransactionPeerUnsupported: parsedPeer = .unsupported case let .starsTransactionPeer(apiPeer): @@ -595,6 +600,7 @@ public final class StarsContext { case fragment case premiumBot case ads + case apiLimitExtension case unsupported case peer(EnginePeer) } diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift index 3051274d1a..4dd642f7f4 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/InputContextQueries.swift @@ -130,10 +130,10 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, cha case .hashtag: break default: - signal = .single({ _ in return .hashtags([]) }) + signal = .single({ _ in return .hashtags([], query) }) } } else { - signal = .single({ _ in return .hashtags([]) }) + signal = .single({ _ in return .hashtags([], query) }) } let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.messages.recentlyUsedHashtags() @@ -145,7 +145,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, cha result.append(hashtag) } } - return { _ in return .hashtags(result) } + return { _ in return .hashtags(result, query) } } |> castError(ChatContextQueryError.self) diff --git a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift index e05bba66bd..73f748e91b 100644 --- a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift @@ -274,7 +274,7 @@ public final class StarsAvatarComponent: Component { self.iconView.isHidden = false self.avatarNode.isHidden = true self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white) - case .unsupported: + case .unsupported, .apiLimitExtension: iconInset = 7.0 self.backgroundView.image = generateGradientFilledCircleImage( diameter: size.width, diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift index 09afb0a4e3..50544f7399 100644 --- a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift @@ -788,7 +788,7 @@ public final class StarsImageComponent: Component { direction: .mirroredDiagonal ) iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white) - case .peer, .unsupported: + case .peer, .unsupported, .apiLimitExtension: iconInset = 15.0 iconBackgroundView.image = generateGradientFilledCircleImage( diameter: imageSize.width, diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index cb4c44c7a6..c5ecb26401 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -410,6 +410,9 @@ private final class StarsTransactionSheetContent: CombinedComponent { case .ads: titleText = strings.Stars_Transaction_TelegramAds_Title via = strings.Stars_Transaction_TelegramAds_Subtitle + case .apiLimitExtension: + titleText = strings.Stars_Transaction_TelegramBotApi_Title + via = strings.Stars_Transaction_TelegramBotApi_Subtitle case .unsupported: titleText = strings.Stars_Transaction_Unsupported_Title } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift index 6c4d12c565..23aa277d0f 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsListPanelComponent.swift @@ -344,6 +344,9 @@ final class StarsTransactionsListPanelComponent: Component { case .ads: itemTitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Title itemSubtitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Subtitle + case .apiLimitExtension: + itemTitle = environment.strings.Stars_Intro_Transaction_TelegramBotApi_Title + itemSubtitle = environment.strings.Stars_Intro_Transaction_TelegramBotApi_Subtitle case .unsupported: itemTitle = environment.strings.Stars_Intro_Transaction_Unsupported_Title itemSubtitle = nil diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/SuggestHashtag.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/SuggestHashtag.imageset/Contents.json new file mode 100644 index 0000000000..719c35fbb0 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/SuggestHashtag.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagrecent.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/SuggestHashtag.imageset/tagrecent.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/SuggestHashtag.imageset/tagrecent.pdf new file mode 100644 index 0000000000..62a616bc67 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Hashtag/SuggestHashtag.imageset/tagrecent.pdf differ diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift index b09109858c..11bf2eabe9 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContextPanels.swift @@ -10,7 +10,7 @@ private func inputQueryResultPriority(_ result: ChatPresentationInputQueryResult switch result { case let .stickers(items): return (0, !items.isEmpty) - case let .hashtags(items): + case let .hashtags(items, _): return (1, !items.isEmpty) case let .mentions(items): return (2, !items.isEmpty) @@ -98,15 +98,19 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa return panel } } - case let .hashtags(results): - if !results.isEmpty { + case let .hashtags(results, query): + var peer: EnginePeer? + if let chatPeer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, chatPeer.addressName != nil { + peer = EnginePeer(chatPeer) + } + if !results.isEmpty || (peer != nil && query.count >= 4) { if let currentPanel = currentPanel as? HashtagChatInputContextPanelNode { - currentPanel.updateResults(results) + currentPanel.updateResults(results, query: query, peer: peer) return currentPanel } else { let panel = HashtagChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext) panel.interfaceInteraction = interfaceInteraction - panel.updateResults(results) + panel.updateResults(results, query: query, peer: peer) return panel } } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift index f765fa6430..4cb40d1553 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextQueries.swift @@ -114,10 +114,10 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee case .hashtag: break default: - signal = .single({ _ in return .hashtags([]) }) + signal = .single({ _ in return .hashtags([], query) }) } } else { - signal = .single({ _ in return .hashtags([]) }) + signal = .single({ _ in return .hashtags([], query) }) } let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.messages.recentlyUsedHashtags() @@ -129,7 +129,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee result.append(hashtag) } } - return { _ in return .hashtags(result) } + return { _ in return .hashtags(result, query) } } |> castError(ChatContextQueryError.self) diff --git a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift index e755628005..26ec77e4ea 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputContextPanelNode.swift @@ -16,33 +16,38 @@ import ChatContextQuery import ChatInputContextPanelNode private struct HashtagChatInputContextPanelEntryStableId: Hashable { - let text: String + let title: String } private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable { let index: Int let theme: PresentationTheme - let text: String + let peer: EnginePeer? + let title: String + let text: String? + let badge: String? + let hashtag: String let revealed: Bool + let isAdditionalRecent: Bool var stableId: HashtagChatInputContextPanelEntryStableId { - return HashtagChatInputContextPanelEntryStableId(text: self.text) + return HashtagChatInputContextPanelEntryStableId(title: self.title) } func withUpdatedTheme(_ theme: PresentationTheme) -> HashtagChatInputContextPanelEntry { - return HashtagChatInputContextPanelEntry(index: self.index, theme: theme, text: self.text, revealed: self.revealed) + return HashtagChatInputContextPanelEntry(index: self.index, theme: theme, peer: peer, title: self.title, text: self.text, badge: self.badge, hashtag: self.hashtag, revealed: self.revealed, isAdditionalRecent: self.isAdditionalRecent) } static func ==(lhs: HashtagChatInputContextPanelEntry, rhs: HashtagChatInputContextPanelEntry) -> Bool { - return lhs.index == rhs.index && lhs.text == rhs.text && lhs.theme === rhs.theme && lhs.revealed == rhs.revealed + return lhs.index == rhs.index && lhs.peer == rhs.peer && lhs.title == rhs.title && lhs.text == rhs.text && lhs.badge == rhs.badge && lhs.hashtag == rhs.hashtag && lhs.theme === rhs.theme && lhs.revealed == rhs.revealed && lhs.isAdditionalRecent == rhs.isAdditionalRecent } static func <(lhs: HashtagChatInputContextPanelEntry, rhs: HashtagChatInputContextPanelEntry) -> Bool { return lhs.index < rhs.index } - func item(account: Account, presentationData: PresentationData, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) -> ListViewItem { - return HashtagChatInputPanelItem(presentationData: ItemListPresentationData(presentationData), text: self.text, revealed: self.revealed, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested) + func item(context: AccountContext, presentationData: PresentationData, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) -> ListViewItem { + return HashtagChatInputPanelItem(context: context, presentationData: ItemListPresentationData(presentationData), peer: self.peer, title: self.title, text: self.text, badge: self.badge, hashtag: self.hashtag, revealed: self.revealed, isAdditionalRecent: self.isAdditionalRecent, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested) } } @@ -52,12 +57,12 @@ private struct HashtagChatInputContextPanelTransition { let updates: [ListViewUpdateItem] } -private func preparedTransition(from fromEntries: [HashtagChatInputContextPanelEntry], to toEntries: [HashtagChatInputContextPanelEntry], account: Account, presentationData: PresentationData, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) -> HashtagChatInputContextPanelTransition { +private func preparedTransition(from fromEntries: [HashtagChatInputContextPanelEntry], to toEntries: [HashtagChatInputContextPanelEntry], context: AccountContext, presentationData: PresentationData, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) -> HashtagChatInputContextPanelTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested), directionHint: nil) } return HashtagChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates) } @@ -67,6 +72,8 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { private var currentEntries: [HashtagChatInputContextPanelEntry]? private var currentResults: [String] = [] + private var currentQuery: String = "" + private var currentPeer: EnginePeer? private var revealedHashtag: String? private var enqueuedTransitions: [(HashtagChatInputContextPanelTransition, Bool)] = [] @@ -91,14 +98,72 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { self.addSubnode(self.listView) } - func updateResults(_ results: [String]) { + func updateResults(_ results: [String], query: String, peer: EnginePeer?) { self.currentResults = results + self.currentQuery = query + self.currentPeer = peer var entries: [HashtagChatInputContextPanelEntry] = [] var index = 0 var stableIds = Set() - for text in results { - let entry = HashtagChatInputContextPanelEntry(index: index, theme: self.theme, text: text, revealed: text == self.revealedHashtag) + + var isAdditionalRecent = false + if let peer, let _ = peer.addressName { + isAdditionalRecent = true + } + //TODO:localize + if query.count > 3 { + if let peer, let addressName = peer.addressName { + let genericEntry = HashtagChatInputContextPanelEntry( + index: 0, + theme: self.theme, + peer: nil, + title: "Use #\(query)", + text: "searches posts from all channels", + badge: nil, + hashtag: query, + revealed: false, + isAdditionalRecent: false + ) + stableIds.insert(genericEntry.stableId) + entries.append(genericEntry) + + let peerEntry = HashtagChatInputContextPanelEntry( + index: 1, + theme: self.theme, + peer: peer, + title: "Use #\(query)@\(addressName)", + text: "searches only posts from this channel", + badge: "NEW", + hashtag: "\(query)@\(addressName)", + revealed: false, + isAdditionalRecent: false + ) + stableIds.insert(peerEntry.stableId) + entries.append(peerEntry) + } + } + + index = 2 + + for hashtag in results { + if hashtag == query { + continue + } + if !hashtag.hasPrefix(query) { + continue + } + let entry = HashtagChatInputContextPanelEntry( + index: index, + theme: self.theme, + peer: hashtag.contains("@") ? peer : nil, + title: "#\(hashtag)", + text: nil, + badge: nil, + hashtag: hashtag, + revealed: hashtag == self.revealedHashtag, + isAdditionalRecent: isAdditionalRecent && !hashtag.contains("@") + ) if stableIds.contains(entry.stableId) { continue } @@ -112,10 +177,10 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) { let firstTime = from == nil let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - let transition = preparedTransition(from: from ?? [], to: to, account: self.context.account, presentationData: presentationData, setHashtagRevealed: { [weak self] text in + let transition = preparedTransition(from: from ?? [], to: to, context: self.context, presentationData: presentationData, setHashtagRevealed: { [weak self] text in if let strongSelf = self { strongSelf.revealedHashtag = text - strongSelf.updateResults(strongSelf.currentResults) + strongSelf.updateResults(strongSelf.currentResults, query: strongSelf.currentQuery, peer: strongSelf.currentPeer) } }, hashtagSelected: { [weak self] text in if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction { @@ -131,8 +196,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { if let range = hashtagQueryRange { let inputText = NSMutableAttributedString(attributedString: textInputState.inputText) - let replacementText = text + " " - + let replacementText = text inputText.replaceCharacters(in: range, with: replacementText) let selectionPosition = range.lowerBound + (replacementText as NSString).length @@ -172,7 +236,11 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode { //options.insert(.LowLatency) } else { options.insert(.AnimateTopItemPosition) - options.insert(.AnimateCrossfade) + if transition.insertions.isEmpty && transition.deletions.isEmpty && transition.updates.count <= 2 { + options.insert(.AnimateInsertion) + } else { + options.insert(.AnimateCrossfade) + } } var insets = UIEdgeInsets() diff --git a/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift index dfccdec0f2..594071f323 100644 --- a/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HashtagChatInputPanelItem.swift @@ -8,21 +8,35 @@ import Postbox import TelegramPresentationData import TelegramUIPreferences import ItemListUI +import AvatarNode +import AccountContext final class HashtagChatInputPanelItem: ListViewItem { + fileprivate let context: AccountContext fileprivate let presentationData: ItemListPresentationData - fileprivate let text: String + fileprivate let peer: EnginePeer? + fileprivate let title: String + fileprivate let text: String? + fileprivate let badge: String? + fileprivate let hashtag: String fileprivate let revealed: Bool + fileprivate let isAdditionalRecent: Bool fileprivate let setHashtagRevealed: (String?) -> Void private let hashtagSelected: (String) -> Void fileprivate let removeRequested: (String) -> Void let selectable: Bool = true - public init(presentationData: ItemListPresentationData, text: String, revealed: Bool, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) { + public init(context: AccountContext, presentationData: ItemListPresentationData, peer: EnginePeer?, title: String, text: String?, badge: String? = nil, hashtag: String, revealed: Bool, isAdditionalRecent: Bool, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) { + self.context = context self.presentationData = presentationData + self.peer = peer + self.title = title self.text = text + self.badge = badge + self.hashtag = hashtag self.revealed = revealed + self.isAdditionalRecent = isAdditionalRecent self.setHashtagRevealed = setHashtagRevealed self.hashtagSelected = hashtagSelected self.removeRequested = removeRequested @@ -79,14 +93,29 @@ final class HashtagChatInputPanelItem: ListViewItem { if self.revealed { self.setHashtagRevealed(nil) } else { - self.hashtagSelected(self.text) + if self.isAdditionalRecent { + self.hashtagSelected(self.hashtag) + } else { + self.hashtagSelected(self.hashtag + " ") + } } } } +private let avatarFont = avatarPlaceholderFont(size: 16.0) + final class HashtagChatInputPanelItemNode: ListViewItemNode { static let itemHeight: CGFloat = 42.0 + + private let iconBackgroundLayer = SimpleLayer() + private let iconLayer = SimpleLayer() + private var avatarNode: AvatarNode? + + private let badgeBackgroundLayer = SimpleLayer() + + private let titleNode: TextNode private let textNode: TextNode + private let badgeNode: TextNode private let topSeparatorNode: ASDisplayNode private let separatorNode: ASDisplayNode private let highlightedBackgroundNode: ASDisplayNode @@ -105,7 +134,12 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { private var validLayout: (CGSize, CGFloat, CGFloat)? init() { + self.iconBackgroundLayer.cornerRadius = 15.0 + self.badgeBackgroundLayer.cornerRadius = 4.0 + + self.titleNode = TextNode() self.textNode = TextNode() + self.badgeNode = TextNode() self.topSeparatorNode = ASDisplayNode() self.topSeparatorNode.isLayerBacked = true @@ -120,9 +154,10 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { self.activateAreaNode.accessibilityTraits = [.button] super.init(layerBacked: false, dynamicBounce: false) - + self.addSubnode(self.topSeparatorNode) self.addSubnode(self.separatorNode) + self.addSubnode(self.titleNode) self.addSubnode(self.textNode) self.addSubnode(self.activateAreaNode) @@ -131,6 +166,12 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { override func didLoad() { super.didLoad() + self.view.layer.addSublayer(self.iconBackgroundLayer) + self.iconBackgroundLayer.addSublayer(self.iconLayer) + + self.view.layer.addSublayer(self.badgeBackgroundLayer) + self.addSubnode(self.badgeNode) + let recognizer = ItemListRevealOptionsGestureRecognizer(target: self, action: #selector(self.revealGesture(_:))) self.recognizer = recognizer recognizer.allowAnyDirection = false @@ -149,16 +190,24 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { } func asyncLayout() -> (_ item: HashtagChatInputPanelItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) { + let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTextLayout = TextNode.asyncLayout(self.textNode) + let makeBadgeLayout = TextNode.asyncLayout(self.badgeNode) + return { [weak self] item, params, mergedTop, mergedBottom in - let textFont = Font.medium(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) - - let baseWidth = params.width - params.leftInset - params.rightInset + let titleFont = Font.semibold(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) + let textFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0)) + let badgeFont = Font.medium(floor(item.presentationData.fontSize.baseDisplaySize * 10.0 / 17.0)) let leftInset: CGFloat = 15.0 + params.leftInset + let textLeftInset: CGFloat = 40.0 + let baseWidth = params.width - params.leftInset - params.rightInset - textLeftInset - let title = "#\(item.text)" - let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: title, font: textFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: baseWidth, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (badgeLayout, badgeApply) = makeBadgeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.badge ?? "", font: badgeFont, textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: baseWidth, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: baseWidth - badgeLayout.size.width, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.text ?? "", font: textFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: baseWidth, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: HashtagChatInputPanelItemNode.itemHeight), insets: UIEdgeInsets()) @@ -166,26 +215,70 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { if let strongSelf = self { strongSelf.item = item strongSelf.validLayout = (nodeLayout.contentSize, params.leftInset, params.rightInset) - + let revealOffset = strongSelf.revealOffset + if strongSelf.iconLayer.contents == nil { + strongSelf.iconLayer.contents = UIImage(bundleImageName: "Chat/Hashtag/SuggestHashtag")?.cgImage + } + strongSelf.iconBackgroundLayer.backgroundColor = item.presentationData.theme.list.itemAccentColor.cgColor + strongSelf.iconLayer.layerTintColor = item.presentationData.theme.list.itemCheckColors.foregroundColor.cgColor + strongSelf.badgeBackgroundLayer.backgroundColor = item.presentationData.theme.list.itemAccentColor.cgColor + strongSelf.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor + let _ = titleApply() let _ = textApply() - strongSelf.textNode.frame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: floor((nodeLayout.contentSize.height - textLayout.size.height) / 2.0)), size: textLayout.size) + let _ = badgeApply() + + if textLayout.size.height > 0.0 { + let combinedHeight = titleLayout.size.height + textLayout.size.height + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + textLeftInset, y: floor((nodeLayout.contentSize.height - combinedHeight) / 2.0)), size: titleLayout.size) + strongSelf.textNode.frame = CGRect(origin: CGPoint(x: leftInset + textLeftInset, y: floor((nodeLayout.contentSize.height - combinedHeight) / 2.0) + combinedHeight - textLayout.size.height), size: textLayout.size) + } else { + strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: revealOffset + leftInset + textLeftInset, y: floor((nodeLayout.contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size) + } + + if badgeLayout.size.height > 0.0 { + let badgeFrame = CGRect(origin: CGPoint(x: strongSelf.titleNode.frame.maxX + 8.0, y: floorToScreenPixels(strongSelf.titleNode.frame.midY - badgeLayout.size.height / 2.0)), size: badgeLayout.size) + let badgeBackgroundFrame = badgeFrame.insetBy(dx: -3.0, dy: -2.0) + + strongSelf.badgeNode.frame = badgeFrame + strongSelf.badgeBackgroundLayer.frame = badgeBackgroundFrame + } strongSelf.topSeparatorNode.isHidden = mergedTop strongSelf.separatorNode.isHidden = !mergedBottom + let iconSize = CGSize(width: 30.0, height: 30.0) + strongSelf.iconBackgroundLayer.frame = CGRect(origin: CGPoint(x: leftInset - 3.0, y: floor((nodeLayout.contentSize.height - 30.0) / 2.0)), size: iconSize) + strongSelf.iconLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 30.0, height: 30.0)) + + if let peer = item.peer { + strongSelf.iconBackgroundLayer.isHidden = true + let avatarNode: AvatarNode + if let current = strongSelf.avatarNode { + avatarNode = current + } else { + avatarNode = AvatarNode(font: avatarFont) + strongSelf.addSubnode(avatarNode) + strongSelf.avatarNode = avatarNode + } + avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: peer) + avatarNode.frame = strongSelf.iconBackgroundLayer.frame + } else { + strongSelf.iconBackgroundLayer.isHidden = false + } + strongSelf.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: UIScreenPixel)) strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset, height: UIScreenPixel)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel)) - strongSelf.activateAreaNode.accessibilityLabel = title + strongSelf.activateAreaNode.accessibilityLabel = item.title strongSelf.activateAreaNode.frame = CGRect(origin: .zero, size: nodeLayout.size) strongSelf.setRevealOptions([ItemListRevealOption(key: 0, title: item.presentationData.strings.Common_Delete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]) @@ -197,7 +290,8 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) { if let (_, leftInset, _) = self.validLayout { - transition.updateFrameAdditive(node: self.textNode, frame: CGRect(origin: CGPoint(x: min(offset, 0.0) + 15.0 + leftInset, y: self.textNode.frame.minY), size: self.textNode.frame.size)) + transition.updateFrameAdditive(layer: self.iconBackgroundLayer, frame: CGRect(origin: CGPoint(x: min(offset, 0.0) + 12.0 + leftInset, y: self.iconBackgroundLayer.frame.minY), size: self.iconBackgroundLayer.frame.size)) + transition.updateFrameAdditive(node: self.titleNode, frame: CGRect(origin: CGPoint(x: min(offset, 0.0) + 15.0 + 40.0 + leftInset, y: self.titleNode.frame.minY), size: self.titleNode.frame.size)) } } @@ -276,6 +370,11 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { } override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + if let item = self.item { + if let _ = item.text { + return false + } + } return true } @@ -356,7 +455,7 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode { guard let item = self.item else { return } - item.removeRequested(item.text) + item.removeRequested(item.hashtag) } private func setupAndAddRevealNode() {