mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Hashtag search improvements
This commit is contained in:
parent
04b25d7152
commit
4806840989
@ -13132,3 +13132,9 @@ Sorry for the inconvenience.";
|
|||||||
"TopApps.Info.Title" = "Top Mini Apps";
|
"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.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";
|
"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";
|
||||||
|
|||||||
@ -870,7 +870,7 @@ public struct ChatInputQueryCommandsResult: Equatable {
|
|||||||
|
|
||||||
public enum ChatPresentationInputQueryResult: Equatable {
|
public enum ChatPresentationInputQueryResult: Equatable {
|
||||||
case stickers([FoundStickerItem])
|
case stickers([FoundStickerItem])
|
||||||
case hashtags([String])
|
case hashtags([String], String)
|
||||||
case mentions([EnginePeer])
|
case mentions([EnginePeer])
|
||||||
case commands(ChatInputQueryCommandsResult)
|
case commands(ChatInputQueryCommandsResult)
|
||||||
case emojis([(String, TelegramMediaFile?, String)], NSRange)
|
case emojis([(String, TelegramMediaFile?, String)], NSRange)
|
||||||
@ -884,9 +884,9 @@ public enum ChatPresentationInputQueryResult: Equatable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .hashtags(lhsResults):
|
case let .hashtags(lhsResults, lhsQuery):
|
||||||
if case let .hashtags(rhsResults) = rhs {
|
if case let .hashtags(rhsResults, rhsQuery) = rhs {
|
||||||
return lhsResults == rhsResults
|
return lhsResults == rhsResults && lhsQuery == rhsQuery
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -352,7 +352,8 @@ final class BrowserAddressListComponent: Component {
|
|||||||
highlighting: .default,
|
highlighting: .default,
|
||||||
updateIsHighlighted: { view, _ in
|
updateIsHighlighted: { view, _ in
|
||||||
|
|
||||||
})
|
}
|
||||||
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: itemFrame.size
|
containerSize: itemFrame.size
|
||||||
|
|||||||
@ -2495,7 +2495,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let firstRangeOrigin = chatListSearchResult.text.distance(from: chatListSearchResult.text.startIndex, to: firstRange.lowerBound)
|
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
|
var leftOrigin: Int = 0
|
||||||
(composedString.string as NSString).enumerateSubstrings(in: NSMakeRange(0, firstRangeOrigin), options: [.byWords, .reverse]) { (str, range1, _, _) in
|
(composedString.string as NSString).enumerateSubstrings(in: NSMakeRange(0, firstRangeOrigin), options: [.byWords, .reverse]) { (str, range1, _, _) in
|
||||||
let distanceFromEnd = firstRangeOrigin - range1.location
|
let distanceFromEnd = firstRangeOrigin - range1.location
|
||||||
|
|||||||
@ -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) {
|
func updateFrameAdditive(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||||
if node.frame.equalTo(frame) && !force {
|
if node.frame.equalTo(frame) && !force {
|
||||||
completion?(true)
|
completion?(true)
|
||||||
|
|||||||
@ -262,6 +262,9 @@ final class StarsTransactionItemNode: ListViewItemNode, ItemListItemNode {
|
|||||||
case .ads:
|
case .ads:
|
||||||
itemTitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Title
|
itemTitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Title
|
||||||
itemSubtitle = item.presentationData.strings.Stars_Intro_Transaction_TelegramAds_Subtitle
|
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:
|
case .unsupported:
|
||||||
itemTitle = item.presentationData.strings.Stars_Intro_Transaction_Unsupported_Title
|
itemTitle = item.presentationData.strings.Stars_Intro_Transaction_Unsupported_Title
|
||||||
itemSubtitle = nil
|
itemSubtitle = nil
|
||||||
|
|||||||
@ -902,8 +902,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[1401868056] = { return Api.StarsSubscription.parse_starsSubscription($0) }
|
dict[1401868056] = { return Api.StarsSubscription.parse_starsSubscription($0) }
|
||||||
dict[88173912] = { return Api.StarsSubscriptionPricing.parse_starsSubscriptionPricing($0) }
|
dict[88173912] = { return Api.StarsSubscriptionPricing.parse_starsSubscriptionPricing($0) }
|
||||||
dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($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[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) }
|
||||||
|
dict[-110658899] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAPI($0) }
|
||||||
dict[1617438738] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAds($0) }
|
dict[1617438738] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAds($0) }
|
||||||
dict[-1269320843] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAppStore($0) }
|
dict[-1269320843] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAppStore($0) }
|
||||||
dict[-382740222] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerFragment($0) }
|
dict[-382740222] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerFragment($0) }
|
||||||
|
|||||||
@ -1010,13 +1010,13 @@ public extension Api {
|
|||||||
}
|
}
|
||||||
public extension Api {
|
public extension Api {
|
||||||
enum StarsTransaction: TypeConstructorDescription {
|
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) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
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 {
|
if boxed {
|
||||||
buffer.appendInt32(178185410)
|
buffer.appendInt32(-1216644148)
|
||||||
}
|
}
|
||||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeString(id, 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 << 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 << 13) != 0 {serializeInt32(giveawayPostId!, buffer: buffer, boxed: false)}
|
||||||
if Int(flags) & Int(1 << 14) != 0 {stargift!.serialize(buffer, true)}
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
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):
|
||||||
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)])
|
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() {
|
if Int(_1!) & Int(1 << 14) != 0 {if let signature = reader.readInt32() {
|
||||||
_16 = Api.parse(reader, signature: signature) as? Api.StarGift
|
_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 _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = _3 != nil
|
||||||
@ -1106,8 +1112,10 @@ public extension Api {
|
|||||||
let _c14 = (Int(_1!) & Int(1 << 12) == 0) || _14 != nil
|
let _c14 = (Int(_1!) & Int(1 << 12) == 0) || _14 != nil
|
||||||
let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil
|
let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil
|
||||||
let _c16 = (Int(_1!) & Int(1 << 14) == 0) || _16 != 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 {
|
let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil
|
||||||
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 _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 {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
public extension Api {
|
public extension Api {
|
||||||
enum StarsTransactionPeer: TypeConstructorDescription {
|
enum StarsTransactionPeer: TypeConstructorDescription {
|
||||||
case starsTransactionPeer(peer: Api.Peer)
|
case starsTransactionPeer(peer: Api.Peer)
|
||||||
|
case starsTransactionPeerAPI
|
||||||
case starsTransactionPeerAds
|
case starsTransactionPeerAds
|
||||||
case starsTransactionPeerAppStore
|
case starsTransactionPeerAppStore
|
||||||
case starsTransactionPeerFragment
|
case starsTransactionPeerFragment
|
||||||
@ -15,6 +16,12 @@ public extension Api {
|
|||||||
buffer.appendInt32(-670195363)
|
buffer.appendInt32(-670195363)
|
||||||
}
|
}
|
||||||
peer.serialize(buffer, true)
|
peer.serialize(buffer, true)
|
||||||
|
break
|
||||||
|
case .starsTransactionPeerAPI:
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-110658899)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
case .starsTransactionPeerAds:
|
case .starsTransactionPeerAds:
|
||||||
if boxed {
|
if boxed {
|
||||||
@ -59,6 +66,8 @@ public extension Api {
|
|||||||
switch self {
|
switch self {
|
||||||
case .starsTransactionPeer(let peer):
|
case .starsTransactionPeer(let peer):
|
||||||
return ("starsTransactionPeer", [("peer", peer as Any)])
|
return ("starsTransactionPeer", [("peer", peer as Any)])
|
||||||
|
case .starsTransactionPeerAPI:
|
||||||
|
return ("starsTransactionPeerAPI", [])
|
||||||
case .starsTransactionPeerAds:
|
case .starsTransactionPeerAds:
|
||||||
return ("starsTransactionPeerAds", [])
|
return ("starsTransactionPeerAds", [])
|
||||||
case .starsTransactionPeerAppStore:
|
case .starsTransactionPeerAppStore:
|
||||||
@ -87,6 +96,9 @@ public extension Api {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public static func parse_starsTransactionPeerAPI(_ reader: BufferReader) -> StarsTransactionPeer? {
|
||||||
|
return Api.StarsTransactionPeer.starsTransactionPeerAPI
|
||||||
|
}
|
||||||
public static func parse_starsTransactionPeerAds(_ reader: BufferReader) -> StarsTransactionPeer? {
|
public static func parse_starsTransactionPeerAds(_ reader: BufferReader) -> StarsTransactionPeer? {
|
||||||
return Api.StarsTransactionPeer.starsTransactionPeerAds
|
return Api.StarsTransactionPeer.starsTransactionPeerAds
|
||||||
}
|
}
|
||||||
|
|||||||
@ -490,7 +490,10 @@ private final class StarsContextImpl {
|
|||||||
private extension StarsContext.State.Transaction {
|
private extension StarsContext.State.Transaction {
|
||||||
init?(apiTransaction: Api.StarsTransaction, peerId: EnginePeer.Id?, transaction: Transaction) {
|
init?(apiTransaction: Api.StarsTransaction, peerId: EnginePeer.Id?, transaction: Transaction) {
|
||||||
switch apiTransaction {
|
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
|
let parsedPeer: StarsContext.State.Transaction.Peer
|
||||||
var paidMessageId: MessageId?
|
var paidMessageId: MessageId?
|
||||||
var giveawayMessageId: MessageId?
|
var giveawayMessageId: MessageId?
|
||||||
@ -506,6 +509,8 @@ private extension StarsContext.State.Transaction {
|
|||||||
parsedPeer = .premiumBot
|
parsedPeer = .premiumBot
|
||||||
case .starsTransactionPeerAds:
|
case .starsTransactionPeerAds:
|
||||||
parsedPeer = .ads
|
parsedPeer = .ads
|
||||||
|
case .starsTransactionPeerAPI:
|
||||||
|
parsedPeer = .apiLimitExtension
|
||||||
case .starsTransactionPeerUnsupported:
|
case .starsTransactionPeerUnsupported:
|
||||||
parsedPeer = .unsupported
|
parsedPeer = .unsupported
|
||||||
case let .starsTransactionPeer(apiPeer):
|
case let .starsTransactionPeer(apiPeer):
|
||||||
@ -595,6 +600,7 @@ public final class StarsContext {
|
|||||||
case fragment
|
case fragment
|
||||||
case premiumBot
|
case premiumBot
|
||||||
case ads
|
case ads
|
||||||
|
case apiLimitExtension
|
||||||
case unsupported
|
case unsupported
|
||||||
case peer(EnginePeer)
|
case peer(EnginePeer)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -130,10 +130,10 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, cha
|
|||||||
case .hashtag:
|
case .hashtag:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
signal = .single({ _ in return .hashtags([]) })
|
signal = .single({ _ in return .hashtags([], query) })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
signal = .single({ _ in return .hashtags([]) })
|
signal = .single({ _ in return .hashtags([], query) })
|
||||||
}
|
}
|
||||||
|
|
||||||
let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.messages.recentlyUsedHashtags()
|
let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.messages.recentlyUsedHashtags()
|
||||||
@ -145,7 +145,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, cha
|
|||||||
result.append(hashtag)
|
result.append(hashtag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { _ in return .hashtags(result) }
|
return { _ in return .hashtags(result, query) }
|
||||||
}
|
}
|
||||||
|> castError(ChatContextQueryError.self)
|
|> castError(ChatContextQueryError.self)
|
||||||
|
|
||||||
|
|||||||
@ -274,7 +274,7 @@ public final class StarsAvatarComponent: Component {
|
|||||||
self.iconView.isHidden = false
|
self.iconView.isHidden = false
|
||||||
self.avatarNode.isHidden = true
|
self.avatarNode.isHidden = true
|
||||||
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
|
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
|
||||||
case .unsupported:
|
case .unsupported, .apiLimitExtension:
|
||||||
iconInset = 7.0
|
iconInset = 7.0
|
||||||
self.backgroundView.image = generateGradientFilledCircleImage(
|
self.backgroundView.image = generateGradientFilledCircleImage(
|
||||||
diameter: size.width,
|
diameter: size.width,
|
||||||
|
|||||||
@ -788,7 +788,7 @@ public final class StarsImageComponent: Component {
|
|||||||
direction: .mirroredDiagonal
|
direction: .mirroredDiagonal
|
||||||
)
|
)
|
||||||
iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
|
iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: .white)
|
||||||
case .peer, .unsupported:
|
case .peer, .unsupported, .apiLimitExtension:
|
||||||
iconInset = 15.0
|
iconInset = 15.0
|
||||||
iconBackgroundView.image = generateGradientFilledCircleImage(
|
iconBackgroundView.image = generateGradientFilledCircleImage(
|
||||||
diameter: imageSize.width,
|
diameter: imageSize.width,
|
||||||
|
|||||||
@ -410,6 +410,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
|||||||
case .ads:
|
case .ads:
|
||||||
titleText = strings.Stars_Transaction_TelegramAds_Title
|
titleText = strings.Stars_Transaction_TelegramAds_Title
|
||||||
via = strings.Stars_Transaction_TelegramAds_Subtitle
|
via = strings.Stars_Transaction_TelegramAds_Subtitle
|
||||||
|
case .apiLimitExtension:
|
||||||
|
titleText = strings.Stars_Transaction_TelegramBotApi_Title
|
||||||
|
via = strings.Stars_Transaction_TelegramBotApi_Subtitle
|
||||||
case .unsupported:
|
case .unsupported:
|
||||||
titleText = strings.Stars_Transaction_Unsupported_Title
|
titleText = strings.Stars_Transaction_Unsupported_Title
|
||||||
}
|
}
|
||||||
|
|||||||
@ -344,6 +344,9 @@ final class StarsTransactionsListPanelComponent: Component {
|
|||||||
case .ads:
|
case .ads:
|
||||||
itemTitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Title
|
itemTitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Title
|
||||||
itemSubtitle = environment.strings.Stars_Intro_Transaction_TelegramAds_Subtitle
|
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:
|
case .unsupported:
|
||||||
itemTitle = environment.strings.Stars_Intro_Transaction_Unsupported_Title
|
itemTitle = environment.strings.Stars_Intro_Transaction_Unsupported_Title
|
||||||
itemSubtitle = nil
|
itemSubtitle = nil
|
||||||
|
|||||||
12
submodules/TelegramUI/Images.xcassets/Chat/Hashtag/SuggestHashtag.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Hashtag/SuggestHashtag.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "tagrecent.pdf",
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Hashtag/SuggestHashtag.imageset/tagrecent.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Hashtag/SuggestHashtag.imageset/tagrecent.pdf
vendored
Normal file
Binary file not shown.
@ -10,7 +10,7 @@ private func inputQueryResultPriority(_ result: ChatPresentationInputQueryResult
|
|||||||
switch result {
|
switch result {
|
||||||
case let .stickers(items):
|
case let .stickers(items):
|
||||||
return (0, !items.isEmpty)
|
return (0, !items.isEmpty)
|
||||||
case let .hashtags(items):
|
case let .hashtags(items, _):
|
||||||
return (1, !items.isEmpty)
|
return (1, !items.isEmpty)
|
||||||
case let .mentions(items):
|
case let .mentions(items):
|
||||||
return (2, !items.isEmpty)
|
return (2, !items.isEmpty)
|
||||||
@ -98,15 +98,19 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
|
|||||||
return panel
|
return panel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .hashtags(results):
|
case let .hashtags(results, query):
|
||||||
if !results.isEmpty {
|
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 {
|
if let currentPanel = currentPanel as? HashtagChatInputContextPanelNode {
|
||||||
currentPanel.updateResults(results)
|
currentPanel.updateResults(results, query: query, peer: peer)
|
||||||
return currentPanel
|
return currentPanel
|
||||||
} else {
|
} else {
|
||||||
let panel = HashtagChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
|
let panel = HashtagChatInputContextPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, chatPresentationContext: controllerInteraction.presentationContext)
|
||||||
panel.interfaceInteraction = interfaceInteraction
|
panel.interfaceInteraction = interfaceInteraction
|
||||||
panel.updateResults(results)
|
panel.updateResults(results, query: query, peer: peer)
|
||||||
return panel
|
return panel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -114,10 +114,10 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
|||||||
case .hashtag:
|
case .hashtag:
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
signal = .single({ _ in return .hashtags([]) })
|
signal = .single({ _ in return .hashtags([], query) })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
signal = .single({ _ in return .hashtags([]) })
|
signal = .single({ _ in return .hashtags([], query) })
|
||||||
}
|
}
|
||||||
|
|
||||||
let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.messages.recentlyUsedHashtags()
|
let hashtags: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, ChatContextQueryError> = context.engine.messages.recentlyUsedHashtags()
|
||||||
@ -129,7 +129,7 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
|
|||||||
result.append(hashtag)
|
result.append(hashtag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { _ in return .hashtags(result) }
|
return { _ in return .hashtags(result, query) }
|
||||||
}
|
}
|
||||||
|> castError(ChatContextQueryError.self)
|
|> castError(ChatContextQueryError.self)
|
||||||
|
|
||||||
|
|||||||
@ -16,33 +16,38 @@ import ChatContextQuery
|
|||||||
import ChatInputContextPanelNode
|
import ChatInputContextPanelNode
|
||||||
|
|
||||||
private struct HashtagChatInputContextPanelEntryStableId: Hashable {
|
private struct HashtagChatInputContextPanelEntryStableId: Hashable {
|
||||||
let text: String
|
let title: String
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable {
|
private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable {
|
||||||
let index: Int
|
let index: Int
|
||||||
let theme: PresentationTheme
|
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 revealed: Bool
|
||||||
|
let isAdditionalRecent: Bool
|
||||||
|
|
||||||
var stableId: HashtagChatInputContextPanelEntryStableId {
|
var stableId: HashtagChatInputContextPanelEntryStableId {
|
||||||
return HashtagChatInputContextPanelEntryStableId(text: self.text)
|
return HashtagChatInputContextPanelEntryStableId(title: self.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withUpdatedTheme(_ theme: PresentationTheme) -> HashtagChatInputContextPanelEntry {
|
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 {
|
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 {
|
static func <(lhs: HashtagChatInputContextPanelEntry, rhs: HashtagChatInputContextPanelEntry) -> Bool {
|
||||||
return lhs.index < rhs.index
|
return lhs.index < rhs.index
|
||||||
}
|
}
|
||||||
|
|
||||||
func item(account: Account, presentationData: PresentationData, setHashtagRevealed: @escaping (String?) -> Void, hashtagSelected: @escaping (String) -> Void, removeRequested: @escaping (String) -> Void) -> ListViewItem {
|
func item(context: AccountContext, 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)
|
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]
|
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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
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 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(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(context: context, presentationData: presentationData, setHashtagRevealed: setHashtagRevealed, hashtagSelected: hashtagSelected, removeRequested: removeRequested), directionHint: nil) }
|
||||||
|
|
||||||
return HashtagChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
|
return HashtagChatInputContextPanelTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
}
|
}
|
||||||
@ -67,6 +72,8 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
private var currentEntries: [HashtagChatInputContextPanelEntry]?
|
private var currentEntries: [HashtagChatInputContextPanelEntry]?
|
||||||
|
|
||||||
private var currentResults: [String] = []
|
private var currentResults: [String] = []
|
||||||
|
private var currentQuery: String = ""
|
||||||
|
private var currentPeer: EnginePeer?
|
||||||
private var revealedHashtag: String?
|
private var revealedHashtag: String?
|
||||||
|
|
||||||
private var enqueuedTransitions: [(HashtagChatInputContextPanelTransition, Bool)] = []
|
private var enqueuedTransitions: [(HashtagChatInputContextPanelTransition, Bool)] = []
|
||||||
@ -91,14 +98,72 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
self.addSubnode(self.listView)
|
self.addSubnode(self.listView)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateResults(_ results: [String]) {
|
func updateResults(_ results: [String], query: String, peer: EnginePeer?) {
|
||||||
self.currentResults = results
|
self.currentResults = results
|
||||||
|
self.currentQuery = query
|
||||||
|
self.currentPeer = peer
|
||||||
|
|
||||||
var entries: [HashtagChatInputContextPanelEntry] = []
|
var entries: [HashtagChatInputContextPanelEntry] = []
|
||||||
var index = 0
|
var index = 0
|
||||||
var stableIds = Set<HashtagChatInputContextPanelEntryStableId>()
|
var stableIds = Set<HashtagChatInputContextPanelEntryStableId>()
|
||||||
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) {
|
if stableIds.contains(entry.stableId) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -112,10 +177,10 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
|
private func prepareTransition(from: [HashtagChatInputContextPanelEntry]? , to: [HashtagChatInputContextPanelEntry]) {
|
||||||
let firstTime = from == nil
|
let firstTime = from == nil
|
||||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
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 {
|
if let strongSelf = self {
|
||||||
strongSelf.revealedHashtag = text
|
strongSelf.revealedHashtag = text
|
||||||
strongSelf.updateResults(strongSelf.currentResults)
|
strongSelf.updateResults(strongSelf.currentResults, query: strongSelf.currentQuery, peer: strongSelf.currentPeer)
|
||||||
}
|
}
|
||||||
}, hashtagSelected: { [weak self] text in
|
}, hashtagSelected: { [weak self] text in
|
||||||
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
||||||
@ -131,8 +196,7 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
if let range = hashtagQueryRange {
|
if let range = hashtagQueryRange {
|
||||||
let inputText = NSMutableAttributedString(attributedString: textInputState.inputText)
|
let inputText = NSMutableAttributedString(attributedString: textInputState.inputText)
|
||||||
|
|
||||||
let replacementText = text + " "
|
let replacementText = text
|
||||||
|
|
||||||
inputText.replaceCharacters(in: range, with: replacementText)
|
inputText.replaceCharacters(in: range, with: replacementText)
|
||||||
|
|
||||||
let selectionPosition = range.lowerBound + (replacementText as NSString).length
|
let selectionPosition = range.lowerBound + (replacementText as NSString).length
|
||||||
@ -172,7 +236,11 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
|||||||
//options.insert(.LowLatency)
|
//options.insert(.LowLatency)
|
||||||
} else {
|
} else {
|
||||||
options.insert(.AnimateTopItemPosition)
|
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()
|
var insets = UIEdgeInsets()
|
||||||
|
|||||||
@ -8,21 +8,35 @@ import Postbox
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import ItemListUI
|
import ItemListUI
|
||||||
|
import AvatarNode
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
final class HashtagChatInputPanelItem: ListViewItem {
|
final class HashtagChatInputPanelItem: ListViewItem {
|
||||||
|
fileprivate let context: AccountContext
|
||||||
fileprivate let presentationData: ItemListPresentationData
|
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 revealed: Bool
|
||||||
|
fileprivate let isAdditionalRecent: Bool
|
||||||
fileprivate let setHashtagRevealed: (String?) -> Void
|
fileprivate let setHashtagRevealed: (String?) -> Void
|
||||||
private let hashtagSelected: (String) -> Void
|
private let hashtagSelected: (String) -> Void
|
||||||
fileprivate let removeRequested: (String) -> Void
|
fileprivate let removeRequested: (String) -> Void
|
||||||
|
|
||||||
let selectable: Bool = true
|
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.presentationData = presentationData
|
||||||
|
self.peer = peer
|
||||||
|
self.title = title
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.badge = badge
|
||||||
|
self.hashtag = hashtag
|
||||||
self.revealed = revealed
|
self.revealed = revealed
|
||||||
|
self.isAdditionalRecent = isAdditionalRecent
|
||||||
self.setHashtagRevealed = setHashtagRevealed
|
self.setHashtagRevealed = setHashtagRevealed
|
||||||
self.hashtagSelected = hashtagSelected
|
self.hashtagSelected = hashtagSelected
|
||||||
self.removeRequested = removeRequested
|
self.removeRequested = removeRequested
|
||||||
@ -79,14 +93,29 @@ final class HashtagChatInputPanelItem: ListViewItem {
|
|||||||
if self.revealed {
|
if self.revealed {
|
||||||
self.setHashtagRevealed(nil)
|
self.setHashtagRevealed(nil)
|
||||||
} else {
|
} 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 {
|
final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
||||||
static let itemHeight: CGFloat = 42.0
|
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 textNode: TextNode
|
||||||
|
private let badgeNode: TextNode
|
||||||
private let topSeparatorNode: ASDisplayNode
|
private let topSeparatorNode: ASDisplayNode
|
||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
@ -105,7 +134,12 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
|||||||
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
private var validLayout: (CGSize, CGFloat, CGFloat)?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self.iconBackgroundLayer.cornerRadius = 15.0
|
||||||
|
self.badgeBackgroundLayer.cornerRadius = 4.0
|
||||||
|
|
||||||
|
self.titleNode = TextNode()
|
||||||
self.textNode = TextNode()
|
self.textNode = TextNode()
|
||||||
|
self.badgeNode = TextNode()
|
||||||
|
|
||||||
self.topSeparatorNode = ASDisplayNode()
|
self.topSeparatorNode = ASDisplayNode()
|
||||||
self.topSeparatorNode.isLayerBacked = true
|
self.topSeparatorNode.isLayerBacked = true
|
||||||
@ -120,9 +154,10 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
|||||||
self.activateAreaNode.accessibilityTraits = [.button]
|
self.activateAreaNode.accessibilityTraits = [.button]
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.topSeparatorNode)
|
self.addSubnode(self.topSeparatorNode)
|
||||||
self.addSubnode(self.separatorNode)
|
self.addSubnode(self.separatorNode)
|
||||||
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
|
|
||||||
self.addSubnode(self.activateAreaNode)
|
self.addSubnode(self.activateAreaNode)
|
||||||
@ -131,6 +166,12 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
|||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.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(_:)))
|
let recognizer = ItemListRevealOptionsGestureRecognizer(target: self, action: #selector(self.revealGesture(_:)))
|
||||||
self.recognizer = recognizer
|
self.recognizer = recognizer
|
||||||
recognizer.allowAnyDirection = false
|
recognizer.allowAnyDirection = false
|
||||||
@ -149,16 +190,24 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: HashtagChatInputPanelItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
|
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 makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||||
|
let makeBadgeLayout = TextNode.asyncLayout(self.badgeNode)
|
||||||
|
|
||||||
return { [weak self] item, params, mergedTop, mergedBottom in
|
return { [weak self] item, params, mergedTop, mergedBottom in
|
||||||
let textFont = Font.medium(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0))
|
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 baseWidth = params.width - params.leftInset - params.rightInset
|
let badgeFont = Font.medium(floor(item.presentationData.fontSize.baseDisplaySize * 10.0 / 17.0))
|
||||||
|
|
||||||
let leftInset: CGFloat = 15.0 + params.leftInset
|
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 (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 (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 (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())
|
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 {
|
if let strongSelf = self {
|
||||||
strongSelf.item = item
|
strongSelf.item = item
|
||||||
strongSelf.validLayout = (nodeLayout.contentSize, params.leftInset, params.rightInset)
|
strongSelf.validLayout = (nodeLayout.contentSize, params.leftInset, params.rightInset)
|
||||||
|
|
||||||
let revealOffset = strongSelf.revealOffset
|
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.separatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||||
strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
strongSelf.topSeparatorNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||||
strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
strongSelf.backgroundColor = item.presentationData.theme.list.plainBackgroundColor
|
||||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||||
|
|
||||||
|
let _ = titleApply()
|
||||||
let _ = textApply()
|
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.topSeparatorNode.isHidden = mergedTop
|
||||||
strongSelf.separatorNode.isHidden = !mergedBottom
|
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.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.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.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.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)])
|
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) {
|
func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
if let (_, leftInset, _) = self.validLayout {
|
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 {
|
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
|
if let item = self.item {
|
||||||
|
if let _ = item.text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,7 +455,7 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
|||||||
guard let item = self.item else {
|
guard let item = self.item else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item.removeRequested(item.text)
|
item.removeRequested(item.hashtag)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupAndAddRevealNode() {
|
private func setupAndAddRevealNode() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user