mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
2fa059477c
commit
3d03fc94c6
@ -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";
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -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: []
|
||||||
))
|
))
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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
|
||||||
) {
|
) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user