Various improvements

This commit is contained in:
Ilya Laktyushin 2024-10-25 19:09:31 +04:00
parent 2fa059477c
commit 3d03fc94c6
23 changed files with 917 additions and 113 deletions

View File

@ -11753,9 +11753,11 @@ Sorry for the inconvenience.";
"Monetization.TransactionInfo.ViewInExplorer" = "View in Blockchain Explorer"; "Monetization.TransactionInfo.ViewInExplorer" = "View in Blockchain Explorer";
"Monetization.Intro.Title" = "Earn From Your Channel"; "Monetization.Intro.Title" = "Earn From Your Channel";
"Monetization.Intro.Bot.Title" = "Earn From Your Bot";
"Monetization.Intro.Ads.Title" = "Telegram Ads"; "Monetization.Intro.Ads.Title" = "Telegram Ads";
"Monetization.Intro.Ads.Text" = "Telegram can display ads in your channel."; "Monetization.Intro.Ads.Text" = "Telegram can display ads in your channel.";
"Monetization.Intro.Bot.Ads.Text" = "Telegram can display ads in your bot.";
"Monetization.Intro.Split.Title" = "50:50 Revenue Split"; "Monetization.Intro.Split.Title" = "50:50 Revenue Split";
"Monetization.Intro.Split.Text" = "You receive 50% of the ad revenue in TON."; "Monetization.Intro.Split.Text" = "You receive 50% of the ad revenue in TON.";
@ -13000,6 +13002,9 @@ Sorry for the inconvenience.";
"Gift.View.Availability" = "Availability"; "Gift.View.Availability" = "Availability";
"Gift.View.Availability.Of" = "%1$@ of %2$@"; "Gift.View.Availability.Of" = "%1$@ of %2$@";
"Gift.View.Availability.NewOf" = "%1$@ of %2$@ left"; "Gift.View.Availability.NewOf" = "%1$@ of %2$@ left";
"Gift.View.Visibility" = "Visibility";
"Gift.View.Visibility.Visible" = "Visible on your page";
"Gift.View.Visibility.Hide" = "hide";
"Gift.View.Hide" = "Hide from My Page"; "Gift.View.Hide" = "Hide from My Page";
"Gift.View.Display" = "Display on My Page"; "Gift.View.Display" = "Display on My Page";
"Gift.View.Convert" = "Convert to %@"; "Gift.View.Convert" = "Convert to %@";
@ -13141,3 +13146,6 @@ Sorry for the inconvenience.";
"Stars.Transaction.TelegramBotApi.Title" = "Paid Limit Extension"; "Stars.Transaction.TelegramBotApi.Title" = "Paid Limit Extension";
"Stars.Transaction.TelegramBotApi.Subtitle" = "Bot API"; "Stars.Transaction.TelegramBotApi.Subtitle" = "Bot API";
"Monetization.Bot.Header" = "Telegram shares 50% of the revenue from ads displayed in your bot. [Learn More >]()";
"Monetization.Bot.BalanceTitle" = "AVAILABLE BALANCE";

View File

@ -1559,8 +1559,13 @@ private func monetizationEntries(
) -> [StatsEntry] { ) -> [StatsEntry] {
var entries: [StatsEntry] = [] var entries: [StatsEntry] = []
var isBot = false
if case let .user(user) = peer, let _ = user.botInfo {
isBot = true
}
if canViewRevenue { if canViewRevenue {
entries.append(.adsHeader(presentationData.theme, presentationData.strings.Monetization_Header)) entries.append(.adsHeader(presentationData.theme, isBot ? presentationData.strings.Monetization_Bot_Header : presentationData.strings.Monetization_Header))
if !data.topHoursGraph.isEmpty { if !data.topHoursGraph.isEmpty {
entries.append(.adsImpressionsTitle(presentationData.theme, presentationData.strings.Monetization_ImpressionsTitle)) entries.append(.adsImpressionsTitle(presentationData.theme, presentationData.strings.Monetization_ImpressionsTitle))
@ -1602,7 +1607,7 @@ private func monetizationEntries(
} }
if canViewRevenue { if canViewRevenue {
entries.append(.adsTonBalanceTitle(presentationData.theme, presentationData.strings.Monetization_TonBalanceTitle)) entries.append(.adsTonBalanceTitle(presentationData.theme, isBot ? presentationData.strings.Monetization_Bot_BalanceTitle : presentationData.strings.Monetization_TonBalanceTitle))
entries.append(.adsTonBalance(presentationData.theme, data, isCreator && data.balances.availableBalance > 0, data.balances.withdrawEnabled)) entries.append(.adsTonBalance(presentationData.theme, data, isCreator && data.balances.availableBalance > 0, data.balances.withdrawEnabled))
if isCreator { if isCreator {
@ -1644,7 +1649,7 @@ private func monetizationEntries(
if displayTonTransactions { if displayTonTransactions {
if !addedTransactionsTabs { if !addedTransactionsTabs {
entries.append(.adsTransactionsTitle(presentationData.theme, presentationData.strings.Monetization_TonTransactions.uppercased())) entries.append(.adsTransactionsTitle(presentationData.theme, isBot ? presentationData.strings.Monetization_TransactionsTitle.uppercased() : presentationData.strings.Monetization_TonTransactions.uppercased()))
} }
var transactions = transactionsInfo.transactions var transactions = transactionsInfo.transactions
@ -1788,7 +1793,15 @@ private func channelStatsControllerEntries(
return [] return []
} }
public func channelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: PeerId, section: ChannelStatsSection = .stats, boostStatus: ChannelBoostStatus? = nil, boostStatusUpdated: ((ChannelBoostStatus) -> Void)? = nil) -> ViewController { public func channelStatsController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
peerId: PeerId,
section: ChannelStatsSection = .stats,
existingRevenueContext: RevenueStatsContext? = nil,
boostStatus: ChannelBoostStatus? = nil,
boostStatusUpdated: ((ChannelBoostStatus) -> Void)? = nil
) -> ViewController {
let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false, starsSelected: false, transactionsExpanded: false, moreTransactionsDisplayed: 0), ignoreRepeated: true) let statePromise = ValuePromise(ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false, starsSelected: false, transactionsExpanded: false, moreTransactionsDisplayed: 0), ignoreRepeated: true)
let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false, starsSelected: false, transactionsExpanded: false, moreTransactionsDisplayed: 0)) let stateValue = Atomic(value: ChannelStatsControllerState(section: section, boostersExpanded: false, moreBoostersDisplayed: 0, giftsSelected: false, starsSelected: false, transactionsExpanded: false, moreTransactionsDisplayed: 0))
let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in let updateState: ((ChannelStatsControllerState) -> ChannelStatsControllerState) -> Void = { f in
@ -1845,7 +1858,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false) let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false)
let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true) let giftsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: true)
let revenueContext = RevenueStatsContext(account: context.account, peerId: peerId) let revenueContext = existingRevenueContext ?? RevenueStatsContext(account: context.account, peerId: peerId)
let revenueState = Promise<RevenueStatsContextState?>() let revenueState = Promise<RevenueStatsContextState?>()
revenueState.set(.single(nil) |> then(revenueContext.state |> map(Optional.init))) revenueState.set(.single(nil) |> then(revenueContext.state |> map(Optional.init)))
@ -2013,7 +2026,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
buyAdsImpl?() buyAdsImpl?()
}, },
openMonetizationIntro: { openMonetizationIntro: {
let controller = MonetizationIntroScreen(context: context, openMore: {}) let controller = MonetizationIntroScreen(context: context, mode: existingRevenueContext != nil ? .bot : .channel, openMore: {})
pushImpl?(controller) pushImpl?(controller)
}, },
openMonetizationInfo: { openMonetizationInfo: {
@ -2112,7 +2125,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
) )
|> deliverOnMainQueue |> deliverOnMainQueue
|> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, revenueState, revenueTransactions, starsState, starsTransactions, peerData, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in |> map { presentationData, state, peer, data, messageView, stories, boostData, boostersState, giftsState, revenueState, revenueTransactions, starsState, starsTransactions, peerData, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
let (canViewStats, adsRestricted, canViewRevenue, canViewStarsRevenue) = peerData let (canViewStats, adsRestricted, _, canViewStarsRevenue) = peerData
var canViewRevenue = peerData.2
let _ = canViewStatsValue.swap(canViewStats) let _ = canViewStatsValue.swap(canViewStats)
@ -2167,7 +2181,11 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
var headerItem: BoostHeaderItem? var headerItem: BoostHeaderItem?
var leftNavigationButton: ItemListNavigationButton? var leftNavigationButton: ItemListNavigationButton?
var boostsOnly = false var boostsOnly = false
if section == .boosts { if existingRevenueContext != nil {
//TODO:localize
title = .text("Toncoin Balance")
canViewRevenue = true
} else if section == .boosts {
title = .text("") title = .text("")
let headerTitle = isGroup ? presentationData.strings.GroupBoost_Title : presentationData.strings.ChannelBoost_Title let headerTitle = isGroup ? presentationData.strings.GroupBoost_Title : presentationData.strings.ChannelBoost_Title

View File

@ -20,15 +20,18 @@ private final class SheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext let context: AccountContext
let mode: MonetizationIntroScreen.Mode
let openMore: () -> Void let openMore: () -> Void
let dismiss: () -> Void let dismiss: () -> Void
init( init(
context: AccountContext, context: AccountContext,
mode: MonetizationIntroScreen.Mode,
openMore: @escaping () -> Void, openMore: @escaping () -> Void,
dismiss: @escaping () -> Void dismiss: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.mode = mode
self.openMore = openMore self.openMore = openMore
self.dismiss = dismiss self.dismiss = dismiss
} }
@ -37,6 +40,9 @@ private final class SheetContent: CombinedComponent {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.mode != rhs.mode {
return false
}
return true return true
} }
@ -136,7 +142,7 @@ private final class SheetContent: CombinedComponent {
let title = title.update( let title = title.update(
component: BalancedTextComponent( component: BalancedTextComponent(
text: .plain(NSAttributedString(string: strings.Monetization_Intro_Title, font: titleFont, textColor: textColor)), text: .plain(NSAttributedString(string: component.mode == .bot ? strings.Monetization_Intro_Bot_Title : strings.Monetization_Intro_Title, font: titleFont, textColor: textColor)),
horizontalAlignment: .center, horizontalAlignment: .center,
maximumNumberOfLines: 0, maximumNumberOfLines: 0,
lineSpacing: 0.1 lineSpacing: 0.1
@ -157,7 +163,7 @@ private final class SheetContent: CombinedComponent {
component: AnyComponent(ParagraphComponent( component: AnyComponent(ParagraphComponent(
title: strings.Monetization_Intro_Ads_Title, title: strings.Monetization_Intro_Ads_Title,
titleColor: textColor, titleColor: textColor,
text: strings.Monetization_Intro_Ads_Text, text: component.mode == .bot ? strings.Monetization_Intro_Bot_Ads_Text : strings.Monetization_Intro_Ads_Text,
textColor: secondaryTextColor, textColor: secondaryTextColor,
iconName: "Ads/Ads", iconName: "Ads/Ads",
iconColor: linkColor iconColor: linkColor
@ -343,13 +349,16 @@ private final class SheetContainerComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext let context: AccountContext
let mode: MonetizationIntroScreen.Mode
let openMore: () -> Void let openMore: () -> Void
init( init(
context: AccountContext, context: AccountContext,
mode: MonetizationIntroScreen.Mode,
openMore: @escaping () -> Void openMore: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.mode = mode
self.openMore = openMore self.openMore = openMore
} }
@ -357,6 +366,9 @@ private final class SheetContainerComponent: CombinedComponent {
if lhs.context !== rhs.context { if lhs.context !== rhs.context {
return false return false
} }
if lhs.mode != rhs.mode {
return false
}
return true return true
} }
@ -375,6 +387,7 @@ private final class SheetContainerComponent: CombinedComponent {
component: SheetComponent<EnvironmentType>( component: SheetComponent<EnvironmentType>(
content: AnyComponent<EnvironmentType>(SheetContent( content: AnyComponent<EnvironmentType>(SheetContent(
context: context.component.context, context: context.component.context,
mode: context.component.mode,
openMore: context.component.openMore, openMore: context.component.openMore,
dismiss: { dismiss: {
animateOut.invoke(Action { _ in animateOut.invoke(Action { _ in
@ -444,9 +457,15 @@ private final class SheetContainerComponent: CombinedComponent {
final class MonetizationIntroScreen: ViewControllerComponentContainer { final class MonetizationIntroScreen: ViewControllerComponentContainer {
private let context: AccountContext private let context: AccountContext
private var openMore: (() -> Void)? private var openMore: (() -> Void)?
enum Mode: Equatable {
case channel
case bot
}
init( init(
context: AccountContext, context: AccountContext,
mode: Mode,
openMore: @escaping () -> Void openMore: @escaping () -> Void
) { ) {
self.context = context self.context = context
@ -456,6 +475,7 @@ final class MonetizationIntroScreen: ViewControllerComponentContainer {
context: context, context: context,
component: SheetContainerComponent( component: SheetContainerComponent(
context: context, context: context,
mode: mode,
openMore: openMore openMore: openMore
), ),
navigationBarAppearance: .none, navigationBarAppearance: .none,

View File

@ -1116,7 +1116,9 @@ public extension TelegramEngine.EngineData.Item {
guard let view = view as? CachedPeerDataView else { guard let view = view as? CachedPeerDataView else {
preconditionFailure() preconditionFailure()
} }
if let cachedData = view.cachedPeerData as? CachedChannelData { if let cachedData = view.cachedPeerData as? CachedUserData {
return cachedData.flags.contains(.canViewRevenue)
} else if let cachedData = view.cachedPeerData as? CachedChannelData {
return cachedData.flags.contains(.canViewRevenue) return cachedData.flags.contains(.canViewRevenue)
} else { } else {
return false return false

View File

@ -111,6 +111,24 @@ public struct PresentationResourcesSettings {
drawBorder(context: context, rect: bounds) drawBorder(context: context, rect: bounds)
}) })
public static let ton = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
let path = UIBezierPath(roundedRect: bounds, cornerRadius: 7.0)
context.addPath(path.cgPath)
context.clip()
context.setFillColor(UIColor(rgb: 0x32ade6).cgColor)
context.fill(bounds)
if let image = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonAbout"), color: UIColor(rgb: 0xffffff)), let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((bounds.width - image.size.width) / 2.0), y: floorToScreenPixels((bounds.height - image.size.height) / 2.0)), size: image.size))
}
drawBorder(context: context, rect: bounds)
})
public static let stars = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in public static let stars = generateImage(CGSize(width: 29.0, height: 29.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds) context.clear(bounds)

View File

@ -46,11 +46,6 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate
break break
} }
} }
if value < 0 {
balanceText.insert("-", at: balanceText.startIndex)
} else if showPlus {
balanceText.insert("+", at: balanceText.startIndex)
}
if let dotIndex = balanceText.range(of: dateTimeFormat.decimalSeparator) { if let dotIndex = balanceText.range(of: dateTimeFormat.decimalSeparator) {
balanceText = String(balanceText[balanceText.startIndex ..< min(balanceText.endIndex, balanceText.index(dotIndex.upperBound, offsetBy: 2))]) balanceText = String(balanceText[balanceText.startIndex ..< min(balanceText.endIndex, balanceText.index(dotIndex.upperBound, offsetBy: 2))])
@ -59,12 +54,22 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate
if let integerPart = Int32(integerPartString) { if let integerPart = Int32(integerPartString) {
let modifiedIntegerPart = presentationStringsFormattedNumber(integerPart, dateTimeFormat.groupingSeparator) let modifiedIntegerPart = presentationStringsFormattedNumber(integerPart, dateTimeFormat.groupingSeparator)
let resultString = "\(modifiedIntegerPart)\(balanceText[dotIndex.lowerBound...])" var resultString = "\(modifiedIntegerPart)\(balanceText[dotIndex.lowerBound...])"
if value < 0 {
resultString.insert("-", at: resultString.startIndex)
} else if showPlus {
resultString.insert("+", at: resultString.startIndex)
}
return resultString return resultString
} }
} else if let integerPart = Int32(balanceText) { } else if let integerPart = Int32(balanceText) {
balanceText = presentationStringsFormattedNumber(integerPart, dateTimeFormat.groupingSeparator) balanceText = presentationStringsFormattedNumber(integerPart, dateTimeFormat.groupingSeparator)
} }
if value < 0 {
balanceText.insert("-", at: balanceText.startIndex)
} else if showPlus {
balanceText.insert("+", at: balanceText.startIndex)
}
return balanceText return balanceText
} }

View File

@ -24,7 +24,7 @@ public final class GiftItemComponent: Component {
case red case red
case blue case blue
var colors: [UIColor] { func colors(theme: PresentationTheme) -> [UIColor] {
switch self { switch self {
case .red: case .red:
return [ return [
@ -33,10 +33,17 @@ public final class GiftItemComponent: Component {
] ]
case .blue: case .blue:
return [ if theme.overallDarkAppearance {
UIColor(rgb: 0x34a4fc), return [
UIColor(rgb: 0x6fd3ff) UIColor(rgb: 0x025799),
] UIColor(rgb: 0x29a8e2)
]
} else {
return [
UIColor(rgb: 0x34a4fc),
UIColor(rgb: 0x6fd3ff)
]
}
} }
} }
} }
@ -166,10 +173,15 @@ public final class GiftItemComponent: Component {
func update(component: GiftItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize { func update(component: GiftItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let isFirstTime = self.component == nil let isFirstTime = self.component == nil
let previousComponent = self.component
self.component = component self.component = component
self.componentState = state self.componentState = state
var themeUpdated = false
if previousComponent?.theme !== component.theme {
themeUpdated = true
}
let size = CGSize(width: availableSize.width, height: component.title != nil ? 178.0 : 154.0) let size = CGSize(width: availableSize.width, height: component.title != nil ? 178.0 : 154.0)
if component.isLoading { if component.isLoading {
@ -339,8 +351,8 @@ public final class GiftItemComponent: Component {
} }
ribbonTextView.bounds = CGRect(origin: .zero, size: ribbonTextSize) ribbonTextView.bounds = CGRect(origin: .zero, size: ribbonTextSize)
if self.ribbon.image == nil { if self.ribbon.image == nil || themeUpdated {
self.ribbon.image = generateGradientTintedImage(image: UIImage(bundleImageName: "Premium/GiftRibbon"), colors: ribbon.color.colors, direction: .diagonal) self.ribbon.image = generateGradientTintedImage(image: UIImage(bundleImageName: "Premium/GiftRibbon"), colors: ribbon.color.colors(theme: component.theme), direction: .diagonal)
} }
if let ribbonImage = self.ribbon.image { if let ribbonImage = self.ribbon.image {
self.ribbon.frame = CGRect(origin: CGPoint(x: size.width - ribbonImage.size.width + 2.0, y: -2.0), size: ribbonImage.size) self.ribbon.frame = CGRect(origin: CGPoint(x: size.width - ribbonImage.size.width + 2.0, y: -2.0), size: ribbonImage.size)

View File

@ -556,6 +556,35 @@ private final class GiftViewSheetContent: CombinedComponent {
) )
)) ))
} }
if savedToProfile {
tableItems.append(.init(
id: "visibility",
title: strings.Gift_View_Visibility,
component: AnyComponent(
HStack([
AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_View_Visibility_Visible, font: tableFont, textColor: tableTextColor))))
),
AnyComponentWithIdentity(
id: AnyHashable(1),
component: AnyComponent(Button(
content: AnyComponent(ButtonContentComponent(
context: component.context,
text: strings.Gift_View_Visibility_Hide,
color: theme.list.itemAccentColor
)),
action: {
component.updateSavedToProfile(false)
}
))
)
], spacing: 4.0)
),
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)
))
}
if let text { if let text {
let attributedText = stringWithAppliedEntities(text, entities: entities ?? [], baseColor: tableTextColor, linkColor: tableLinkColor, baseFont: tableFont, linkFont: tableFont, boldFont: tableBoldFont, italicFont: tableItalicFont, boldItalicFont: tableBoldItalicFont, fixedFont: tableMonospaceFont, blockQuoteFont: tableFont, message: nil) let attributedText = stringWithAppliedEntities(text, entities: entities ?? [], baseColor: tableTextColor, linkColor: tableLinkColor, baseFont: tableFont, linkFont: tableFont, boldFont: tableBoldFont, italicFont: tableItalicFont, boldItalicFont: tableBoldItalicFont, fixedFont: tableMonospaceFont, blockQuoteFont: tableFont, message: nil)
@ -637,7 +666,9 @@ private final class GiftViewSheetContent: CombinedComponent {
) )
originY += additionalText.size.height originY += additionalText.size.height
originY += 16.0 originY += 16.0
}
if incoming && !converted && !savedToProfile {
let button = button.update( let button = button.update(
component: SolidRoundedButtonComponent( component: SolidRoundedButtonComponent(
title: savedToProfile ? strings.Gift_View_Hide : strings.Gift_View_Display, title: savedToProfile ? strings.Gift_View_Hide : strings.Gift_View_Display,

View File

@ -208,7 +208,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
style: .media, style: .media,
placeholder: .plain(presentationData.strings.MediaPicker_AddCaption), placeholder: .plain(presentationData.strings.MediaPicker_AddCaption),
maxLength: Int(self.context.userLimits.maxCaptionLength), maxLength: Int(self.context.userLimits.maxCaptionLength),
queryTypes: [.mention], queryTypes: [.mention, .hashtag],
alwaysDarkWhenHasText: false, alwaysDarkWhenHasText: false,
resetInputContents: resetInputContents, resetInputContents: resetInputContents,
nextInputMode: { [weak self] _ in nextInputMode: { [weak self] _ in

View File

@ -119,7 +119,7 @@ public final class DrawingMessageRenderer {
let size = self.updateMessagesLayout(layout: layout, presentationData: mockPresentationData) let size = self.updateMessagesLayout(layout: layout, presentationData: mockPresentationData)
let _ = self.updateMessagesLayout(layout: layout, presentationData: mockPresentationData) let _ = self.updateMessagesLayout(layout: layout, presentationData: mockPresentationData)
Queue.mainQueue().after(0.2, { Queue.mainQueue().after(0.35, {
var mediaRect: CGRect? var mediaRect: CGRect?
if let messageNode = self.messageNodes?.first { if let messageNode = self.messageNodes?.first {
if self.isOverlay { if self.isOverlay {
@ -343,7 +343,7 @@ public final class DrawingMessageRenderer {
} }
public func render(completion: @escaping (Result) -> Void) { public func render(completion: @escaping (Result) -> Void) {
Queue.mainQueue().after(0.12) { Queue.mainQueue().justDispatch {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let defaultPresentationData = defaultPresentationData() let defaultPresentationData = defaultPresentationData()

View File

@ -1229,7 +1229,7 @@ final class MediaEditorScreenComponent: Component {
style: .editor, style: .editor,
placeholder: .plain(environment.strings.Story_Editor_InputPlaceholderAddCaption), placeholder: .plain(environment.strings.Story_Editor_InputPlaceholderAddCaption),
maxLength: Int(component.context.userLimits.maxStoryCaptionLength), maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
queryTypes: [.mention], queryTypes: [.mention, .hashtag],
alwaysDarkWhenHasText: false, alwaysDarkWhenHasText: false,
resetInputContents: nil, resetInputContents: nil,
nextInputMode: { _ in return nextInputMode }, nextInputMode: { _ in return nextInputMode },
@ -1363,12 +1363,12 @@ final class MediaEditorScreenComponent: Component {
header: header, header: header,
isChannel: false, isChannel: false,
storyItem: nil, storyItem: nil,
chatLocation: nil chatLocation: controller.customTarget.flatMap { .peer(id: $0) }
)), )),
environment: {}, environment: {},
containerSize: CGSize(width: inputPanelAvailableWidth, height: inputPanelAvailableHeight) containerSize: CGSize(width: inputPanelAvailableWidth, height: inputPanelAvailableHeight)
) )
if self.inputPanelExternalState.isEditing && controller.node.entitiesView.hasSelection { if self.inputPanelExternalState.isEditing && controller.node.entitiesView.hasSelection {
Queue.mainQueue().justDispatch { Queue.mainQueue().justDispatch {
controller.node.entitiesView.selectEntity(nil) controller.node.entitiesView.selectEntity(nil)

View File

@ -11,14 +11,18 @@ import PeerListItemComponent
final class ContextResultPanelComponent: Component { final class ContextResultPanelComponent: Component {
enum Results: Equatable { enum Results: Equatable {
case mentions([EnginePeer]) case mentions([EnginePeer])
case hashtags([String]) case hashtags(EnginePeer?, [String], String)
var count: Int { var count: Int {
switch self { switch self {
case let .hashtags(hashtags):
return hashtags.count
case let .mentions(peers): case let .mentions(peers):
return peers.count return peers.count
case let .hashtags(peer, hashtags, query):
var count = hashtags.count
if let _ = peer, query.count >= 4 {
count += 2
}
return count
} }
} }
} }
@ -186,38 +190,23 @@ final class ContextResultPanelComponent: Component {
let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -200.0) let visibleBounds = self.scrollView.bounds.insetBy(dx: 0.0, dy: -200.0)
// var synchronousLoad = false
// if let hint = transition.userData(PeerListItemComponent.TransitionHint.self) {
// synchronousLoad = hint.synchronousLoad
// }
var validIds: [AnyHashable] = [] var validIds: [AnyHashable] = []
if let range = itemLayout.visibleItems(for: visibleBounds), case let .mentions(peers) = component.results { if let range = itemLayout.visibleItems(for: visibleBounds) {
for index in range.lowerBound ..< range.upperBound { for index in range.lowerBound ..< range.upperBound {
guard index < peers.count else { guard index < component.results.count else {
continue continue
} }
let itemFrame = itemLayout.itemFrame(for: index) let itemFrame = itemLayout.itemFrame(for: index)
var itemTransition = transition var itemTransition = transition
let peer = peers[index] let id: AnyHashable
validIds.append(peer.id)
let visibleItem: ComponentView<Empty> let itemComponent: AnyComponent<Empty>
if let current = self.visibleItems[peer.id] { switch component.results {
visibleItem = current case let .mentions(peers):
} else { let peer = peers[index]
if !transition.animation.isImmediate { id = peer.id
itemTransition = .immediate itemComponent = AnyComponent(PeerListItemComponent(
}
visibleItem = ComponentView()
self.visibleItems[peer.id] = visibleItem
}
let _ = visibleItem.update(
transition: itemTransition,
component: AnyComponent(PeerListItemComponent(
context: component.context, context: component.context,
theme: component.theme, theme: component.theme,
strings: component.strings, strings: component.strings,
@ -236,21 +225,100 @@ final class ContextResultPanelComponent: Component {
} }
component.action(.mention(peer)) component.action(.mention(peer))
} }
)), ))
case let .hashtags(peer, hashtags, query):
var hashtagIndex = index
if let _ = peer, query.count >= 4 {
hashtagIndex -= 2
}
if let peer, let addressName = peer.addressName, hashtagIndex < 0 {
//TODO: localize
var isGroup = false
if case let .channel(channel) = peer, case .group = channel.info {
isGroup = true
}
id = hashtagIndex
if hashtagIndex == -2 {
itemComponent = AnyComponent(HashtagListItemComponent(
context: component.context,
theme: component.theme,
strings: component.strings,
peer: nil,
title: "Use #\(query)",
subtitle: "searches posts from all channels",
hashtag: query,
hasNext: index != hashtags.count - 1,
action: { [weak self] hashtag, _ in
guard let self, let component = self.component else {
return
}
component.action(.hashtag(query))
}
))
} else {
itemComponent = AnyComponent(HashtagListItemComponent(
context: component.context,
theme: component.theme,
strings: component.strings,
peer: peer,
title: "Use #\(query)@\(addressName)",
subtitle: isGroup ? "searches only posts from this group" : "searches only posts from this channel",
hashtag: "\(query)@\(addressName)",
hasNext: index != hashtags.count - 1,
action: { [weak self] hashtag, _ in
guard let self, let component = self.component else {
return
}
component.action(.hashtag("\(query)@\(addressName)"))
}
))
}
} else {
let hashtag = hashtags[hashtagIndex]
id = hashtag
itemComponent = AnyComponent(HashtagListItemComponent(
context: component.context,
theme: component.theme,
strings: component.strings,
peer: nil,
title: "#\(hashtag)",
subtitle: nil,
hashtag: hashtag,
hasNext: index != hashtags.count - 1,
action: { [weak self] hashtag, _ in
guard let self, let component = self.component else {
return
}
component.action(.hashtag(hashtag))
}
))
}
}
validIds.append(id)
let visibleItem: ComponentView<Empty>
if let current = self.visibleItems[id] {
visibleItem = current
} else {
if !transition.animation.isImmediate {
itemTransition = .immediate
}
visibleItem = ComponentView()
self.visibleItems[id] = visibleItem
}
let _ = visibleItem.update(
transition: itemTransition,
component: itemComponent,
environment: {}, environment: {},
containerSize: itemFrame.size containerSize: itemFrame.size
) )
if let itemView = visibleItem.view { if let itemView = visibleItem.view {
// var animateIn = false
if itemView.superview == nil { if itemView.superview == nil {
// animateIn = true
self.scrollView.addSubview(itemView) self.scrollView.addSubview(itemView)
} }
itemTransition.setFrame(view: itemView, frame: itemFrame) itemTransition.setFrame(view: itemView, frame: itemFrame)
// if animateIn {
// itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
// }
} }
} }
} }
@ -284,9 +352,10 @@ final class ContextResultPanelComponent: Component {
let sideInset: CGFloat = 3.0 let sideInset: CGFloat = 3.0
self.backgroundView.updateColor(color: UIColor(white: 0.0, alpha: 0.7), transition: transition.containedViewLayoutTransition) self.backgroundView.updateColor(color: UIColor(white: 0.0, alpha: 0.7), transition: transition.containedViewLayoutTransition)
let measureItemSize = self.measureItem.update( let itemComponent: AnyComponent<Empty>
transition: .immediate, switch component.results {
component: AnyComponent(PeerListItemComponent( case .mentions:
itemComponent = AnyComponent(PeerListItemComponent(
context: component.context, context: component.context,
theme: component.theme, theme: component.theme,
strings: component.strings, strings: component.strings,
@ -301,7 +370,25 @@ final class ContextResultPanelComponent: Component {
hasNext: true, hasNext: true,
action: { _, _, _ in action: { _, _, _ in
} }
)), ))
case .hashtags:
itemComponent = AnyComponent(HashtagListItemComponent(
context: component.context,
theme: component.theme,
strings: component.strings,
peer: nil,
title: "AAAAAAAAAAAA",
subtitle: nil,
hashtag: "",
hasNext: true,
action: { _, _ in
}
))
}
let measureItemSize = self.measureItem.update(
transition: .immediate,
component: itemComponent,
environment: {}, environment: {},
containerSize: CGSize(width: availableSize.width, height: 1000.0) containerSize: CGSize(width: availableSize.width, height: 1000.0)
) )

View File

@ -0,0 +1,509 @@
import Foundation
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import ComponentFlow
import SwiftSignalKit
import AccountContext
import TelegramCore
import Postbox
import MultilineTextComponent
import AvatarNode
import TelegramPresentationData
import CheckNode
import TelegramStringFormatting
import AppBundle
import PeerPresenceStatusManager
import EmojiStatusComponent
import ContextUI
import EmojiTextAttachmentView
import TextFormat
import PhotoResources
import ListSectionComponent
import ListItemSwipeOptionContainer
private let avatarFont = avatarPlaceholderFont(size: 15.0)
public final class HashtagListItemComponent: Component {
public final class TransitionHint {
public let synchronousLoad: Bool
public init(synchronousLoad: Bool) {
self.synchronousLoad = synchronousLoad
}
}
public final class InlineAction: Equatable {
public enum Color: Equatable {
case destructive
}
public let id: AnyHashable
public let title: String
public let color: Color
public let action: () -> Void
public init(id: AnyHashable, title: String, color: Color, action: @escaping () -> Void) {
self.id = id
self.title = title
self.color = color
self.action = action
}
public static func ==(lhs: InlineAction, rhs: InlineAction) -> Bool {
if lhs === rhs {
return true
}
if lhs.id != rhs.id {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.color != rhs.color {
return false
}
return true
}
}
public final class InlineActionsState: Equatable {
public let actions: [InlineAction]
public init(actions: [InlineAction]) {
self.actions = actions
}
public static func ==(lhs: InlineActionsState, rhs: InlineActionsState) -> Bool {
if lhs === rhs {
return true
}
if lhs.actions != rhs.actions {
return false
}
return true
}
}
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let peer: EnginePeer?
let title: String
let subtitle: String?
let hashtag: String
let hasNext: Bool
let action: (String, HashtagListItemComponent.View) -> Void
let contextAction: ((String, ContextExtractedContentContainingView, ContextGesture) -> Void)?
let inlineActions: InlineActionsState?
public init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
peer: EnginePeer?,
title: String,
subtitle: String?,
hashtag: String,
hasNext: Bool,
action: @escaping (String, HashtagListItemComponent.View) -> Void,
contextAction: ((String, ContextExtractedContentContainingView, ContextGesture) -> Void)? = nil,
inlineActions: InlineActionsState? = nil
) {
self.context = context
self.theme = theme
self.strings = strings
self.peer = peer
self.title = title
self.subtitle = subtitle
self.hashtag = hashtag
self.hasNext = hasNext
self.action = action
self.contextAction = contextAction
self.inlineActions = inlineActions
}
public static func ==(lhs: HashtagListItemComponent, rhs: HashtagListItemComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.subtitle != rhs.subtitle {
return false
}
if lhs.hashtag != rhs.hashtag {
return false
}
if lhs.hasNext != rhs.hasNext {
return false
}
if lhs.inlineActions != rhs.inlineActions {
return false
}
return true
}
public final class View: ContextControllerSourceView, ListSectionComponent.ChildView {
public let extractedContainerView: ContextExtractedContentContainingView
private let containerButton: HighlightTrackingButton
private let swipeOptionContainer: ListItemSwipeOptionContainer
private let iconBackgroundLayer = SimpleLayer()
private let iconLayer = SimpleLayer()
private let title = ComponentView<Empty>()
private var label = ComponentView<Empty>()
private let separatorLayer: SimpleLayer
private var avatarNode: AvatarNode?
private let badgeBackgroundLayer = SimpleLayer()
private var component: HashtagListItemComponent?
private weak var state: EmptyComponentState?
public var avatarFrame: CGRect {
if let avatarNode = self.avatarNode {
return avatarNode.frame
} else {
return CGRect(origin: CGPoint(), size: CGSize())
}
}
public var titleFrame: CGRect? {
return self.title.view?.frame
}
public var labelFrame: CGRect? {
guard let value = self.label.view?.frame else {
return nil
}
return value
}
private var isExtractedToContextMenu: Bool = false
public var customUpdateIsHighlighted: ((Bool) -> Void)?
public private(set) var separatorInset: CGFloat = 0.0
override init(frame: CGRect) {
self.separatorLayer = SimpleLayer()
self.iconBackgroundLayer.cornerRadius = 15.0
self.badgeBackgroundLayer.cornerRadius = 4.0
self.extractedContainerView = ContextExtractedContentContainingView()
self.containerButton = HighlightTrackingButton()
self.containerButton.layer.anchorPoint = CGPoint()
self.containerButton.isExclusiveTouch = true
self.swipeOptionContainer = ListItemSwipeOptionContainer(frame: CGRect())
super.init(frame: frame)
self.addSubview(self.extractedContainerView)
self.targetViewForActivationProgress = self.extractedContainerView.contentView
self.extractedContainerView.contentView.addSubview(self.swipeOptionContainer)
self.swipeOptionContainer.addSubview(self.containerButton)
self.layer.addSublayer(self.separatorLayer)
self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.extractedContainerView.isExtractedToContextPreviewUpdated = { [weak self] value in
guard let self else {
return
}
self.containerButton.clipsToBounds = value
self.containerButton.backgroundColor = nil
self.containerButton.layer.cornerRadius = value ? 10.0 : 0.0
}
self.extractedContainerView.willUpdateIsExtractedToContextPreview = { [weak self] value, transition in
guard let self else {
return
}
self.isExtractedToContextMenu = value
let mappedTransition: ComponentTransition
if value {
mappedTransition = ComponentTransition(transition)
} else {
mappedTransition = ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut))
}
self.state?.updated(transition: mappedTransition)
}
self.activated = { [weak self] gesture, _ in
guard let self, let component = self.component else {
gesture.cancel()
return
}
component.contextAction?(component.hashtag, self.extractedContainerView, gesture)
}
self.containerButton.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if let customUpdateIsHighlighted = self.customUpdateIsHighlighted {
customUpdateIsHighlighted(highlighted)
}
}
self.swipeOptionContainer.updateRevealOffset = { [weak self] offset, transition in
guard let self else {
return
}
transition.setBounds(view: self.containerButton, bounds: CGRect(origin: CGPoint(x: -offset, y: 0.0), size: self.containerButton.bounds.size))
}
self.swipeOptionContainer.revealOptionSelected = { [weak self] option, _ in
guard let self, let component = self.component else {
return
}
guard let inlineActions = component.inlineActions else {
return
}
self.swipeOptionContainer.setRevealOptionsOpened(false, animated: true)
if let inlineAction = inlineActions.actions.first(where: { $0.id == option.key }) {
inlineAction.action()
}
}
self.containerButton.layer.addSublayer(self.iconBackgroundLayer)
self.iconBackgroundLayer.addSublayer(self.iconLayer)
self.containerButton.layer.addSublayer(self.badgeBackgroundLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func pressed() {
guard let component = self.component else {
return
}
component.action(component.hashtag, self)
}
func update(component: HashtagListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
var synchronousLoad = false
if let hint = transition.userData(TransitionHint.self) {
synchronousLoad = hint.synchronousLoad
}
self.isGestureEnabled = false
let themeUpdated = self.component?.theme !== component.theme
self.component = component
self.state = state
let labelData: (String, UIColor)
if let subtitle = component.subtitle {
labelData = (subtitle, component.theme.list.itemSecondaryTextColor)
} else {
labelData = ("", .clear)
}
let contextInset: CGFloat
if self.isExtractedToContextMenu {
contextInset = 12.0
} else {
contextInset = 0.0
}
let height: CGFloat = 42.0
let titleFont: UIFont = Font.semibold(14.0)
let subtitleFont: UIFont = Font.regular(14.0)
let verticalInset: CGFloat = 1.0
let leftInset: CGFloat = 55.0
let rightInset: CGFloat = 16.0
let avatarSize: CGFloat = 30.0
let avatarFrame = CGRect(origin: CGPoint(x: 12.0, y: floorToScreenPixels((height - verticalInset * 2.0 - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
if let peer = component.peer {
let avatarNode: AvatarNode
if let current = self.avatarNode {
avatarNode = current
} else {
avatarNode = AvatarNode(font: avatarFont)
avatarNode.isLayerBacked = false
avatarNode.isUserInteractionEnabled = false
self.avatarNode = avatarNode
self.containerButton.layer.insertSublayer(avatarNode.layer, at: 0)
}
if avatarNode.bounds.isEmpty {
avatarNode.frame = avatarFrame
} else {
transition.setFrame(layer: avatarNode.layer, frame: avatarFrame)
}
if peer.smallProfileImage != nil {
avatarNode.setPeerV2(
context: component.context,
theme: component.theme,
peer: peer,
authorOfMessage: nil,
overrideImage: nil,
emptyColor: nil,
clipStyle: .round,
synchronousLoad: synchronousLoad,
displayDimensions: CGSize(width: avatarSize, height: avatarSize)
)
} else {
avatarNode.setPeer(context: component.context, theme: component.theme, peer: peer, clipStyle: .round, synchronousLoad: synchronousLoad, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
}
self.iconBackgroundLayer.isHidden = true
} else {
self.iconBackgroundLayer.isHidden = false
}
let previousTitleFrame = self.title.view?.frame
let titleAvailableWidth = availableSize.width - leftInset - rightInset
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: component.title, font: titleFont, textColor: component.theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: titleAvailableWidth, height: 100.0)
)
let labelAvailableWidth = availableSize.width - leftInset - rightInset
let labelColor: UIColor = labelData.1
let labelSize = self.label.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: labelData.0, font: subtitleFont, textColor: labelColor))
)),
environment: {},
containerSize: CGSize(width: labelAvailableWidth, height: 100.0)
)
let titleVerticalOffset: CGFloat = 0.0
let centralContentHeight: CGFloat
if labelSize.height > 0.0 {
centralContentHeight = titleSize.height + labelSize.height
} else {
centralContentHeight = titleSize.height
}
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: titleVerticalOffset + floor((height - verticalInset * 2.0 - centralContentHeight) / 2.0)), size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
titleView.isUserInteractionEnabled = false
self.containerButton.addSubview(titleView)
}
titleView.frame = titleFrame
if let previousTitleFrame, previousTitleFrame.origin.x != titleFrame.origin.x {
transition.animatePosition(view: titleView, from: CGPoint(x: previousTitleFrame.origin.x - titleFrame.origin.x, y: 0.0), to: CGPoint(), additive: true)
}
}
if let labelView = self.label.view {
let labelFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY), size: labelSize)
if labelView.superview == nil {
labelView.isUserInteractionEnabled = false
labelView.layer.anchorPoint = CGPoint()
self.containerButton.addSubview(labelView)
labelView.center = labelFrame.origin
} else {
transition.setPosition(view: labelView, position: labelFrame.origin)
}
labelView.bounds = CGRect(origin: CGPoint(), size: labelFrame.size)
}
if self.iconLayer.contents == nil {
self.iconLayer.contents = UIImage(bundleImageName: "Chat/Hashtag/SuggestHashtag")?.cgImage
}
if themeUpdated {
let accentColor = UIColor(rgb: 0x007aff)
self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor
self.iconBackgroundLayer.backgroundColor = accentColor.cgColor
self.iconLayer.layerTintColor = UIColor.white.cgColor
self.badgeBackgroundLayer.backgroundColor = accentColor.cgColor
}
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel)))
self.separatorLayer.isHidden = !component.hasNext
let iconSize = CGSize(width: 30.0, height: 30.0)
self.iconBackgroundLayer.frame = CGRect(origin: CGPoint(x: 12.0, y: floor((height - 30.0) / 2.0)), size: iconSize)
self.iconLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: 30.0, height: 30.0))
let resultBounds = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: height))
transition.setFrame(view: self.extractedContainerView, frame: resultBounds)
transition.setFrame(view: self.extractedContainerView.contentView, frame: resultBounds)
self.extractedContainerView.contentRect = resultBounds
let containerFrame = CGRect(origin: CGPoint(x: contextInset, y: verticalInset), size: CGSize(width: availableSize.width - contextInset * 2.0, height: height - verticalInset * 2.0))
let swipeOptionContainerFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: height))
transition.setFrame(view: self.swipeOptionContainer, frame: swipeOptionContainerFrame)
transition.setPosition(view: self.containerButton, position: containerFrame.origin)
transition.setBounds(view: self.containerButton, bounds: CGRect(origin: self.containerButton.bounds.origin, size: containerFrame.size))
self.separatorInset = leftInset
self.swipeOptionContainer.updateLayout(size: swipeOptionContainerFrame.size, leftInset: 0.0, rightInset: 0.0)
var rightOptions: [ListItemSwipeOptionContainer.Option] = []
if let inlineActions = component.inlineActions {
rightOptions = inlineActions.actions.map { action in
let color: UIColor
let textColor: UIColor
switch action.color {
case .destructive:
color = component.theme.list.itemDisclosureActions.destructive.fillColor
textColor = component.theme.list.itemDisclosureActions.destructive.foregroundColor
}
return ListItemSwipeOptionContainer.Option(
key: action.id,
title: action.title,
icon: .none,
color: color,
textColor: textColor
)
}
}
self.swipeOptionContainer.setRevealOptions(([], rightOptions))
return CGSize(width: availableSize.width, height: height)
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -472,6 +472,7 @@ public final class MessageInputPanelComponent: Component {
private var contextQueryStates: [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)] = [:] private var contextQueryStates: [ChatPresentationInputQueryKind: (ChatPresentationInputQuery, Disposable)] = [:]
private var contextQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult] = [:] private var contextQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult] = [:]
private var contextQueryPeer: EnginePeer?
private var contextQueryResultPanel: ComponentView<Empty>? private var contextQueryResultPanel: ComponentView<Empty>?
private var stickersResultPanel: ComponentView<Empty>? private var stickersResultPanel: ComponentView<Empty>?
@ -643,6 +644,17 @@ public final class MessageInputPanelComponent: Component {
} }
let contextQueryUpdates = contextQueryResultState(context: context, inputState: inputState, availableTypes: availableTypes, chatLocation: component.chatLocation, currentQueryStates: &self.contextQueryStates) let contextQueryUpdates = contextQueryResultState(context: context, inputState: inputState, availableTypes: availableTypes, chatLocation: component.chatLocation, currentQueryStates: &self.contextQueryStates)
if self.contextQueryPeer == nil, let peerId = component.chatLocation?.peerId {
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, peer?.addressName != nil else {
return
}
self.contextQueryPeer = peer
self.state?.updated(transition: .immediate)
})
}
for (kind, update) in contextQueryUpdates { for (kind, update) in contextQueryUpdates {
switch update { switch update {
case .remove: case .remove:
@ -1871,6 +1883,8 @@ public final class MessageInputPanelComponent: Component {
var contextResults: ContextResultPanelComponent.Results? var contextResults: ContextResultPanelComponent.Results?
if let result = self.contextQueryResults[.mention], case let .mentions(mentions) = result, !mentions.isEmpty { if let result = self.contextQueryResults[.mention], case let .mentions(mentions) = result, !mentions.isEmpty {
contextResults = .mentions(mentions) contextResults = .mentions(mentions)
} else if let result = self.contextQueryResults[.hashtag], case let .hashtags(hashtags, query) = result, !hashtags.isEmpty || (query.count >= 4 && self.contextQueryPeer != nil) {
contextResults = .hashtags(self.contextQueryPeer, hashtags, query)
} }
if let result = self.contextQueryResults[.emoji], case let .stickers(stickers) = result, !stickers.isEmpty { if let result = self.contextQueryResults[.emoji], case let .stickers(stickers) = result, !stickers.isEmpty {

View File

@ -383,6 +383,7 @@ final class PeerInfoScreenData {
let starsRevenueStatsState: StarsRevenueStats? let starsRevenueStatsState: StarsRevenueStats?
let starsRevenueStatsContext: StarsRevenueStatsContext? let starsRevenueStatsContext: StarsRevenueStatsContext?
let revenueStatsState: RevenueStats? let revenueStatsState: RevenueStats?
let revenueStatsContext: RevenueStatsContext?
let profileGiftsContext: ProfileGiftsContext? let profileGiftsContext: ProfileGiftsContext?
let premiumGiftOptions: [PremiumGiftCodeOption] let premiumGiftOptions: [PremiumGiftCodeOption]
@ -431,6 +432,7 @@ final class PeerInfoScreenData {
starsRevenueStatsState: StarsRevenueStats?, starsRevenueStatsState: StarsRevenueStats?,
starsRevenueStatsContext: StarsRevenueStatsContext?, starsRevenueStatsContext: StarsRevenueStatsContext?,
revenueStatsState: RevenueStats?, revenueStatsState: RevenueStats?,
revenueStatsContext: RevenueStatsContext?,
profileGiftsContext: ProfileGiftsContext?, profileGiftsContext: ProfileGiftsContext?,
premiumGiftOptions: [PremiumGiftCodeOption] premiumGiftOptions: [PremiumGiftCodeOption]
) { ) {
@ -467,6 +469,7 @@ final class PeerInfoScreenData {
self.starsRevenueStatsState = starsRevenueStatsState self.starsRevenueStatsState = starsRevenueStatsState
self.starsRevenueStatsContext = starsRevenueStatsContext self.starsRevenueStatsContext = starsRevenueStatsContext
self.revenueStatsState = revenueStatsState self.revenueStatsState = revenueStatsState
self.revenueStatsContext = revenueStatsContext
self.profileGiftsContext = profileGiftsContext self.profileGiftsContext = profileGiftsContext
self.premiumGiftOptions = premiumGiftOptions self.premiumGiftOptions = premiumGiftOptions
} }
@ -962,6 +965,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
starsRevenueStatsState: nil, starsRevenueStatsState: nil,
starsRevenueStatsContext: nil, starsRevenueStatsContext: nil,
revenueStatsState: nil, revenueStatsState: nil,
revenueStatsContext: nil,
profileGiftsContext: nil, profileGiftsContext: nil,
premiumGiftOptions: [] premiumGiftOptions: []
) )
@ -1009,6 +1013,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
starsRevenueStatsState: nil, starsRevenueStatsState: nil,
starsRevenueStatsContext: nil, starsRevenueStatsContext: nil,
revenueStatsState: nil, revenueStatsState: nil,
revenueStatsContext: nil,
profileGiftsContext: nil, profileGiftsContext: nil,
premiumGiftOptions: [] premiumGiftOptions: []
)) ))
@ -1264,15 +1269,15 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
let starsRevenueContextAndState = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)) let starsRevenueContextAndState = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> mapToSignal { peer -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in |> mapToSignal { peer -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in
var showStarsState = false var canViewStarsRevenue = false
if let peer, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) || context.sharedContext.applicationBindings.appBuildType == .internal { if let peer, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) || context.sharedContext.applicationBindings.appBuildType == .internal {
showStarsState = true canViewStarsRevenue = true
} }
#if DEBUG #if DEBUG
showStarsState = "".isEmpty canViewStarsRevenue = "".isEmpty
#endif #endif
guard showStarsState else { guard canViewStarsRevenue else {
return .single((nil, nil)) return .single((nil, nil))
} }
let starsRevenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId) let starsRevenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId)
@ -1281,6 +1286,30 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
return (starsRevenueStatsContext, state.stats) return (starsRevenueStatsContext, state.stats)
} }
} }
let revenueContextAndState = combineLatest(
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> distinctUntilChanged,
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CanViewRevenue(id: peerId))
|> distinctUntilChanged
)
|> mapToSignal { peer, canViewRevenue -> Signal<(RevenueStatsContext?, RevenueStats?), NoError> in
var canViewRevenue = canViewRevenue
if let peer, case let .user(user) = peer, let _ = user.botInfo, context.sharedContext.applicationBindings.appBuildType == .internal {
canViewRevenue = true
}
#if DEBUG
canViewRevenue = "".isEmpty
#endif
guard canViewRevenue else {
return .single((nil, nil))
}
let revenueStatsContext = RevenueStatsContext(account: context.account, peerId: peerId)
return revenueStatsContext.state
|> map { state -> (RevenueStatsContext?, RevenueStats?) in
return (revenueStatsContext, state.stats)
}
}
return combineLatest( return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true), context.account.viewTracker.peerView(peerId, updateData: true),
@ -1299,9 +1328,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: false), peerInfoPersonalChannel(context: context, peerId: peerId, isSettings: false),
privacySettings, privacySettings,
starsRevenueContextAndState, starsRevenueContextAndState,
revenueContextAndState,
premiumGiftOptions premiumGiftOptions
) )
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, premiumGiftOptions -> PeerInfoScreenData in |> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, revenueContextAndState, premiumGiftOptions -> PeerInfoScreenData in
var availablePanes = availablePanes var availablePanes = availablePanes
if isMyProfile { if isMyProfile {
availablePanes?.insert(.stories, at: 0) availablePanes?.insert(.stories, at: 0)
@ -1417,7 +1447,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
starsState: nil, starsState: nil,
starsRevenueStatsState: starsRevenueContextAndState.1, starsRevenueStatsState: starsRevenueContextAndState.1,
starsRevenueStatsContext: starsRevenueContextAndState.0, starsRevenueStatsContext: starsRevenueContextAndState.0,
revenueStatsState: nil, revenueStatsState: revenueContextAndState.1,
revenueStatsContext: revenueContextAndState.0,
profileGiftsContext: profileGiftsContext, profileGiftsContext: profileGiftsContext,
premiumGiftOptions: premiumGiftOptions premiumGiftOptions: premiumGiftOptions
) )
@ -1629,6 +1660,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
starsRevenueStatsState: starsRevenueContextAndState.1, starsRevenueStatsState: starsRevenueContextAndState.1,
starsRevenueStatsContext: starsRevenueContextAndState.0, starsRevenueStatsContext: starsRevenueContextAndState.0,
revenueStatsState: revenueContextAndState.1, revenueStatsState: revenueContextAndState.1,
revenueStatsContext: revenueContextAndState.0,
profileGiftsContext: nil, profileGiftsContext: nil,
premiumGiftOptions: [] premiumGiftOptions: []
) )
@ -1931,6 +1963,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
starsRevenueStatsState: nil, starsRevenueStatsState: nil,
starsRevenueStatsContext: nil, starsRevenueStatsContext: nil,
revenueStatsState: nil, revenueStatsState: nil,
revenueStatsContext: nil,
profileGiftsContext: nil, profileGiftsContext: nil,
premiumGiftOptions: [] premiumGiftOptions: []
)) ))

View File

@ -562,6 +562,7 @@ private final class PeerInfoInteraction {
let editingOpenNameColorSetup: () -> Void let editingOpenNameColorSetup: () -> Void
let editingOpenInviteLinksSetup: () -> Void let editingOpenInviteLinksSetup: () -> Void
let editingOpenDiscussionGroupSetup: () -> Void let editingOpenDiscussionGroupSetup: () -> Void
let editingOpenRevenue: () -> Void
let editingOpenStars: () -> Void let editingOpenStars: () -> Void
let openParticipantsSection: (PeerInfoParticipantsSection) -> Void let openParticipantsSection: (PeerInfoParticipantsSection) -> Void
let openRecentActions: () -> Void let openRecentActions: () -> Void
@ -629,6 +630,7 @@ private final class PeerInfoInteraction {
editingOpenNameColorSetup: @escaping () -> Void, editingOpenNameColorSetup: @escaping () -> Void,
editingOpenInviteLinksSetup: @escaping () -> Void, editingOpenInviteLinksSetup: @escaping () -> Void,
editingOpenDiscussionGroupSetup: @escaping () -> Void, editingOpenDiscussionGroupSetup: @escaping () -> Void,
editingOpenRevenue: @escaping () -> Void,
editingOpenStars: @escaping () -> Void, editingOpenStars: @escaping () -> Void,
openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void, openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void,
openRecentActions: @escaping () -> Void, openRecentActions: @escaping () -> Void,
@ -695,6 +697,7 @@ private final class PeerInfoInteraction {
self.editingOpenNameColorSetup = editingOpenNameColorSetup self.editingOpenNameColorSetup = editingOpenNameColorSetup
self.editingOpenInviteLinksSetup = editingOpenInviteLinksSetup self.editingOpenInviteLinksSetup = editingOpenInviteLinksSetup
self.editingOpenDiscussionGroupSetup = editingOpenDiscussionGroupSetup self.editingOpenDiscussionGroupSetup = editingOpenDiscussionGroupSetup
self.editingOpenRevenue = editingOpenRevenue
self.editingOpenStars = editingOpenStars self.editingOpenStars = editingOpenStars
self.openParticipantsSection = openParticipantsSection self.openParticipantsSection = openParticipantsSection
self.openRecentActions = openRecentActions self.openRecentActions = openRecentActions
@ -1503,25 +1506,40 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
})) }))
} }
let revenueBalance = data.revenueStatsState?.balances.currentBalance ?? 0
let overallRevenueBalance = data.revenueStatsState?.balances.overallRevenue ?? 0
let starsBalance = data.starsRevenueStatsState?.balances.currentBalance ?? 0 let starsBalance = data.starsRevenueStatsState?.balances.currentBalance ?? 0
let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue ?? 0 let overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue ?? 0
if overallStarsBalance > 0 { if overallRevenueBalance > 0 || overallStarsBalance > 0 {
var string = ""
if overallStarsBalance > 0 {
string.append("*\(presentationStringsFormattedNumber(Int32(starsBalance), presentationData.dateTimeFormat.groupingSeparator))")
}
let attributedString = NSMutableAttributedString(string: string, font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), textColor: presentationData.theme.list.itemSecondaryTextColor)
if let range = attributedString.string.range(of: "*") {
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
}
//TODO:localize //TODO:localize
items[.balances]!.append(PeerInfoScreenHeaderItem(id: 20, text: "BALANCE")) items[.balances]!.append(PeerInfoScreenHeaderItem(id: 20, text: "BALANCE"))
items[.balances]!.append(PeerInfoScreenDisclosureItem(id: 21, label: .attributedText(attributedString), text: "Stars", icon: PresentationResourcesSettings.stars, action: { if overallRevenueBalance > 0 {
interaction.editingOpenStars() let string = "*\(formatTonAmountText(revenueBalance, dateTimeFormat: presentationData.dateTimeFormat))"
})) let attributedString = NSMutableAttributedString(string: string, font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), textColor: presentationData.theme.list.itemSecondaryTextColor)
if let range = attributedString.string.range(of: "*") {
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton), range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
}
items[.balances]!.append(PeerInfoScreenDisclosureItem(id: 21, label: .attributedText(attributedString), text: "Toncoin", icon: PresentationResourcesSettings.ton, action: {
interaction.editingOpenRevenue()
}))
}
if overallStarsBalance > 0 {
let string = "*\(presentationStringsFormattedNumber(Int32(starsBalance), presentationData.dateTimeFormat.groupingSeparator))"
let attributedString = NSMutableAttributedString(string: string, font: Font.regular(presentationData.listsFontSize.itemListBaseFontSize), textColor: presentationData.theme.list.itemSecondaryTextColor)
if let range = attributedString.string.range(of: "*") {
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
}
items[.balances]!.append(PeerInfoScreenDisclosureItem(id: 22, label: .attributedText(attributedString), text: "Stars", icon: PresentationResourcesSettings.stars, action: {
interaction.editingOpenStars()
}))
}
} else {
print()
} }
if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {
@ -2772,6 +2790,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
editingOpenDiscussionGroupSetup: { [weak self] in editingOpenDiscussionGroupSetup: { [weak self] in
self?.editingOpenDiscussionGroupSetup() self?.editingOpenDiscussionGroupSetup()
}, },
editingOpenRevenue: { [weak self] in
self?.editingOpenRevenue()
},
editingOpenStars: { [weak self] in editingOpenStars: { [weak self] in
self?.editingOpenStars() self?.editingOpenStars()
}, },
@ -8524,6 +8545,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
self.controller?.push(channelDiscussionGroupSetupController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id)) self.controller?.push(channelDiscussionGroupSetupController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id))
} }
private func editingOpenRevenue() {
guard let revenueContext = self.data?.revenueStatsContext else {
return
}
let controller = channelStatsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: self.peerId, section: .monetization, existingRevenueContext: revenueContext, boostStatus: nil)
self.controller?.push(controller)
}
private func editingOpenStars() { private func editingOpenStars() {
guard let revenueContext = self.data?.starsRevenueStatsContext else { guard let revenueContext = self.data?.starsRevenueStatsContext else {
return return

View File

@ -104,7 +104,11 @@ final class StarsStatisticsScreenComponent: Component {
} }
if let view = gestureRecognizer.view?.hitTest(gestureRecognizer.location(in: gestureRecognizer.view), with: nil) as? UIControl { if let view = gestureRecognizer.view?.hitTest(gestureRecognizer.location(in: gestureRecognizer.view), with: nil) as? UIControl {
return !view.isTracking if view is UIButton {
return true
} else {
return !view.isTracking
}
} }
return true return true
@ -181,8 +185,8 @@ final class StarsStatisticsScreenComponent: Component {
super.init(frame: frame) super.init(frame: frame)
self.scrollView.delaysContentTouches = false self.scrollView.delaysContentTouches = true
// self.scrollView.canCancelContentTouches = true self.scrollView.canCancelContentTouches = true
self.scrollView.clipsToBounds = false self.scrollView.clipsToBounds = false
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.contentInsetAdjustmentBehavior = .never
@ -677,7 +681,7 @@ final class StarsStatisticsScreenComponent: Component {
panel: AnyComponent(StarsTransactionsListPanelComponent( panel: AnyComponent(StarsTransactionsListPanelComponent(
context: component.context, context: component.context,
transactionsContext: allTransactionsContext, transactionsContext: allTransactionsContext,
isAccount: true, isAccount: false,
action: { transaction in action: { transaction in
component.openTransaction(transaction) component.openTransaction(transaction)
} }
@ -690,7 +694,7 @@ final class StarsStatisticsScreenComponent: Component {
panel: AnyComponent(StarsTransactionsListPanelComponent( panel: AnyComponent(StarsTransactionsListPanelComponent(
context: component.context, context: component.context,
transactionsContext: incomingTransactionsContext, transactionsContext: incomingTransactionsContext,
isAccount: true, isAccount: false,
action: { transaction in action: { transaction in
component.openTransaction(transaction) component.openTransaction(transaction)
} }
@ -703,7 +707,7 @@ final class StarsStatisticsScreenComponent: Component {
panel: AnyComponent(StarsTransactionsListPanelComponent( panel: AnyComponent(StarsTransactionsListPanelComponent(
context: component.context, context: component.context,
transactionsContext: outgoingTransactionsContext, transactionsContext: outgoingTransactionsContext,
isAccount: true, isAccount: false,
action: { transaction in action: { transaction in
component.openTransaction(transaction) component.openTransaction(transaction)
} }

View File

@ -136,8 +136,8 @@ final class StarsTransactionsListPanelComponent: Component {
super.init(frame: frame) super.init(frame: frame)
self.scrollView.delaysContentTouches = false self.scrollView.delaysContentTouches = true
// self.scrollView.canCancelContentTouches = true self.scrollView.canCancelContentTouches = true
self.scrollView.clipsToBounds = false self.scrollView.clipsToBounds = false
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.contentInsetAdjustmentBehavior = .never

View File

@ -152,8 +152,8 @@ final class StarsTransactionsScreenComponent: Component {
super.init(frame: frame) super.init(frame: frame)
self.scrollView.delaysContentTouches = false self.scrollView.delaysContentTouches = true
// self.scrollView.canCancelContentTouches = true self.scrollView.canCancelContentTouches = true
self.scrollView.clipsToBounds = false self.scrollView.clipsToBounds = false
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never self.scrollView.contentInsetAdjustmentBehavior = .never

View File

@ -2864,7 +2864,7 @@ public final class StoryItemSetContainerComponent: Component {
style: .story, style: .story,
placeholder: inputPlaceholder, placeholder: inputPlaceholder,
maxLength: 4096, maxLength: 4096,
queryTypes: [.mention, .emoji], queryTypes: [.mention, .hashtag, .emoji],
alwaysDarkWhenHasText: component.metrics.widthClass == .regular, alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
resetInputContents: resetInputContents, resetInputContents: resetInputContents,
nextInputMode: { [weak self] hasText in nextInputMode: { [weak self] hasText in

View File

@ -15,8 +15,10 @@ import ChatControllerInteraction
import ChatContextQuery import ChatContextQuery
import ChatInputContextPanelNode import ChatInputContextPanelNode
private struct HashtagChatInputContextPanelEntryStableId: Hashable { private enum HashtagChatInputContextPanelEntryStableId: Hashable {
let title: String case generic
case peer
case hashtag(String)
} }
private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable { private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable {
@ -31,7 +33,14 @@ private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable {
let isAdditionalRecent: Bool let isAdditionalRecent: Bool
var stableId: HashtagChatInputContextPanelEntryStableId { var stableId: HashtagChatInputContextPanelEntryStableId {
return HashtagChatInputContextPanelEntryStableId(title: self.title) switch self.index {
case 0:
return .generic
case 1:
return .peer
default:
return .hashtag(self.title)
}
} }
func withUpdatedTheme(_ theme: PresentationTheme) -> HashtagChatInputContextPanelEntry { func withUpdatedTheme(_ theme: PresentationTheme) -> HashtagChatInputContextPanelEntry {
@ -128,12 +137,16 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
stableIds.insert(genericEntry.stableId) stableIds.insert(genericEntry.stableId)
entries.append(genericEntry) entries.append(genericEntry)
var isGroup = false
if case let .channel(channel) = peer, case .group = channel.info {
isGroup = true
}
let peerEntry = HashtagChatInputContextPanelEntry( let peerEntry = HashtagChatInputContextPanelEntry(
index: 1, index: 1,
theme: self.theme, theme: self.theme,
peer: peer, peer: peer,
title: "Use #\(query)@\(addressName)", title: "Use #\(query)@\(addressName)",
text: "searches only posts from this channel", text: isGroup ? "searches only posts from this group" : "searches only posts from this channel",
badge: "NEW", badge: "NEW",
hashtag: "\(query)@\(addressName)", hashtag: "\(query)@\(addressName)",
revealed: false, revealed: false,

View File

@ -93,11 +93,11 @@ final class HashtagChatInputPanelItem: ListViewItem {
if self.revealed { if self.revealed {
self.setHashtagRevealed(nil) self.setHashtagRevealed(nil)
} else { } else {
if self.isAdditionalRecent { // if self.isAdditionalRecent {
self.hashtagSelected(self.hashtag) // self.hashtagSelected(self.hashtag)
} else { // } else {
self.hashtagSelected(self.hashtag + " ") self.hashtagSelected(self.hashtag + " ")
} // }
} }
} }
} }
@ -254,7 +254,7 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
strongSelf.separatorNode.isHidden = !mergedBottom strongSelf.separatorNode.isHidden = !mergedBottom
let iconSize = CGSize(width: 30.0, height: 30.0) 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.iconBackgroundLayer.frame = CGRect(origin: CGPoint(x: params.leftInset + 12.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)) 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 { if let peer = item.peer {
@ -274,7 +274,7 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
} }
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 + textLeftInset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - textLeftInset, 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))

View File

@ -2362,7 +2362,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
updatedPresentationData: nil, updatedPresentationData: nil,
peer: peer._asPeer(), peer: peer._asPeer(),
mode: .generic, mode: .generic,
avatarInitiallyExpanded: true, avatarInitiallyExpanded: peer.smallProfileImage != nil,
fromChat: false, fromChat: false,
requestsContext: nil requestsContext: nil
) { ) {