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.Intro.Title" = "Earn From Your Channel";
|
||||
"Monetization.Intro.Bot.Title" = "Earn From Your Bot";
|
||||
|
||||
"Monetization.Intro.Ads.Title" = "Telegram Ads";
|
||||
"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.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.Of" = "%1$@ of %2$@";
|
||||
"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.Display" = "Display on My Page";
|
||||
"Gift.View.Convert" = "Convert to %@";
|
||||
@ -13141,3 +13146,6 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Stars.Transaction.TelegramBotApi.Title" = "Paid Limit Extension";
|
||||
"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] {
|
||||
var entries: [StatsEntry] = []
|
||||
|
||||
var isBot = false
|
||||
if case let .user(user) = peer, let _ = user.botInfo {
|
||||
isBot = true
|
||||
}
|
||||
|
||||
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 {
|
||||
entries.append(.adsImpressionsTitle(presentationData.theme, presentationData.strings.Monetization_ImpressionsTitle))
|
||||
@ -1602,7 +1607,7 @@ private func monetizationEntries(
|
||||
}
|
||||
|
||||
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))
|
||||
|
||||
if isCreator {
|
||||
@ -1644,7 +1649,7 @@ private func monetizationEntries(
|
||||
|
||||
if displayTonTransactions {
|
||||
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
|
||||
@ -1788,7 +1793,15 @@ private func channelStatsControllerEntries(
|
||||
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 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
|
||||
@ -1845,7 +1858,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
|
||||
let boostsContext = ChannelBoostersContext(account: context.account, peerId: peerId, gift: false)
|
||||
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?>()
|
||||
revenueState.set(.single(nil) |> then(revenueContext.state |> map(Optional.init)))
|
||||
|
||||
@ -2013,7 +2026,7 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
buyAdsImpl?()
|
||||
},
|
||||
openMonetizationIntro: {
|
||||
let controller = MonetizationIntroScreen(context: context, openMore: {})
|
||||
let controller = MonetizationIntroScreen(context: context, mode: existingRevenueContext != nil ? .bot : .channel, openMore: {})
|
||||
pushImpl?(controller)
|
||||
},
|
||||
openMonetizationInfo: {
|
||||
@ -2112,7 +2125,8 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
)
|
||||
|> deliverOnMainQueue
|
||||
|> 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)
|
||||
|
||||
@ -2167,7 +2181,11 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
var headerItem: BoostHeaderItem?
|
||||
var leftNavigationButton: ItemListNavigationButton?
|
||||
var boostsOnly = false
|
||||
if section == .boosts {
|
||||
if existingRevenueContext != nil {
|
||||
//TODO:localize
|
||||
title = .text("Toncoin Balance")
|
||||
canViewRevenue = true
|
||||
} else if section == .boosts {
|
||||
title = .text("")
|
||||
|
||||
let headerTitle = isGroup ? presentationData.strings.GroupBoost_Title : presentationData.strings.ChannelBoost_Title
|
||||
|
@ -20,15 +20,18 @@ private final class SheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let mode: MonetizationIntroScreen.Mode
|
||||
let openMore: () -> Void
|
||||
let dismiss: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
mode: MonetizationIntroScreen.Mode,
|
||||
openMore: @escaping () -> Void,
|
||||
dismiss: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
self.openMore = openMore
|
||||
self.dismiss = dismiss
|
||||
}
|
||||
@ -37,6 +40,9 @@ private final class SheetContent: CombinedComponent {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.mode != rhs.mode {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -136,7 +142,7 @@ private final class SheetContent: CombinedComponent {
|
||||
|
||||
let title = title.update(
|
||||
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,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1
|
||||
@ -157,7 +163,7 @@ private final class SheetContent: CombinedComponent {
|
||||
component: AnyComponent(ParagraphComponent(
|
||||
title: strings.Monetization_Intro_Ads_Title,
|
||||
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,
|
||||
iconName: "Ads/Ads",
|
||||
iconColor: linkColor
|
||||
@ -343,13 +349,16 @@ private final class SheetContainerComponent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let mode: MonetizationIntroScreen.Mode
|
||||
let openMore: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
mode: MonetizationIntroScreen.Mode,
|
||||
openMore: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
self.openMore = openMore
|
||||
}
|
||||
|
||||
@ -357,6 +366,9 @@ private final class SheetContainerComponent: CombinedComponent {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.mode != rhs.mode {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -375,6 +387,7 @@ private final class SheetContainerComponent: CombinedComponent {
|
||||
component: SheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(SheetContent(
|
||||
context: context.component.context,
|
||||
mode: context.component.mode,
|
||||
openMore: context.component.openMore,
|
||||
dismiss: {
|
||||
animateOut.invoke(Action { _ in
|
||||
@ -444,9 +457,15 @@ private final class SheetContainerComponent: CombinedComponent {
|
||||
final class MonetizationIntroScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
private var openMore: (() -> Void)?
|
||||
|
||||
|
||||
enum Mode: Equatable {
|
||||
case channel
|
||||
case bot
|
||||
}
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
mode: Mode,
|
||||
openMore: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
@ -456,6 +475,7 @@ final class MonetizationIntroScreen: ViewControllerComponentContainer {
|
||||
context: context,
|
||||
component: SheetContainerComponent(
|
||||
context: context,
|
||||
mode: mode,
|
||||
openMore: openMore
|
||||
),
|
||||
navigationBarAppearance: .none,
|
||||
|
@ -1116,7 +1116,9 @@ public extension TelegramEngine.EngineData.Item {
|
||||
guard let view = view as? CachedPeerDataView else {
|
||||
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)
|
||||
} else {
|
||||
return false
|
||||
|
@ -111,6 +111,24 @@ public struct PresentationResourcesSettings {
|
||||
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
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
@ -46,11 +46,6 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
} else if let integerPart = Int32(balanceText) {
|
||||
balanceText = presentationStringsFormattedNumber(integerPart, dateTimeFormat.groupingSeparator)
|
||||
}
|
||||
if value < 0 {
|
||||
balanceText.insert("-", at: balanceText.startIndex)
|
||||
} else if showPlus {
|
||||
balanceText.insert("+", at: balanceText.startIndex)
|
||||
}
|
||||
|
||||
return balanceText
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ public final class GiftItemComponent: Component {
|
||||
case red
|
||||
case blue
|
||||
|
||||
var colors: [UIColor] {
|
||||
func colors(theme: PresentationTheme) -> [UIColor] {
|
||||
switch self {
|
||||
case .red:
|
||||
return [
|
||||
@ -33,10 +33,17 @@ public final class GiftItemComponent: Component {
|
||||
|
||||
]
|
||||
case .blue:
|
||||
return [
|
||||
UIColor(rgb: 0x34a4fc),
|
||||
UIColor(rgb: 0x6fd3ff)
|
||||
]
|
||||
if theme.overallDarkAppearance {
|
||||
return [
|
||||
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 {
|
||||
let isFirstTime = self.component == nil
|
||||
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
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)
|
||||
|
||||
if component.isLoading {
|
||||
@ -339,8 +351,8 @@ public final class GiftItemComponent: Component {
|
||||
}
|
||||
ribbonTextView.bounds = CGRect(origin: .zero, size: ribbonTextSize)
|
||||
|
||||
if self.ribbon.image == nil {
|
||||
self.ribbon.image = generateGradientTintedImage(image: UIImage(bundleImageName: "Premium/GiftRibbon"), colors: ribbon.color.colors, direction: .diagonal)
|
||||
if self.ribbon.image == nil || themeUpdated {
|
||||
self.ribbon.image = generateGradientTintedImage(image: UIImage(bundleImageName: "Premium/GiftRibbon"), colors: ribbon.color.colors(theme: component.theme), direction: .diagonal)
|
||||
}
|
||||
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)
|
||||
|
@ -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 {
|
||||
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 += 16.0
|
||||
|
||||
}
|
||||
|
||||
if incoming && !converted && !savedToProfile {
|
||||
let button = button.update(
|
||||
component: SolidRoundedButtonComponent(
|
||||
title: savedToProfile ? strings.Gift_View_Hide : strings.Gift_View_Display,
|
||||
|
@ -208,7 +208,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
||||
style: .media,
|
||||
placeholder: .plain(presentationData.strings.MediaPicker_AddCaption),
|
||||
maxLength: Int(self.context.userLimits.maxCaptionLength),
|
||||
queryTypes: [.mention],
|
||||
queryTypes: [.mention, .hashtag],
|
||||
alwaysDarkWhenHasText: false,
|
||||
resetInputContents: resetInputContents,
|
||||
nextInputMode: { [weak self] _ in
|
||||
|
@ -119,7 +119,7 @@ public final class DrawingMessageRenderer {
|
||||
let size = 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?
|
||||
if let messageNode = self.messageNodes?.first {
|
||||
if self.isOverlay {
|
||||
@ -343,7 +343,7 @@ public final class DrawingMessageRenderer {
|
||||
}
|
||||
|
||||
public func render(completion: @escaping (Result) -> Void) {
|
||||
Queue.mainQueue().after(0.12) {
|
||||
Queue.mainQueue().justDispatch {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let defaultPresentationData = defaultPresentationData()
|
||||
|
||||
|
@ -1229,7 +1229,7 @@ final class MediaEditorScreenComponent: Component {
|
||||
style: .editor,
|
||||
placeholder: .plain(environment.strings.Story_Editor_InputPlaceholderAddCaption),
|
||||
maxLength: Int(component.context.userLimits.maxStoryCaptionLength),
|
||||
queryTypes: [.mention],
|
||||
queryTypes: [.mention, .hashtag],
|
||||
alwaysDarkWhenHasText: false,
|
||||
resetInputContents: nil,
|
||||
nextInputMode: { _ in return nextInputMode },
|
||||
@ -1363,12 +1363,12 @@ final class MediaEditorScreenComponent: Component {
|
||||
header: header,
|
||||
isChannel: false,
|
||||
storyItem: nil,
|
||||
chatLocation: nil
|
||||
chatLocation: controller.customTarget.flatMap { .peer(id: $0) }
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: inputPanelAvailableWidth, height: inputPanelAvailableHeight)
|
||||
)
|
||||
|
||||
|
||||
if self.inputPanelExternalState.isEditing && controller.node.entitiesView.hasSelection {
|
||||
Queue.mainQueue().justDispatch {
|
||||
controller.node.entitiesView.selectEntity(nil)
|
||||
|
@ -11,14 +11,18 @@ import PeerListItemComponent
|
||||
final class ContextResultPanelComponent: Component {
|
||||
enum Results: Equatable {
|
||||
case mentions([EnginePeer])
|
||||
case hashtags([String])
|
||||
case hashtags(EnginePeer?, [String], String)
|
||||
|
||||
var count: Int {
|
||||
switch self {
|
||||
case let .hashtags(hashtags):
|
||||
return hashtags.count
|
||||
case let .mentions(peers):
|
||||
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)
|
||||
|
||||
// var synchronousLoad = false
|
||||
// if let hint = transition.userData(PeerListItemComponent.TransitionHint.self) {
|
||||
// synchronousLoad = hint.synchronousLoad
|
||||
// }
|
||||
|
||||
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 {
|
||||
guard index < peers.count else {
|
||||
guard index < component.results.count else {
|
||||
continue
|
||||
}
|
||||
|
||||
let itemFrame = itemLayout.itemFrame(for: index)
|
||||
|
||||
var itemTransition = transition
|
||||
let peer = peers[index]
|
||||
validIds.append(peer.id)
|
||||
let id: AnyHashable
|
||||
|
||||
let visibleItem: ComponentView<Empty>
|
||||
if let current = self.visibleItems[peer.id] {
|
||||
visibleItem = current
|
||||
} else {
|
||||
if !transition.animation.isImmediate {
|
||||
itemTransition = .immediate
|
||||
}
|
||||
visibleItem = ComponentView()
|
||||
self.visibleItems[peer.id] = visibleItem
|
||||
}
|
||||
|
||||
let _ = visibleItem.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(PeerListItemComponent(
|
||||
let itemComponent: AnyComponent<Empty>
|
||||
switch component.results {
|
||||
case let .mentions(peers):
|
||||
let peer = peers[index]
|
||||
id = peer.id
|
||||
itemComponent = AnyComponent(PeerListItemComponent(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
@ -236,21 +225,100 @@ final class ContextResultPanelComponent: Component {
|
||||
}
|
||||
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: {},
|
||||
containerSize: itemFrame.size
|
||||
)
|
||||
if let itemView = visibleItem.view {
|
||||
// var animateIn = false
|
||||
if itemView.superview == nil {
|
||||
// animateIn = true
|
||||
self.scrollView.addSubview(itemView)
|
||||
}
|
||||
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
|
||||
self.backgroundView.updateColor(color: UIColor(white: 0.0, alpha: 0.7), transition: transition.containedViewLayoutTransition)
|
||||
|
||||
let measureItemSize = self.measureItem.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PeerListItemComponent(
|
||||
let itemComponent: AnyComponent<Empty>
|
||||
switch component.results {
|
||||
case .mentions:
|
||||
itemComponent = AnyComponent(PeerListItemComponent(
|
||||
context: component.context,
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
@ -301,7 +370,25 @@ final class ContextResultPanelComponent: Component {
|
||||
hasNext: true,
|
||||
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: {},
|
||||
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 contextQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult] = [:]
|
||||
private var contextQueryPeer: EnginePeer?
|
||||
private var contextQueryResultPanel: 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)
|
||||
|
||||
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 {
|
||||
switch update {
|
||||
case .remove:
|
||||
@ -1871,6 +1883,8 @@ public final class MessageInputPanelComponent: Component {
|
||||
var contextResults: ContextResultPanelComponent.Results?
|
||||
if let result = self.contextQueryResults[.mention], case let .mentions(mentions) = result, !mentions.isEmpty {
|
||||
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 {
|
||||
|
@ -383,6 +383,7 @@ final class PeerInfoScreenData {
|
||||
let starsRevenueStatsState: StarsRevenueStats?
|
||||
let starsRevenueStatsContext: StarsRevenueStatsContext?
|
||||
let revenueStatsState: RevenueStats?
|
||||
let revenueStatsContext: RevenueStatsContext?
|
||||
let profileGiftsContext: ProfileGiftsContext?
|
||||
let premiumGiftOptions: [PremiumGiftCodeOption]
|
||||
|
||||
@ -431,6 +432,7 @@ final class PeerInfoScreenData {
|
||||
starsRevenueStatsState: StarsRevenueStats?,
|
||||
starsRevenueStatsContext: StarsRevenueStatsContext?,
|
||||
revenueStatsState: RevenueStats?,
|
||||
revenueStatsContext: RevenueStatsContext?,
|
||||
profileGiftsContext: ProfileGiftsContext?,
|
||||
premiumGiftOptions: [PremiumGiftCodeOption]
|
||||
) {
|
||||
@ -467,6 +469,7 @@ final class PeerInfoScreenData {
|
||||
self.starsRevenueStatsState = starsRevenueStatsState
|
||||
self.starsRevenueStatsContext = starsRevenueStatsContext
|
||||
self.revenueStatsState = revenueStatsState
|
||||
self.revenueStatsContext = revenueStatsContext
|
||||
self.profileGiftsContext = profileGiftsContext
|
||||
self.premiumGiftOptions = premiumGiftOptions
|
||||
}
|
||||
@ -962,6 +965,7 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
||||
starsRevenueStatsState: nil,
|
||||
starsRevenueStatsContext: nil,
|
||||
revenueStatsState: nil,
|
||||
revenueStatsContext: nil,
|
||||
profileGiftsContext: nil,
|
||||
premiumGiftOptions: []
|
||||
)
|
||||
@ -1009,6 +1013,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
starsRevenueStatsState: nil,
|
||||
starsRevenueStatsContext: nil,
|
||||
revenueStatsState: nil,
|
||||
revenueStatsContext: nil,
|
||||
profileGiftsContext: nil,
|
||||
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))
|
||||
|> 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 {
|
||||
showStarsState = true
|
||||
canViewStarsRevenue = true
|
||||
}
|
||||
#if DEBUG
|
||||
showStarsState = "".isEmpty
|
||||
canViewStarsRevenue = "".isEmpty
|
||||
#endif
|
||||
|
||||
guard showStarsState else {
|
||||
guard canViewStarsRevenue else {
|
||||
return .single((nil, nil))
|
||||
}
|
||||
let starsRevenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId)
|
||||
@ -1281,6 +1286,30 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
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(
|
||||
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),
|
||||
privacySettings,
|
||||
starsRevenueContextAndState,
|
||||
revenueContextAndState,
|
||||
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
|
||||
if isMyProfile {
|
||||
availablePanes?.insert(.stories, at: 0)
|
||||
@ -1417,7 +1447,8 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
starsState: nil,
|
||||
starsRevenueStatsState: starsRevenueContextAndState.1,
|
||||
starsRevenueStatsContext: starsRevenueContextAndState.0,
|
||||
revenueStatsState: nil,
|
||||
revenueStatsState: revenueContextAndState.1,
|
||||
revenueStatsContext: revenueContextAndState.0,
|
||||
profileGiftsContext: profileGiftsContext,
|
||||
premiumGiftOptions: premiumGiftOptions
|
||||
)
|
||||
@ -1629,6 +1660,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
starsRevenueStatsState: starsRevenueContextAndState.1,
|
||||
starsRevenueStatsContext: starsRevenueContextAndState.0,
|
||||
revenueStatsState: revenueContextAndState.1,
|
||||
revenueStatsContext: revenueContextAndState.0,
|
||||
profileGiftsContext: nil,
|
||||
premiumGiftOptions: []
|
||||
)
|
||||
@ -1931,6 +1963,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
starsRevenueStatsState: nil,
|
||||
starsRevenueStatsContext: nil,
|
||||
revenueStatsState: nil,
|
||||
revenueStatsContext: nil,
|
||||
profileGiftsContext: nil,
|
||||
premiumGiftOptions: []
|
||||
))
|
||||
|
@ -562,6 +562,7 @@ private final class PeerInfoInteraction {
|
||||
let editingOpenNameColorSetup: () -> Void
|
||||
let editingOpenInviteLinksSetup: () -> Void
|
||||
let editingOpenDiscussionGroupSetup: () -> Void
|
||||
let editingOpenRevenue: () -> Void
|
||||
let editingOpenStars: () -> Void
|
||||
let openParticipantsSection: (PeerInfoParticipantsSection) -> Void
|
||||
let openRecentActions: () -> Void
|
||||
@ -629,6 +630,7 @@ private final class PeerInfoInteraction {
|
||||
editingOpenNameColorSetup: @escaping () -> Void,
|
||||
editingOpenInviteLinksSetup: @escaping () -> Void,
|
||||
editingOpenDiscussionGroupSetup: @escaping () -> Void,
|
||||
editingOpenRevenue: @escaping () -> Void,
|
||||
editingOpenStars: @escaping () -> Void,
|
||||
openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void,
|
||||
openRecentActions: @escaping () -> Void,
|
||||
@ -695,6 +697,7 @@ private final class PeerInfoInteraction {
|
||||
self.editingOpenNameColorSetup = editingOpenNameColorSetup
|
||||
self.editingOpenInviteLinksSetup = editingOpenInviteLinksSetup
|
||||
self.editingOpenDiscussionGroupSetup = editingOpenDiscussionGroupSetup
|
||||
self.editingOpenRevenue = editingOpenRevenue
|
||||
self.editingOpenStars = editingOpenStars
|
||||
self.openParticipantsSection = openParticipantsSection
|
||||
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 overallStarsBalance = data.starsRevenueStatsState?.balances.overallRevenue ?? 0
|
||||
|
||||
if 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))
|
||||
}
|
||||
|
||||
if overallRevenueBalance > 0 || overallStarsBalance > 0 {
|
||||
//TODO:localize
|
||||
items[.balances]!.append(PeerInfoScreenHeaderItem(id: 20, text: "BALANCE"))
|
||||
items[.balances]!.append(PeerInfoScreenDisclosureItem(id: 21, label: .attributedText(attributedString), text: "Stars", icon: PresentationResourcesSettings.stars, action: {
|
||||
interaction.editingOpenStars()
|
||||
}))
|
||||
if overallRevenueBalance > 0 {
|
||||
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) {
|
||||
@ -2772,6 +2790,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
editingOpenDiscussionGroupSetup: { [weak self] in
|
||||
self?.editingOpenDiscussionGroupSetup()
|
||||
},
|
||||
editingOpenRevenue: { [weak self] in
|
||||
self?.editingOpenRevenue()
|
||||
},
|
||||
editingOpenStars: { [weak self] in
|
||||
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))
|
||||
}
|
||||
|
||||
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() {
|
||||
guard let revenueContext = self.data?.starsRevenueStatsContext else {
|
||||
return
|
||||
|
@ -104,7 +104,11 @@ final class StarsStatisticsScreenComponent: Component {
|
||||
}
|
||||
|
||||
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
|
||||
@ -181,8 +185,8 @@ final class StarsStatisticsScreenComponent: Component {
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.scrollView.delaysContentTouches = false
|
||||
// self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.delaysContentTouches = true
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
@ -677,7 +681,7 @@ final class StarsStatisticsScreenComponent: Component {
|
||||
panel: AnyComponent(StarsTransactionsListPanelComponent(
|
||||
context: component.context,
|
||||
transactionsContext: allTransactionsContext,
|
||||
isAccount: true,
|
||||
isAccount: false,
|
||||
action: { transaction in
|
||||
component.openTransaction(transaction)
|
||||
}
|
||||
@ -690,7 +694,7 @@ final class StarsStatisticsScreenComponent: Component {
|
||||
panel: AnyComponent(StarsTransactionsListPanelComponent(
|
||||
context: component.context,
|
||||
transactionsContext: incomingTransactionsContext,
|
||||
isAccount: true,
|
||||
isAccount: false,
|
||||
action: { transaction in
|
||||
component.openTransaction(transaction)
|
||||
}
|
||||
@ -703,7 +707,7 @@ final class StarsStatisticsScreenComponent: Component {
|
||||
panel: AnyComponent(StarsTransactionsListPanelComponent(
|
||||
context: component.context,
|
||||
transactionsContext: outgoingTransactionsContext,
|
||||
isAccount: true,
|
||||
isAccount: false,
|
||||
action: { transaction in
|
||||
component.openTransaction(transaction)
|
||||
}
|
||||
|
@ -136,8 +136,8 @@ final class StarsTransactionsListPanelComponent: Component {
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.scrollView.delaysContentTouches = false
|
||||
// self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.delaysContentTouches = true
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
|
@ -152,8 +152,8 @@ final class StarsTransactionsScreenComponent: Component {
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.scrollView.delaysContentTouches = false
|
||||
// self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.delaysContentTouches = true
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
|
@ -2864,7 +2864,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
style: .story,
|
||||
placeholder: inputPlaceholder,
|
||||
maxLength: 4096,
|
||||
queryTypes: [.mention, .emoji],
|
||||
queryTypes: [.mention, .hashtag, .emoji],
|
||||
alwaysDarkWhenHasText: component.metrics.widthClass == .regular,
|
||||
resetInputContents: resetInputContents,
|
||||
nextInputMode: { [weak self] hasText in
|
||||
|
@ -15,8 +15,10 @@ import ChatControllerInteraction
|
||||
import ChatContextQuery
|
||||
import ChatInputContextPanelNode
|
||||
|
||||
private struct HashtagChatInputContextPanelEntryStableId: Hashable {
|
||||
let title: String
|
||||
private enum HashtagChatInputContextPanelEntryStableId: Hashable {
|
||||
case generic
|
||||
case peer
|
||||
case hashtag(String)
|
||||
}
|
||||
|
||||
private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable {
|
||||
@ -31,7 +33,14 @@ private struct HashtagChatInputContextPanelEntry: Comparable, Identifiable {
|
||||
let isAdditionalRecent: Bool
|
||||
|
||||
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 {
|
||||
@ -128,12 +137,16 @@ final class HashtagChatInputContextPanelNode: ChatInputContextPanelNode {
|
||||
stableIds.insert(genericEntry.stableId)
|
||||
entries.append(genericEntry)
|
||||
|
||||
var isGroup = false
|
||||
if case let .channel(channel) = peer, case .group = channel.info {
|
||||
isGroup = true
|
||||
}
|
||||
let peerEntry = HashtagChatInputContextPanelEntry(
|
||||
index: 1,
|
||||
theme: self.theme,
|
||||
peer: peer,
|
||||
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",
|
||||
hashtag: "\(query)@\(addressName)",
|
||||
revealed: false,
|
||||
|
@ -93,11 +93,11 @@ final class HashtagChatInputPanelItem: ListViewItem {
|
||||
if self.revealed {
|
||||
self.setHashtagRevealed(nil)
|
||||
} else {
|
||||
if self.isAdditionalRecent {
|
||||
self.hashtagSelected(self.hashtag)
|
||||
} else {
|
||||
// if self.isAdditionalRecent {
|
||||
// self.hashtagSelected(self.hashtag)
|
||||
// } else {
|
||||
self.hashtagSelected(self.hashtag + " ")
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -254,7 +254,7 @@ final class HashtagChatInputPanelItemNode: ListViewItemNode {
|
||||
strongSelf.separatorNode.isHidden = !mergedBottom
|
||||
|
||||
let iconSize = CGSize(width: 30.0, height: 30.0)
|
||||
strongSelf.iconBackgroundLayer.frame = CGRect(origin: CGPoint(x: leftInset - 3.0, y: floor((nodeLayout.contentSize.height - 30.0) / 2.0)), size: iconSize)
|
||||
strongSelf.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))
|
||||
|
||||
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.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))
|
||||
|
||||
|
@ -2362,7 +2362,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
mode: .generic,
|
||||
avatarInitiallyExpanded: true,
|
||||
avatarInitiallyExpanded: peer.smallProfileImage != nil,
|
||||
fromChat: false,
|
||||
requestsContext: nil
|
||||
) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user