[WIP] Tags

This commit is contained in:
Isaac 2024-01-19 22:23:10 +04:00
parent 67fd1b6c2b
commit d1493c4abd
36 changed files with 906 additions and 170 deletions

View File

@ -10934,7 +10934,7 @@ Sorry for the inconvenience.";
"Chat.ContextMenuTagsTitle" = "Tag the message with an emoji for quick access later"; "Chat.ContextMenuTagsTitle" = "Tag the message with an emoji for quick access later";
"Chat.ReactionContextMenu.FilterByTag" = "Fiter by Tag"; "Chat.ReactionContextMenu.FilterByTag" = "Filter by Tag";
"Chat.ReactionContextMenu.RemoveTag" = "Remove Tag"; "Chat.ReactionContextMenu.RemoveTag" = "Remove Tag";
"Chat.EmptyStateMessagingRestrictedToPremium.Text" = "Subscribe to **Premium**\nto message **%@**."; "Chat.EmptyStateMessagingRestrictedToPremium.Text" = "Subscribe to **Premium**\nto message **%@**.";
@ -10974,3 +10974,5 @@ Sorry for the inconvenience.";
"Settings.Privacy.ReadTimePremiumActive" = "Subcribed to Telegram Premium"; "Settings.Privacy.ReadTimePremiumActive" = "Subcribed to Telegram Premium";
"Settings.Privacy.ReadTimePremiumActiveFooter" = "Because you are a Telegram Premium subscriber, you will see the last seen and read time of all users who are sharing it with you even if you are hiding yours."; "Settings.Privacy.ReadTimePremiumActiveFooter" = "Because you are a Telegram Premium subscriber, you will see the last seen and read time of all users who are sharing it with you even if you are hiding yours.";
"Privacy.VoiceMessages.NonPremiumHelp" = "Subscribe to Telegram Premium to restrict who can send you voice or video messages.\n\n[What is Telegram Premium?]()";

View File

@ -356,6 +356,7 @@ public enum ChatSearchDomain: Equatable {
case everything case everything
case members case members
case member(Peer) case member(Peer)
case tag(MessageReaction.Reaction)
public static func ==(lhs: ChatSearchDomain, rhs: ChatSearchDomain) -> Bool { public static func ==(lhs: ChatSearchDomain, rhs: ChatSearchDomain) -> Bool {
switch lhs { switch lhs {
@ -377,6 +378,12 @@ public enum ChatSearchDomain: Equatable {
} else { } else {
return false return false
} }
case let .tag(reaction):
if case .tag(reaction) = rhs {
return true
} else {
return false
}
} }
} }
} }

View File

@ -821,7 +821,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
guard let layout = self.layout else { guard let layout = self.layout else {
return return
} }
layout.spec.component.action(layout.spec.component.reaction.value) layout.spec.component.action(self, layout.spec.component.reaction.value)
} }
fileprivate func apply(layout: Layout, animation: ListViewItemUpdateAnimation, arguments: ReactionButtonsAsyncLayoutContainer.Arguments) { fileprivate func apply(layout: Layout, animation: ListViewItemUpdateAnimation, arguments: ReactionButtonsAsyncLayoutContainer.Arguments) {
@ -1061,7 +1061,7 @@ public final class ReactionButtonComponent: Equatable {
public let isTag: Bool public let isTag: Bool
public let count: Int public let count: Int
public let chosenOrder: Int? public let chosenOrder: Int?
public let action: (MessageReaction.Reaction) -> Void public let action: (ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void
public init( public init(
context: AccountContext, context: AccountContext,
@ -1071,7 +1071,7 @@ public final class ReactionButtonComponent: Equatable {
isTag: Bool, isTag: Bool,
count: Int, count: Int,
chosenOrder: Int?, chosenOrder: Int?,
action: @escaping (MessageReaction.Reaction) -> Void action: @escaping (ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void
) { ) {
self.context = context self.context = context
self.colors = colors self.colors = colors
@ -1218,7 +1218,7 @@ public final class ReactionButtonsAsyncLayoutContainer {
public func update( public func update(
context: AccountContext, context: AccountContext,
action: @escaping (MessageReaction.Reaction) -> Void, action: @escaping (ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void,
reactions: [ReactionButtonsAsyncLayoutContainer.Reaction], reactions: [ReactionButtonsAsyncLayoutContainer.Reaction],
colors: ReactionButtonComponent.Colors, colors: ReactionButtonComponent.Colors,
isTag: Bool, isTag: Bool,

View File

@ -1586,6 +1586,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
if self.reactionContextNodeIsAnimatingOut, let reactionContextNode = self.reactionContextNode { if self.reactionContextNodeIsAnimatingOut, let reactionContextNode = self.reactionContextNode {
reactionContextNode.bounds = reactionContextNode.bounds.offsetBy(dx: 0.0, dy: offset.y) reactionContextNode.bounds = reactionContextNode.bounds.offsetBy(dx: 0.0, dy: offset.y)
transition.animateOffsetAdditive(node: reactionContextNode, offset: -offset.y) transition.animateOffsetAdditive(node: reactionContextNode, offset: -offset.y)
if let itemContentNode = self.itemContentNode {
itemContentNode.bounds = itemContentNode.bounds.offsetBy(dx: 0.0, dy: offset.y)
transition.animateOffsetAdditive(node: itemContentNode, offset: -offset.y)
}
} }
} }
} }

View File

@ -1827,6 +1827,13 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil, stackFromBottom: self.stackFromBottom) return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil, stackFromBottom: self.stackFromBottom)
} }
public func addAfterTransactionsCompleted(_ f: @escaping () -> Void) {
self.transactionQueue.addTransaction({ transactionCompletion in
f()
transactionCompletion()
})
}
public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, additionalScrollDistance: CGFloat = 0.0, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, additionalScrollDistance: CGFloat = 0.0, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) {
if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil && additionalScrollDistance.isZero { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil && additionalScrollDistance.isZero {
if let updateOpaqueState = updateOpaqueState { if let updateOpaqueState = updateOpaqueState {
@ -2640,6 +2647,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
if let updateSizeAndInsets = updateSizeAndInsets { if let updateSizeAndInsets = updateSizeAndInsets {
if updateSizeAndInsets.size != self.visibleSize || updateSizeAndInsets.insets != self.insets { if updateSizeAndInsets.size != self.visibleSize || updateSizeAndInsets.insets != self.insets {
sizeOrInsetsUpdated = true sizeOrInsetsUpdated = true
if updateSizeAndInsets.insets.top == 83.0 && updateSizeAndInsets.duration < 0.5 {
assert(true)
}
} }
} }

View File

@ -133,9 +133,16 @@ private final class TitleLabelView: UIView {
} }
func update(size: CGSize, text: String, theme: PresentationTheme, transition: ContainedViewLayoutTransition) { func update(size: CGSize, text: String, theme: PresentationTheme, transition: ContainedViewLayoutTransition) {
let foregroundColor: UIColor
if theme.overallDarkAppearance {
foregroundColor = UIColor(white: 1.0, alpha: 0.5)
} else {
foregroundColor = UIColor(white: 0.5, alpha: 0.9)
}
let contentSize = self.contentView.update( let contentSize = self.contentView.update(
transition: .immediate, transition: .immediate,
component: AnyComponent(Text(text: text, font: Font.regular(13.0), color: UIColor(white: 1.0, alpha: 0.2))), component: AnyComponent(Text(text: text, font: Font.regular(13.0), color: foregroundColor)),
environment: {}, environment: {},
containerSize: size containerSize: size
) )
@ -1903,6 +1910,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
itemNode.layer.animateAlpha(from: itemNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) itemNode.layer.animateAlpha(from: itemNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
} }
if let titleLabelView = self.titleLabelView {
titleLabelView.layer.animateAlpha(from: titleLabelView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
if let reactionComponentView = self.reactionSelectionComponentHost?.view { if let reactionComponentView = self.reactionSelectionComponentHost?.view {
reactionComponentView.alpha = 0.0 reactionComponentView.alpha = 0.0
reactionComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) reactionComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
@ -2470,6 +2481,10 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
} }
public func highlightGestureMoved(location: CGPoint, hover: Bool) { public func highlightGestureMoved(location: CGPoint, hover: Bool) {
if self.allPresetReactionsAreAvailable {
return
}
let highlightedReaction = self.previewReaction(at: location)?.reaction let highlightedReaction = self.previewReaction(at: location)?.reaction
if self.highlightedReaction != highlightedReaction { if self.highlightedReaction != highlightedReaction {
self.highlightedReaction = highlightedReaction self.highlightedReaction = highlightedReaction
@ -2489,10 +2504,18 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate {
} }
public func highlightGestureFinished(performAction: Bool) { public func highlightGestureFinished(performAction: Bool) {
if self.allPresetReactionsAreAvailable {
return
}
self.highlightGestureFinished(performAction: performAction, isLarge: false) self.highlightGestureFinished(performAction: performAction, isLarge: false)
} }
private func highlightGestureFinished(performAction: Bool, isLarge: Bool) { private func highlightGestureFinished(performAction: Bool, isLarge: Bool) {
if self.allPresetReactionsAreAvailable {
return
}
if let highlightedReaction = self.highlightedReaction { if let highlightedReaction = self.highlightedReaction {
self.highlightedReaction = nil self.highlightedReaction = nil
if performAction { if performAction {

View File

@ -391,7 +391,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
arguments.openGroupsPrivacy() arguments.openGroupsPrivacy()
}) })
case let .voiceMessagePrivacy(theme, text, value, hasPremium): case let .voiceMessagePrivacy(theme, text, value, hasPremium):
return ItemListDisclosureItem(presentationData: presentationData, title: text, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: value, labelStyle: !hasPremium ? .textWithIcon(UIImage(bundleImageName: "Chat/Input/Accessory Panels/TextLockIcon")!.precomposed()) : .text, sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: text, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: value, labelStyle: .text, sectionId: self.section, style: .blocks, action: {
arguments.openVoiceMessagePrivacy() arguments.openVoiceMessagePrivacy()
}) })
case let .messagePrivacy(theme, value, hasPremium): case let .messagePrivacy(theme, value, hasPremium):
@ -933,7 +933,6 @@ public func privacyAndSecurityController(
} }
})) }))
}, openVoiceMessagePrivacy: { }, openVoiceMessagePrivacy: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let signal = combineLatest( let signal = combineLatest(
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),
privacySettingsPromise.get() privacySettingsPromise.get()
@ -941,9 +940,6 @@ public func privacyAndSecurityController(
|> take(1) |> take(1)
|> deliverOnMainQueue |> deliverOnMainQueue
currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] peer, info in currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] peer, info in
let isPremium = peer?.isPremium ?? false
if isPremium {
if let info = info { if let info = info {
pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceMessages, current: info.voiceMessages, updated: { updated, _, _, _ in pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceMessages, current: info.voiceMessages, updated: { updated, _, _, _ in
if let currentInfoDisposable = currentInfoDisposable { if let currentInfoDisposable = currentInfoDisposable {
@ -961,23 +957,6 @@ public func privacyAndSecurityController(
} }
}), true) }), true)
} }
} else {
let hapticFeedback = HapticFeedback()
hapticFeedback.impact()
var alreadyPresented = false
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Privacy_VoiceMessages_Tooltip, timeout: nil, customUndoText: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in
if action == .info {
if !alreadyPresented {
let controller = PremiumIntroScreen(context: context, source: .settings)
pushControllerImpl?(controller, true)
alreadyPresented = true
}
return true
}
return false
}))
}
})) }))
}, openBioPrivacy: { }, openBioPrivacy: {
let signal = privacySettingsPromise.get() let signal = privacySettingsPromise.get()

View File

@ -59,6 +59,7 @@ private final class SelectivePrivacySettingsControllerArguments {
let removePublicPhoto: (() -> Void)? let removePublicPhoto: (() -> Void)?
let updateHideReadTime: ((Bool) -> Void)? let updateHideReadTime: ((Bool) -> Void)?
let openPremiumIntro: () -> Void let openPremiumIntro: () -> Void
let displayLockedInfo: () -> Void
init( init(
context: AccountContext, context: AccountContext,
@ -71,7 +72,8 @@ private final class SelectivePrivacySettingsControllerArguments {
setPublicPhoto: (() -> Void)?, setPublicPhoto: (() -> Void)?,
removePublicPhoto: (() -> Void)?, removePublicPhoto: (() -> Void)?,
updateHideReadTime: ((Bool) -> Void)?, updateHideReadTime: ((Bool) -> Void)?,
openPremiumIntro: @escaping () -> Void openPremiumIntro: @escaping () -> Void,
displayLockedInfo: @escaping () -> Void
) { ) {
self.context = context self.context = context
self.updateType = updateType self.updateType = updateType
@ -84,6 +86,7 @@ private final class SelectivePrivacySettingsControllerArguments {
self.removePublicPhoto = removePublicPhoto self.removePublicPhoto = removePublicPhoto
self.updateHideReadTime = updateHideReadTime self.updateHideReadTime = updateHideReadTime
self.openPremiumIntro = openPremiumIntro self.openPremiumIntro = openPremiumIntro
self.displayLockedInfo = displayLockedInfo
} }
} }
@ -116,9 +119,9 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
case forwardsPreviewHeader(PresentationTheme, String) case forwardsPreviewHeader(PresentationTheme, String)
case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, String) case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, String)
case settingHeader(PresentationTheme, String) case settingHeader(PresentationTheme, String)
case everybody(PresentationTheme, String, Bool) case everybody(PresentationTheme, String, Bool, Bool)
case contacts(PresentationTheme, String, Bool) case contacts(PresentationTheme, String, Bool, Bool)
case nobody(PresentationTheme, String, Bool) case nobody(PresentationTheme, String, Bool, Bool)
case settingInfo(PresentationTheme, String, String) case settingInfo(PresentationTheme, String, String)
case exceptionsHeader(PresentationTheme, String) case exceptionsHeader(PresentationTheme, String)
case disableFor(PresentationTheme, String, String) case disableFor(PresentationTheme, String, String)
@ -260,20 +263,20 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
} else { } else {
return false return false
} }
case let .everybody(lhsTheme, lhsText, lhsValue): case let .everybody(lhsTheme, lhsText, lhsValue, lhsIsLocked):
if case let .everybody(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .everybody(rhsTheme, rhsText, rhsValue, rhsIsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsIsLocked == rhsIsLocked {
return true return true
} else { } else {
return false return false
} }
case let .contacts(lhsTheme, lhsText, lhsValue): case let .contacts(lhsTheme, lhsText, lhsValue, lhsIsLocked):
if case let .contacts(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .contacts(rhsTheme, rhsText, rhsValue, rhsIsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsIsLocked == rhsIsLocked {
return true return true
} else { } else {
return false return false
} }
case let .nobody(lhsTheme, lhsText, lhsValue): case let .nobody(lhsTheme, lhsText, lhsValue, lhsIsLocked):
if case let .nobody(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .nobody(rhsTheme, rhsText, rhsValue, rhsIsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsIsLocked == rhsIsLocked {
return true return true
} else { } else {
return false return false
@ -450,17 +453,28 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: tooltipText) return ForwardPrivacyChatPreviewItem(context: arguments.context, theme: theme, strings: strings, sectionId: self.section, fontSize: fontSize, chatBubbleCorners: chatBubbleCorners, wallpaper: wallpaper, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, peerName: peerName, linkEnabled: linkEnabled, tooltipText: tooltipText)
case let .settingHeader(_, text): case let .settingHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section)
case let .everybody(_, text, value): case let .everybody(_, text, value, isLocked):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, zeroSeparatorInsets: false, sectionId: self.section, action: {
if isLocked {
} else {
arguments.updateType(.everybody) arguments.updateType(.everybody)
}
}) })
case let .contacts(_, text, value): case let .contacts(_, text, value, isLocked):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, zeroSeparatorInsets: false, sectionId: self.section, action: {
if isLocked {
arguments.displayLockedInfo()
} else {
arguments.updateType(.contacts) arguments.updateType(.contacts)
}
}) })
case let .nobody(_, text, value): case let .nobody(_, text, value, isLocked):
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { return ItemListCheckboxItem(presentationData: presentationData, icon: !isLocked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: text, style: .left, checked: value && !isLocked, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateType(.nobody) if isLocked {
arguments.displayLockedInfo()
} else {
arguments.updateType(.contacts)
}
}) })
case let .settingInfo(_, text, link): case let .settingInfo(_, text, link):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
@ -696,10 +710,16 @@ private struct SelectivePrivacySettingsControllerState: Equatable {
private func selectivePrivacySettingsControllerEntries(presentationData: PresentationData, kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState, peerName: String, phoneNumber: String, peer: EnginePeer?, publicPhoto: TelegramMediaImage?) -> [SelectivePrivacySettingsEntry] { private func selectivePrivacySettingsControllerEntries(presentationData: PresentationData, kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState, peerName: String, phoneNumber: String, peer: EnginePeer?, publicPhoto: TelegramMediaImage?) -> [SelectivePrivacySettingsEntry] {
var entries: [SelectivePrivacySettingsEntry] = [] var entries: [SelectivePrivacySettingsEntry] = []
let isPremium = peer?.isPremium ?? false
let settingTitle: String let settingTitle: String
let settingInfoText: String? let settingInfoText: String?
let disableForText: String let disableForText: String
let enableForText: String let enableForText: String
let phoneLink = "https://t.me/+\(phoneNumber)"
var settingInfoLink = phoneLink
switch kind { switch kind {
case .presence: case .presence:
settingTitle = presentationData.strings.PrivacyLastSeenSettings_WhoCanSeeMyTimestamp settingTitle = presentationData.strings.PrivacyLastSeenSettings_WhoCanSeeMyTimestamp
@ -737,7 +757,12 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
enableForText = presentationData.strings.PrivacyLastSeenSettings_AlwaysShareWith enableForText = presentationData.strings.PrivacyLastSeenSettings_AlwaysShareWith
case .voiceMessages: case .voiceMessages:
settingTitle = presentationData.strings.Privacy_VoiceMessages_WhoCanSend settingTitle = presentationData.strings.Privacy_VoiceMessages_WhoCanSend
if isPremium {
settingInfoText = presentationData.strings.Privacy_VoiceMessages_CustomHelp settingInfoText = presentationData.strings.Privacy_VoiceMessages_CustomHelp
} else {
settingInfoText = presentationData.strings.Privacy_VoiceMessages_NonPremiumHelp
settingInfoLink = "premium"
}
disableForText = presentationData.strings.Privacy_GroupsAndChannels_NeverAllow disableForText = presentationData.strings.Privacy_GroupsAndChannels_NeverAllow
enableForText = presentationData.strings.Privacy_GroupsAndChannels_AlwaysAllow enableForText = presentationData.strings.Privacy_GroupsAndChannels_AlwaysAllow
case .bio: case .bio:
@ -767,13 +792,18 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
entries.append(.settingHeader(presentationData.theme, settingTitle)) entries.append(.settingHeader(presentationData.theme, settingTitle))
entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody)) if case .voiceMessages = kind {
entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts)) entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody || !isPremium, false))
entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody)) entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts && isPremium, !isPremium))
entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody && isPremium, !isPremium))
} else {
entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody, false))
entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts, false))
entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody, false))
}
let phoneLink = "https://t.me/+\(phoneNumber)"
if let settingInfoText = settingInfoText { if let settingInfoText = settingInfoText {
entries.append(.settingInfo(presentationData.theme, settingInfoText, phoneLink)) entries.append(.settingInfo(presentationData.theme, settingInfoText, settingInfoLink))
} }
if case .phoneNumber = kind, state.setting == .nobody { if case .phoneNumber = kind, state.setting == .nobody {
@ -783,6 +813,9 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomPublicLink("+\(phoneNumber)").string : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp, phoneLink)) entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomPublicLink("+\(phoneNumber)").string : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp, phoneLink))
} }
if case .voiceMessages = kind, !isPremium {
} else {
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions)) entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions))
switch state.setting { switch state.setting {
@ -808,6 +841,7 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
exceptionsInfo = presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp exceptionsInfo = presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp
} }
entries.append(.peersInfo(presentationData.theme, exceptionsInfo)) entries.append(.peersInfo(presentationData.theme, exceptionsInfo))
}
if case .voiceCalls = kind, let p2pMode = state.callP2PMode, let integrationAvailable = state.callIntegrationAvailable, let integrationEnabled = state.callIntegrationEnabled { if case .voiceCalls = kind, let p2pMode = state.callP2PMode, let integrationAvailable = state.callIntegrationAvailable, let integrationEnabled = state.callIntegrationEnabled {
entries.append(.callsP2PHeader(presentationData.theme, presentationData.strings.Privacy_Calls_P2P.uppercased())) entries.append(.callsP2PHeader(presentationData.theme, presentationData.strings.Privacy_Calls_P2P.uppercased()))
@ -1149,10 +1183,15 @@ func selectivePrivacySettingsController(
return state.withUpdatedPhoneDiscoveryEnabled(value) return state.withUpdatedPhoneDiscoveryEnabled(value)
} }
}, copyPhoneLink: { link in }, copyPhoneLink: { link in
if link == "premium" {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil)
pushControllerImpl?(controller, true)
} else {
UIPasteboard.general.string = link UIPasteboard.general.string = link
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}
}, setPublicPhoto: { }, setPublicPhoto: {
requestPublicPhotoSetup?({ result in requestPublicPhotoSetup?({ result in
var result = result var result = result
@ -1183,6 +1222,16 @@ func selectivePrivacySettingsController(
}, openPremiumIntro: { }, openPremiumIntro: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil) let controller = context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil)
pushControllerImpl?(controller, true) pushControllerImpl?(controller, true)
}, displayLockedInfo: {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: presentationData.strings.Privacy_Messages_PremiumToast_Title, text: presentationData.strings.Privacy_Messages_PremiumToast_Text, customUndoText: presentationData.strings.Privacy_Messages_PremiumToast_Action, timeout: nil, linkAction: { _ in
}), elevatedLayout: false, action: { action in
if case .undo = action {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil)
pushControllerImpl?(controller, true)
}
return false
}), nil)
}) })
let peer = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) let peer = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))

View File

@ -222,6 +222,11 @@ private func mergedResult(_ state: SearchMessagesState) -> SearchMessagesResult
} }
func _internal_searchMessages(account: Account, location: SearchMessagesLocation, query: String, state: SearchMessagesState?, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> { func _internal_searchMessages(account: Account, location: SearchMessagesLocation, query: String, state: SearchMessagesState?, limit: Int32 = 100) -> Signal<(SearchMessagesResult, SearchMessagesState), NoError> {
if case let .peer(peerId, fromId, tags, reactions, threadId, minDate, maxDate) = location, fromId == nil, tags == nil, let reactions, !reactions.isEmpty, threadId == nil, minDate == nil, maxDate == 0 {
let _ = peerId
print("short cirquit")
}
let remoteSearchResult: Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError> let remoteSearchResult: Signal<(Api.messages.Messages?, Api.messages.Messages?), NoError>
switch location { switch location {
case let .peer(peerId, fromId, tags, reactions, threadId, minDate, maxDate): case let .peer(peerId, fromId, tags, reactions, threadId, minDate, maxDate):

View File

@ -214,6 +214,7 @@ public enum PresentationResourceKey: Int32 {
case chatInputSearchPanelCalendarImage case chatInputSearchPanelCalendarImage
case chatInputSearchPanelMembersImage case chatInputSearchPanelMembersImage
case chatHistoryNavigationButtonBackground
case chatHistoryNavigationButtonImage case chatHistoryNavigationButtonImage
case chatHistoryNavigationUpButtonImage case chatHistoryNavigationUpButtonImage
case chatHistoryMentionsButtonImage case chatHistoryMentionsButtonImage

View File

@ -586,15 +586,24 @@ public struct PresentationResourcesChat {
}) })
} }
public static func chatHistoryNavigationButtonBackground(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatHistoryNavigationButtonBackground.rawValue, { theme in
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setLineWidth(0.5)
context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor)
context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5)))
})
})
}
public static func chatHistoryNavigationButtonImage(_ theme: PresentationTheme) -> UIImage? { public static func chatHistoryNavigationButtonImage(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.chatHistoryNavigationButtonImage.rawValue, { theme in return theme.image(PresentationResourceKey.chatHistoryNavigationButtonImage.rawValue, { theme in
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setLineWidth(0.5)
context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor) context.setStrokeColor(theme.rootController.navigationBar.accentTextColor.cgColor)
context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5)))
context.setStrokeColor(theme.chat.historyNavigation.foregroundColor.cgColor)
context.setLineWidth(1.5) context.setLineWidth(1.5)
let position = CGPoint(x: 9.0 - 0.5, y: 23.0) let position = CGPoint(x: 9.0 - 0.5, y: 23.0)
@ -610,10 +619,8 @@ public struct PresentationResourcesChat {
return theme.image(PresentationResourceKey.chatHistoryNavigationUpButtonImage.rawValue, { theme in return theme.image(PresentationResourceKey.chatHistoryNavigationUpButtonImage.rawValue, { theme in
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setLineWidth(0.5)
context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor) context.setStrokeColor(theme.rootController.navigationBar.accentTextColor.cgColor)
context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5)))
context.setStrokeColor(theme.chat.historyNavigation.foregroundColor.cgColor)
context.setLineWidth(1.5) context.setLineWidth(1.5)
context.translateBy(x: size.width * 0.5, y: size.height * 0.5) context.translateBy(x: size.width * 0.5, y: size.height * 0.5)
@ -632,11 +639,6 @@ public struct PresentationResourcesChat {
return theme.image(PresentationResourceKey.chatHistoryMentionsButtonImage.rawValue, { theme in return theme.image(PresentationResourceKey.chatHistoryMentionsButtonImage.rawValue, { theme in
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.chat.historyNavigation.fillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.5, y: 0.5), size: CGSize(width: size.width - 1.0, height: size.height - 1.0)))
context.setLineWidth(0.5)
context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor)
context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5)))
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigateToMentions"), color: theme.chat.historyNavigation.foregroundColor), let cgImage = image.cgImage { if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/NavigateToMentions"), color: theme.chat.historyNavigation.foregroundColor), let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
@ -649,11 +651,6 @@ public struct PresentationResourcesChat {
return theme.image(PresentationResourceKey.chatHistoryReactionsButtonImage.rawValue, { theme in return theme.image(PresentationResourceKey.chatHistoryReactionsButtonImage.rawValue, { theme in
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(theme.chat.historyNavigation.fillColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.5, y: 0.5), size: CGSize(width: size.width - 1.0, height: size.height - 1.0)))
context.setLineWidth(0.5)
context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor)
context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5)))
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.chat.historyNavigation.foregroundColor), let cgImage = image.cgImage { if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.chat.historyNavigation.foregroundColor), let cgImage = image.cgImage {
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) context.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))

View File

@ -1279,7 +1279,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
self.statusNode = statusNode self.statusNode = statusNode
self.addSubnode(statusNode) self.addSubnode(statusNode)
statusNode.reactionSelected = { [weak self] value in statusNode.reactionSelected = { [weak self] _, value in
guard let self, let message = self.message else { guard let self, let message = self.message else {
return return
} }

View File

@ -55,7 +55,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode {
self.addButtonNode.addTarget(self, action: #selector(self.addButtonPressed), forControlEvents: .touchUpInside) self.addButtonNode.addTarget(self, action: #selector(self.addButtonPressed), forControlEvents: .touchUpInside)
self.messageButtonNode.addTarget(self, action: #selector(self.messageButtonPressed), forControlEvents: .touchUpInside) self.messageButtonNode.addTarget(self, action: #selector(self.messageButtonPressed), forControlEvents: .touchUpInside)
self.dateAndStatusNode.reactionSelected = { [weak self] value in self.dateAndStatusNode.reactionSelected = { [weak self] _, value in
guard let strongSelf = self, let item = strongSelf.item else { guard let strongSelf = self, let item = strongSelf.item else {
return return
} }

View File

@ -316,7 +316,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} }
} }
} }
public var reactionSelected: ((MessageReaction.Reaction) -> Void)? public var reactionSelected: ((ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void)?
public var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)? public var openReactionPreview: ((ContextGesture?, ContextExtractedContentContainingView, MessageReaction.Reaction) -> Void)?
override public init() { override public init() {
@ -739,11 +739,11 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
resultingHeight = layoutSize.height resultingHeight = layoutSize.height
reactionButtonsResult = reactionButtonsContainer.update( reactionButtonsResult = reactionButtonsContainer.update(
context: arguments.context, context: arguments.context,
action: { value in action: { itemNode, value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.reactionSelected?(value) strongSelf.reactionSelected?(itemNode, value)
}, },
reactions: [], reactions: [],
colors: reactionColors, colors: reactionColors,
@ -752,6 +752,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
) )
case let .trailingContent(contentWidth, reactionSettings): case let .trailingContent(contentWidth, reactionSettings):
if let reactionSettings = reactionSettings, !reactionSettings.displayInline { if let reactionSettings = reactionSettings, !reactionSettings.displayInline {
let isTag = arguments.areReactionsTags
var totalReactionCount: Int = 0 var totalReactionCount: Int = 0
for reaction in arguments.reactions { for reaction in arguments.reactions {
totalReactionCount += Int(reaction.count) totalReactionCount += Int(reaction.count)
@ -759,11 +761,15 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
reactionButtonsResult = reactionButtonsContainer.update( reactionButtonsResult = reactionButtonsContainer.update(
context: arguments.context, context: arguments.context,
action: { value in action: { itemNode, value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.reactionSelected?(value) if isTag {
strongSelf.openReactionPreview?(nil, itemNode.containerView, value)
} else {
strongSelf.reactionSelected?(itemNode, value)
}
}, },
reactions: arguments.reactions.map { reaction in reactions: arguments.reactions.map { reaction in
var centerAnimation: TelegramMediaFile? var centerAnimation: TelegramMediaFile?
@ -815,11 +821,11 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
} else { } else {
reactionButtonsResult = reactionButtonsContainer.update( reactionButtonsResult = reactionButtonsContainer.update(
context: arguments.context, context: arguments.context,
action: { value in action: { itemNode, value in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.reactionSelected?(value) strongSelf.reactionSelected?(itemNode, value)
}, },
reactions: [], reactions: [],
colors: reactionColors, colors: reactionColors,

View File

@ -64,7 +64,7 @@ public class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] value in self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] _, value in
guard let strongSelf = self, let item = strongSelf.item else { guard let strongSelf = self, let item = strongSelf.item else {
return return
} }

View File

@ -130,7 +130,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode,
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.dateAndStatusNode.reactionSelected = { [weak self] value in self.dateAndStatusNode.reactionSelected = { [weak self] _, value in
guard let strongSelf = self, let item = strongSelf.item else { guard let strongSelf = self, let item = strongSelf.item else {
return return
} }

View File

@ -144,7 +144,7 @@ public class ChatMessageInstantVideoBubbleContentNode: ChatMessageBubbleContentN
} }
} }
self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] value in self.interactiveFileNode.dateAndStatusNode.reactionSelected = { [weak self] _, value in
guard let strongSelf = self, let item = strongSelf.item else { guard let strongSelf = self, let item = strongSelf.item else {
return return
} }

View File

@ -120,11 +120,15 @@ public final class MessageReactionButtonsNode: ASDisplayNode {
let reactionButtonsResult = self.container.update( let reactionButtonsResult = self.container.update(
context: context, context: context,
action: { [weak self] value in action: { [weak self] itemView, value in
guard let strongSelf = self else { guard let self else {
return return
} }
strongSelf.reactionSelected?(value) if reactions.isTags {
self.openReactionPreview?(nil, itemView.containerView, value)
} else {
self.reactionSelected?(value)
}
}, },
reactions: reactions.reactions.map { reaction in reactions: reactions.reactions.map { reaction in
var centerAnimation: TelegramMediaFile? var centerAnimation: TelegramMediaFile?

View File

@ -714,7 +714,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.addSubnode(statusNode) strongSelf.addSubnode(statusNode)
statusNode.reactionSelected = { [weak strongSelf] value in statusNode.reactionSelected = { [weak strongSelf] _, value in
guard let strongSelf, let item = strongSelf.item else { guard let strongSelf, let item = strongSelf.item else {
return return
} }

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "tag_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,95 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 2.334961 4.834961 cm
0.000000 0.000000 0.000000 scn
3.665000 14.330017 m
1.640877 14.330017 0.000000 12.689140 0.000000 10.665017 c
0.000000 3.665017 l
0.000000 1.640893 1.640876 0.000017 3.665000 0.000017 c
11.913676 0.000017 l
13.215668 0.000017 14.458418 0.544132 15.341534 1.500839 c
18.901434 5.357393 l
19.843767 6.378253 19.843767 7.951766 18.901438 8.972626 c
15.341534 12.829191 l
14.458418 13.785901 13.215667 14.330017 11.913673 14.330017 c
3.665000 14.330017 l
h
1.330000 10.665017 m
1.330000 11.954601 2.375415 13.000017 3.665000 13.000017 c
11.913673 13.000017 l
12.844466 13.000017 13.732906 12.611030 14.364244 11.927080 c
17.924149 8.070515 l
18.396196 7.559127 18.396196 6.770894 17.924147 6.259506 c
14.364245 2.402952 l
13.732907 1.719004 12.844467 1.330017 11.913676 1.330017 c
3.665000 1.330017 l
2.375415 1.330017 1.330000 2.375432 1.330000 3.665017 c
1.330000 10.665017 l
h
13.665039 5.665024 m
14.493466 5.665024 15.165039 6.336596 15.165039 7.165024 c
15.165039 7.993451 14.493466 8.665024 13.665039 8.665024 c
12.836612 8.665024 12.165039 7.993451 12.165039 7.165024 c
12.165039 6.336596 12.836612 5.665024 13.665039 5.665024 c
h
f*
n
Q
endstream
endobj
3 0 obj
1231
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001321 00000 n
0000001344 00000 n
0000001517 00000 n
0000001591 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1650
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "tagfilter_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,258 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 24.000000 24.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 2.334961 4.834961 cm
0.000000 0.000000 0.000000 scn
7.665000 14.330017 m
7.297730 14.330017 7.000000 14.032287 7.000000 13.665017 c
7.000000 13.297748 7.297730 13.000017 7.665000 13.000017 c
11.913673 13.000017 l
12.844466 13.000017 13.732906 12.611030 14.364244 11.927080 c
17.924149 8.070515 l
18.396196 7.559127 18.396196 6.770894 17.924147 6.259506 c
14.364245 2.402952 l
13.732907 1.719004 12.844467 1.330017 11.913676 1.330017 c
3.665000 1.330017 l
2.375415 1.330017 1.330000 2.375432 1.330000 3.665016 c
1.330000 5.165009 l
1.330000 5.532279 1.032269 5.830009 0.665000 5.830009 c
0.297731 5.830009 0.000000 5.532279 0.000000 5.165009 c
0.000000 3.665016 l
0.000000 1.640892 1.640877 0.000017 3.665000 0.000017 c
11.913676 0.000017 l
13.215668 0.000017 14.458418 0.544132 15.341534 1.500839 c
18.901434 5.357393 l
19.843767 6.378253 19.843767 7.951766 18.901438 8.972626 c
15.341534 12.829191 l
14.458418 13.785901 13.215667 14.330017 11.913673 14.330017 c
7.665000 14.330017 l
h
13.665039 5.665024 m
14.493466 5.665024 15.165039 6.336596 15.165039 7.165024 c
15.165039 7.993451 14.493466 8.665024 13.665039 8.665024 c
12.836612 8.665024 12.165039 7.993451 12.165039 7.165024 c
12.165039 6.336596 12.836612 5.665024 13.665039 5.665024 c
h
f*
n
Q
q
1.000000 0.000000 -0.000000 1.000000 0.000000 10.080505 cm
0.000000 0.000000 0.000000 scn
3.445300 2.622628 m
3.076424 2.069314 l
3.445300 2.622628 l
h
4.445300 1.955961 m
4.076425 1.402648 l
4.445300 1.955961 l
h
6.240744 6.700362 m
6.745649 6.267586 l
6.240744 6.700362 l
h
1.414964 8.268703 m
0.910058 7.835927 l
1.414964 8.268703 l
h
2.174221 9.254495 m
6.825779 9.254495 l
6.825779 10.584495 l
2.174221 10.584495 l
2.174221 9.254495 l
h
7.080131 8.701480 m
5.735838 7.133138 l
6.745649 6.267586 l
8.089942 7.835927 l
7.080131 8.701480 l
h
5.335000 6.049571 m
5.335000 2.788012 l
6.665000 2.788012 l
6.665000 6.049571 l
5.335000 6.049571 l
h
4.814176 2.509274 m
3.814175 3.175941 l
3.076424 2.069314 l
4.076425 1.402648 l
4.814176 2.509274 l
h
3.665000 3.454679 m
3.665000 6.049571 l
2.335000 6.049571 l
2.335000 3.454679 l
3.665000 3.454679 l
h
3.264162 7.133138 m
1.919870 8.701480 l
0.910058 7.835927 l
2.254351 6.267586 l
3.264162 7.133138 l
h
3.665000 6.049571 m
3.665000 6.447025 3.522822 6.831368 3.264162 7.133138 c
2.254351 6.267586 l
2.306394 6.206869 2.335000 6.129539 2.335000 6.049571 c
3.665000 6.049571 l
h
3.814175 3.175941 m
3.720979 3.238072 3.665000 3.342670 3.665000 3.454679 c
2.335000 3.454679 l
2.335000 2.897980 2.613223 2.378115 3.076424 2.069314 c
3.814175 3.175941 l
h
5.335000 2.788012 m
5.335000 2.520448 5.036801 2.360857 4.814176 2.509274 c
4.076425 1.402648 l
5.182908 0.664992 6.665000 1.458183 6.665000 2.788012 c
5.335000 2.788012 l
h
5.735838 7.133138 m
5.477178 6.831368 5.335000 6.447025 5.335000 6.049571 c
6.665000 6.049571 l
6.665000 6.129539 6.693606 6.206869 6.745649 6.267586 c
5.735838 7.133138 l
h
6.825779 9.254495 m
7.111988 9.254495 7.266392 8.918785 7.080131 8.701480 c
8.089942 7.835927 l
9.015692 8.915968 8.248278 10.584495 6.825779 10.584495 c
6.825779 9.254495 l
h
2.174221 10.584495 m
0.751723 10.584495 -0.015691 8.915968 0.910058 7.835927 c
1.919870 8.701480 l
1.733608 8.918785 1.888012 9.254495 2.174221 9.254495 c
2.174221 10.584495 l
h
f
n
Q
endstream
endobj
2 0 obj
3329
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 24.000000 24.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 24.000000 m
24.000000 24.000000 l
24.000000 0.000000 l
0.000000 0.000000 l
0.000000 24.000000 l
h
f
n
Q
endstream
endobj
4 0 obj
232
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000003587 00000 n
0000003610 00000 n
0000004090 00000 n
0000004112 00000 n
0000004410 00000 n
0000004512 00000 n
0000004533 00000 n
0000004706 00000 n
0000004780 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
4840
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "tagcrossed_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,109 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 2.334961 3.205139 cm
0.000000 0.000000 0.000000 scn
1.135265 17.265064 m
0.875566 17.524763 0.454512 17.524763 0.194813 17.265064 c
-0.064886 17.005365 -0.064886 16.584312 0.194813 16.324614 c
16.194813 0.324612 l
16.454512 0.064913 16.875566 0.064913 17.135265 0.324612 c
17.394964 0.584311 17.394964 1.005365 17.135265 1.265064 c
15.306840 3.093491 l
15.318465 3.105813 15.330030 3.118204 15.341534 3.130667 c
18.901434 6.987221 l
19.843767 8.008080 19.843767 9.581594 18.901438 10.602453 c
15.341534 14.459019 l
14.458418 15.415730 13.215667 15.959845 11.913673 15.959845 c
3.665000 15.959845 l
3.293929 15.959845 2.935738 15.904698 2.598176 15.802155 c
1.135265 17.265064 l
h
3.770485 14.629845 m
14.365832 4.034498 l
17.924147 7.889334 l
18.396196 8.400722 18.396196 9.188954 17.924149 9.700342 c
14.364244 13.556908 l
13.732906 14.240857 12.844466 14.629845 11.913673 14.629845 c
3.770485 14.629845 l
h
0.000000 12.294845 m
0.000000 13.066666 0.238580 13.782763 0.646016 14.373411 c
1.611738 13.407689 l
1.432060 13.076872 1.330000 12.697777 1.330000 12.294845 c
1.330000 5.294845 l
1.330000 4.005260 2.375415 2.959845 3.665000 2.959845 c
11.913676 2.959845 l
11.961413 2.959845 12.009040 2.960868 12.056525 2.962901 c
13.206793 1.812634 l
12.790398 1.692487 12.355447 1.629845 11.913676 1.629845 c
3.665000 1.629845 l
1.640876 1.629845 0.000000 3.270720 0.000000 5.294845 c
0.000000 12.294845 l
h
13.665039 7.294851 m
14.493466 7.294851 15.165039 7.966424 15.165039 8.794851 c
15.165039 9.623279 14.493466 10.294851 13.665039 10.294851 c
12.836612 10.294851 12.165039 9.623279 12.165039 8.794851 c
12.165039 7.966424 12.836612 7.294851 13.665039 7.294851 c
h
f*
n
Q
endstream
endobj
3 0 obj
1740
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000001830 00000 n
0000001853 00000 n
0000002026 00000 n
0000002100 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2159
%%EOF

View File

@ -519,4 +519,6 @@ func updateChatPresentationInterfaceStateImpl(
} else { } else {
selfController.chatDisplayNode.historyNode.updateTag(tag: nil) selfController.chatDisplayNode.historyNode.updateTag(tag: nil)
} }
selfController.updateDownButtonVisibility()
} }

View File

@ -6953,9 +6953,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
if self.presentationInterfaceState.search != nil && self.presentationInterfaceState.historyFilter != nil { if self.presentationInterfaceState.search != nil && self.presentationInterfaceState.historyFilter != nil {
self.chatDisplayNode.historyNode.addAfterTransactionsCompleted { [weak self] in
guard let self else {
return
}
self.chatDisplayNode.dismissInput() self.chatDisplayNode.dismissInput()
} }
} }
}
self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode, isTracking in self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, transition, itemNode, isTracking in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -6987,6 +6993,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.chatDisplayNode.loadingPlaceholderNode?.addContentOffset(offset: offset, transition: transition) strongSelf.chatDisplayNode.loadingPlaceholderNode?.addContentOffset(offset: offset, transition: transition)
} }
strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode)
} }
self.chatDisplayNode.historyNode.hasPlentyOfMessagesUpdated = { [weak self] hasPlentyOfMessages in self.chatDisplayNode.historyNode.hasPlentyOfMessagesUpdated = { [weak self] hasPlentyOfMessages in
@ -8170,22 +8177,37 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
self.chatDisplayNode.navigateButtons.downPressed = { [weak self] in self.chatDisplayNode.navigateButtons.downPressed = { [weak self] in
if let strongSelf = self, strongSelf.isNodeLoaded { guard let self else {
if let messageId = strongSelf.historyNavigationStack.removeLast() { return
strongSelf.navigateToMessage(from: nil, to: .id(messageId.id, NavigateToMessageParams(timestamp: nil, quote: nil)), rememberInStack: false) }
if self.presentationInterfaceState.search?.resultsState != nil {
self.interfaceInteraction?.navigateMessageSearch(.later)
} else { } else {
if case .known = strongSelf.chatDisplayNode.historyNode.visibleContentOffset() { if let messageId = self.historyNavigationStack.removeLast() {
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() self.navigateToMessage(from: nil, to: .id(messageId.id, NavigateToMessageParams(timestamp: nil, quote: nil)), rememberInStack: false)
} else if case .peer = strongSelf.chatLocation {
strongSelf.scrollToEndOfHistory()
} else if case .replyThread = strongSelf.chatLocation {
strongSelf.scrollToEndOfHistory()
} else { } else {
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() if case .known = self.chatDisplayNode.historyNode.visibleContentOffset() {
self.chatDisplayNode.historyNode.scrollToEndOfHistory()
} else if case .peer = self.chatLocation {
self.scrollToEndOfHistory()
} else if case .replyThread = self.chatLocation {
self.scrollToEndOfHistory()
} else {
self.chatDisplayNode.historyNode.scrollToEndOfHistory()
} }
} }
} }
} }
self.chatDisplayNode.navigateButtons.upPressed = { [weak self] in
guard let self else {
return
}
if self.presentationInterfaceState.search?.resultsState != nil {
self.interfaceInteraction?.navigateMessageSearch(.earlier)
}
}
self.chatDisplayNode.navigateButtons.mentionsPressed = { [weak self] in self.chatDisplayNode.navigateButtons.mentionsPressed = { [weak self] in
if let strongSelf = self, strongSelf.isNodeLoaded, let peerId = strongSelf.chatLocation.peerId { if let strongSelf = self, strongSelf.isNodeLoaded, let peerId = strongSelf.chatLocation.peerId {
@ -9054,7 +9076,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return state.updatedSearch(ChatSearchData(query: "", domain: .members, domainSuggestionContext: .none, resultsState: nil)) return state.updatedSearch(ChatSearchData(query: "", domain: .members, domainSuggestionContext: .none, resultsState: nil))
} else if let search = state.search { } else if let search = state.search {
switch search.domain { switch search.domain {
case .everything: case .everything, .tag:
return state return state
case .members: case .members:
return state.updatedSearch(ChatSearchData(query: "", domain: .everything, domainSuggestionContext: .none, resultsState: nil)) return state.updatedSearch(ChatSearchData(query: "", domain: .everything, domainSuggestionContext: .none, resultsState: nil))
@ -10800,7 +10822,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
return state.updatedHistoryFilter(updatedFilter) var state = state.updatedHistoryFilter(updatedFilter)
if let updatedFilter, !updatedFilter.isActive, let reactionData = updatedFilter.customTags.first, let reaction = ReactionsMessageAttribute.reactionFromMessageTag(tag: reactionData) {
state = state.updatedSearch(ChatSearchData(domain: .tag(reaction)))
} else {
state = state.updatedSearch(ChatSearchData())
}
return state
}) })
}, requestLayout: { [weak self] transition in }, requestLayout: { [weak self] transition in
if let strongSelf = self, let layout = strongSelf.validLayout { if let strongSelf = self, let layout = strongSelf.validLayout {
@ -15665,8 +15693,34 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
func updateDownButtonVisibility() { func updateDownButtonVisibility() {
if let search = self.presentationInterfaceState.search, let results = search.resultsState {
let resultCount = results.messageIndices.count
var resultIndex: Int?
if let currentId = results.currentId, let index = results.messageIndices.firstIndex(where: { $0.id == currentId }) {
resultIndex = index
} else {
resultIndex = nil
}
if let resultIndex {
self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState(
up: ChatHistoryNavigationButtons.ButtonState(isEnabled: resultIndex != 0),
down: ChatHistoryNavigationButtons.ButtonState(isEnabled: resultIndex != resultCount - 1)
)
} else {
self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState(
up: ChatHistoryNavigationButtons.ButtonState(isEnabled: false),
down: ChatHistoryNavigationButtons.ButtonState(isEnabled: false)
)
}
} else {
let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil || self.presentationInterfaceState.recordedMediaPreview != nil let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil || self.presentationInterfaceState.recordedMediaPreview != nil
self.chatDisplayNode.navigateButtons.displayDownButton = self.shouldDisplayDownButton && !recordingMediaMessage
self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState(
up: nil,
down: (self.shouldDisplayDownButton && !recordingMediaMessage) ? ChatHistoryNavigationButtons.ButtonState(isEnabled: true) : nil
)
}
} }
func updateTextInputState(_ textInputState: ChatTextInputState) { func updateTextInputState(_ textInputState: ChatTextInputState) {

View File

@ -920,6 +920,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if hasChatThemeScreen { if hasChatThemeScreen {
return true return true
} }
if strongSelf.chatPresentationInterfaceState.search != nil {
return true
}
return false return false
} }
@ -2655,7 +2660,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
if let _ = self.chatPresentationInterfaceState.search, let interfaceInteraction = self.interfaceInteraction { if let _ = self.chatPresentationInterfaceState.search, let interfaceInteraction = self.interfaceInteraction {
var activate = false var activate = false
if self.searchNavigationNode == nil { if self.searchNavigationNode == nil {
if !self.chatPresentationInterfaceState.hasSearchTags {
activate = true activate = true
}
self.searchNavigationNode = ChatSearchNavigationContentNode(context: self.context, theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, chatLocation: self.chatPresentationInterfaceState.chatLocation, interaction: interfaceInteraction, presentationInterfaceState: self.chatPresentationInterfaceState) self.searchNavigationNode = ChatSearchNavigationContentNode(context: self.context, theme: self.chatPresentationInterfaceState.theme, strings: self.chatPresentationInterfaceState.strings, chatLocation: self.chatPresentationInterfaceState.chatLocation, interaction: interfaceInteraction, presentationInterfaceState: self.chatPresentationInterfaceState)
} }
self.navigationBar?.setContentNode(self.searchNavigationNode, animated: transitionIsAnimated) self.navigationBar?.setContentNode(self.searchNavigationNode, animated: transitionIsAnimated)

View File

@ -19,23 +19,26 @@ extension ChatControllerImpl {
if message.areReactionsTags(accountPeerId: self.context.account.peerId) { if message.areReactionsTags(accountPeerId: self.context.account.peerId) {
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_ReactionContextMenu_FilterByTag, icon: { _ in let tags: [EngineMessage.CustomTag] = [ReactionsMessageAttribute.messageTag(reaction: value)]
return nil
if self.presentationInterfaceState.historyFilter?.customTags != tags {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_ReactionContextMenu_FilterByTag, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/TagFilter"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
guard let self else { guard let self else {
a(.default) a(.default)
return return
} }
self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in self.interfaceInteraction?.updateHistoryFilter { _ in
return state return ChatPresentationInterfaceState.HistoryFilter(customTags: tags, isActive: true)
.updatedSearch(ChatSearchData()) }
.updatedHistoryFilter(ChatPresentationInterfaceState.HistoryFilter(customTags: [ReactionsMessageAttribute.messageTag(reaction: value)], isActive: true))
})
a(.default) a(.default)
}))) })))
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_ReactionContextMenu_RemoveTag, textColor: .destructive, icon: { _ in }
return nil
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_ReactionContextMenu_RemoveTag, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/TagRemove"), color: theme.contextMenu.destructiveColor)
}, action: { [weak self] _, a in }, action: { [weak self] _, a in
a(.dismissWithoutContent) a(.dismissWithoutContent)
guard let self else { guard let self else {

View File

@ -47,6 +47,8 @@ extension ChatControllerImpl {
switch search.domain { switch search.domain {
case .everything: case .everything:
derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil, reactions: reactions, threadId: threadId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState)) derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil, reactions: reactions, threadId: threadId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState))
case let .tag(reaction):
derivedSearchState = ChatSearchState(query: search.query, location: .peer(peerId: peerId, fromId: nil, tags: nil, reactions: reactions ?? [reaction], threadId: threadId, minDate: nil, maxDate: nil), loadMoreState: loadMoreStateFromResultsState(search.resultsState))
case .members: case .members:
derivedSearchState = nil derivedSearchState = nil
case let .member(peer): case let .member(peer):

View File

@ -3684,6 +3684,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
} }
public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets, additionalScrollDistance: CGFloat, scrollToTop: Bool, completion: @escaping () -> Void) { public func updateLayout(transition: ContainedViewLayoutTransition, updateSizeAndInsets: ListViewUpdateSizeAndInsets, additionalScrollDistance: CGFloat, scrollToTop: Bool, completion: @escaping () -> Void) {
/*if updateSizeAndInsets.insets.top == 83.0 {
if !transition.isAnimated {
assert(true)
}
}*/
var scrollToItem: ListViewScrollToItem? var scrollToItem: ListViewScrollToItem?
var postScrollToItem: ListViewScrollToItem? var postScrollToItem: ListViewScrollToItem?
if scrollToTop, case .known = self.visibleContentOffset() { if scrollToTop, case .known = self.visibleContentOffset() {

View File

@ -17,10 +17,11 @@ enum ChatHistoryNavigationButtonType {
class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
let containerNode: ContextExtractedContentContainingNode let containerNode: ContextExtractedContentContainingNode
private let buttonNode: HighlightTrackingButtonNode let buttonNode: HighlightTrackingButtonNode
private let backgroundNode: NavigationBackgroundNode private let backgroundNode: NavigationBackgroundNode
private var backgroundContent: WallpaperBubbleBackgroundNode? private var backgroundContent: WallpaperBubbleBackgroundNode?
private let imageNode: ASImageNode let backgroundImageNode: ASImageNode
let imageNode: ASImageNode
private let badgeBackgroundNode: ASImageNode private let badgeBackgroundNode: ASImageNode
private let badgeTextNode: ImmediateAnimatedCountLabelNode private let badgeTextNode: ImmediateAnimatedCountLabelNode
@ -56,6 +57,11 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
self.backgroundNode = NavigationBackgroundNode(color: theme.chat.inputPanel.panelBackgroundColor) self.backgroundNode = NavigationBackgroundNode(color: theme.chat.inputPanel.panelBackgroundColor)
self.backgroundImageNode = ASImageNode()
self.backgroundImageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBackground(theme)
self.backgroundImageNode.isLayerBacked = true
self.backgroundImageNode.displayWithoutProcessing = true
self.imageNode = ASImageNode() self.imageNode = ASImageNode()
self.imageNode.displayWithoutProcessing = true self.imageNode.displayWithoutProcessing = true
switch type { switch type {
@ -99,7 +105,9 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: size.width / 2.0, transition: .immediate) self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: size.width / 2.0, transition: .immediate)
self.buttonNode.addSubnode(self.backgroundImageNode)
self.buttonNode.addSubnode(self.imageNode) self.buttonNode.addSubnode(self.imageNode)
self.backgroundImageNode.frame = CGRect(origin: CGPoint(), size: size)
self.imageNode.frame = CGRect(origin: CGPoint(), size: size) self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
self.buttonNode.addSubnode(self.badgeBackgroundNode) self.buttonNode.addSubnode(self.badgeBackgroundNode)
@ -123,6 +131,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
case .reactions: case .reactions:
self.imageNode.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme) self.imageNode.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme)
} }
self.backgroundImageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBackground(theme)
self.badgeBackgroundNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBadgeImage(theme) self.badgeBackgroundNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBadgeImage(theme)
var segments: [AnimatedCountLabelNode.Segment] = [] var segments: [AnimatedCountLabelNode.Segment] = []

View File

@ -1,4 +1,5 @@
import Foundation import Foundation
import Foundation
import UIKit import UIKit
import Display import Display
import AsyncDisplayKit import AsyncDisplayKit
@ -6,13 +7,32 @@ import TelegramPresentationData
import WallpaperBackgroundNode import WallpaperBackgroundNode
final class ChatHistoryNavigationButtons: ASDisplayNode { final class ChatHistoryNavigationButtons: ASDisplayNode {
struct ButtonState: Equatable {
var isEnabled: Bool
init(isEnabled: Bool) {
self.isEnabled = isEnabled
}
}
struct DirectionState: Equatable {
var up: ButtonState?
var down: ButtonState?
init(up: ButtonState?, down: ButtonState?) {
self.up = up
self.down = down
}
}
private var theme: PresentationTheme private var theme: PresentationTheme
private var dateTimeFormat: PresentationDateTimeFormat private var dateTimeFormat: PresentationDateTimeFormat
private let isChatRotated: Bool private let isChatRotated: Bool
let reactionsButton: ChatHistoryNavigationButtonNode let reactionsButton: ChatHistoryNavigationButtonNode
let mentionsButton: ChatHistoryNavigationButtonNode let mentionsButton: ChatHistoryNavigationButtonNode
private let downButton: ChatHistoryNavigationButtonNode let downButton: ChatHistoryNavigationButtonNode
let upButton: ChatHistoryNavigationButtonNode
var downPressed: (() -> Void)? { var downPressed: (() -> Void)? {
didSet { didSet {
@ -20,12 +40,18 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
} }
} }
var upPressed: (() -> Void)? {
didSet {
self.upButton.tapped = self.upPressed
}
}
var reactionsPressed: (() -> Void)? var reactionsPressed: (() -> Void)?
var mentionsPressed: (() -> Void)? var mentionsPressed: (() -> Void)?
var displayDownButton: Bool = false { var directionButtonState: DirectionState = DirectionState(up: nil, down: nil) {
didSet { didSet {
if oldValue != self.displayDownButton { if oldValue != self.directionButtonState {
let _ = self.updateLayout(transition: .animated(duration: 0.3, curve: .spring)) let _ = self.updateLayout(transition: .animated(duration: 0.3, curve: .spring))
} }
} }
@ -86,11 +112,16 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
self.downButton.alpha = 0.0 self.downButton.alpha = 0.0
self.downButton.isHidden = true self.downButton.isHidden = true
self.upButton = ChatHistoryNavigationButtonNode(theme: theme, backgroundNode: backgroundNode, type: isChatRotated ? .up : .down)
self.upButton.alpha = 0.0
self.upButton.isHidden = true
super.init() super.init()
self.addSubnode(self.reactionsButton) self.addSubnode(self.reactionsButton)
self.addSubnode(self.mentionsButton) self.addSubnode(self.mentionsButton)
self.addSubnode(self.downButton) self.addSubnode(self.downButton)
self.addSubnode(self.upButton)
self.reactionsButton.tapped = { [weak self] in self.reactionsButton.tapped = { [weak self] in
self?.reactionsPressed?() self?.reactionsPressed?()
@ -101,6 +132,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
} }
self.downButton.isGestureEnabled = false self.downButton.isGestureEnabled = false
self.upButton.isGestureEnabled = false
} }
override func didLoad() { override func didLoad() {
@ -114,6 +146,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
self.reactionsButton.updateTheme(theme: theme, backgroundNode: backgroundNode) self.reactionsButton.updateTheme(theme: theme, backgroundNode: backgroundNode)
self.mentionsButton.updateTheme(theme: theme, backgroundNode: backgroundNode) self.mentionsButton.updateTheme(theme: theme, backgroundNode: backgroundNode)
self.downButton.updateTheme(theme: theme, backgroundNode: backgroundNode) self.downButton.updateTheme(theme: theme, backgroundNode: backgroundNode)
self.upButton.updateTheme(theme: theme, backgroundNode: backgroundNode)
} }
private var absoluteRect: (CGRect, CGSize)? private var absoluteRect: (CGRect, CGSize)?
@ -130,6 +163,11 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
mentionsFrame.origin.y += rect.minY mentionsFrame.origin.y += rect.minY
self.mentionsButton.update(rect: mentionsFrame, within: containerSize, transition: transition) self.mentionsButton.update(rect: mentionsFrame, within: containerSize, transition: transition)
var upFrame = self.upButton.frame
upFrame.origin.x += rect.minX
upFrame.origin.y += rect.minY
self.upButton.update(rect: upFrame, within: containerSize, transition: transition)
var downFrame = self.downButton.frame var downFrame = self.downButton.frame
downFrame.origin.x += rect.minX downFrame.origin.x += rect.minX
downFrame.origin.y += rect.minY downFrame.origin.y += rect.minY
@ -139,11 +177,16 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
func updateLayout(transition: ContainedViewLayoutTransition) -> CGSize { func updateLayout(transition: ContainedViewLayoutTransition) -> CGSize {
let buttonSize = CGSize(width: 38.0, height: 38.0) let buttonSize = CGSize(width: 38.0, height: 38.0)
let completeSize = CGSize(width: buttonSize.width, height: buttonSize.height * 2.0 + 12.0) let completeSize = CGSize(width: buttonSize.width, height: buttonSize.height * 2.0 + 12.0)
var upOffset: CGFloat = 0.0
var mentionsOffset: CGFloat = 0.0 var mentionsOffset: CGFloat = 0.0
var reactionsOffset: CGFloat = 0.0 var reactionsOffset: CGFloat = 0.0
if self.displayDownButton { if let down = self.directionButtonState.down {
self.downButton.imageNode.alpha = down.isEnabled ? 1.0 : 0.5
self.downButton.buttonNode.isEnabled = down.isEnabled
mentionsOffset += buttonSize.height + 12.0 mentionsOffset += buttonSize.height + 12.0
upOffset += buttonSize.height + 12.0
self.downButton.isHidden = false self.downButton.isHidden = false
transition.updateAlpha(node: self.downButton, alpha: 1.0) transition.updateAlpha(node: self.downButton, alpha: 1.0)
@ -158,6 +201,25 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
transition.updateTransformScale(node: self.downButton, scale: 0.2) transition.updateTransformScale(node: self.downButton, scale: 0.2)
} }
if let up = self.directionButtonState.up {
self.upButton.imageNode.alpha = up.isEnabled ? 1.0 : 0.5
self.upButton.buttonNode.isEnabled = up.isEnabled
mentionsOffset += buttonSize.height + 12.0
self.upButton.isHidden = false
transition.updateAlpha(node: self.upButton, alpha: 1.0)
transition.updateTransformScale(node: self.upButton, scale: 1.0)
} else {
transition.updateAlpha(node: self.upButton, alpha: 0.0, completion: { [weak self] completed in
guard let strongSelf = self, completed else {
return
}
strongSelf.upButton.isHidden = true
})
transition.updateTransformScale(node: self.upButton, scale: 0.2)
}
if self.mentionCount != 0 { if self.mentionCount != 0 {
reactionsOffset += buttonSize.height + 12.0 reactionsOffset += buttonSize.height + 12.0
@ -190,10 +252,12 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
if self.isChatRotated { if self.isChatRotated {
transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height), size: buttonSize).center) transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height), size: buttonSize).center)
transition.updatePosition(node: self.upButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - upOffset), size: buttonSize).center)
transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize).center) transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize).center)
transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset - reactionsOffset), size: buttonSize).center) transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset - reactionsOffset), size: buttonSize).center)
} else { } else {
transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: buttonSize).center) transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: buttonSize).center)
transition.updatePosition(node: self.upButton, position: CGRect(origin: CGPoint(x: 0.0, y: upOffset), size: buttonSize).center)
transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: mentionsOffset), size: buttonSize).center) transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: mentionsOffset), size: buttonSize).center)
transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: mentionsOffset + reactionsOffset), size: buttonSize).center) transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: mentionsOffset + reactionsOffset), size: buttonSize).center)
} }

View File

@ -65,8 +65,8 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode {
super.init() super.init()
self.addSubnode(self.upButton) //self.addSubnode(self.upButton)
self.addSubnode(self.downButton) //self.addSubnode(self.downButton)
self.addSubnode(self.calendarButton) self.addSubnode(self.calendarButton)
self.addSubnode(self.membersButton) self.addSubnode(self.membersButton)
self.addSubnode(self.resultsButton) self.addSubnode(self.resultsButton)

View File

@ -105,7 +105,7 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), strings: presentationInterfaceState.strings) self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), strings: presentationInterfaceState.strings)
switch search.domain { switch search.domain {
case .everything: case .everything, .tag:
self.searchBar.tokens = [] self.searchBar.tokens = []
self.searchBar.prefixString = nil self.searchBar.prefixString = nil
let placeholderText: String let placeholderText: String

View File

@ -372,6 +372,10 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc
return ChatPresentationInterfaceState.HistoryFilter(customTags: tags, isActive: filter?.isActive ?? true) return ChatPresentationInterfaceState.HistoryFilter(customTags: tags, isActive: filter?.isActive ?? true)
} }
}) })
if let itemView = self.itemViews[reaction] {
self.scrollView.scrollRectToVisible(itemView.frame.insetBy(dx: -46.0, dy: 0.0), animated: true)
}
}) })
self.itemViews[itemId] = itemView self.itemViews[itemId] = itemView
self.scrollView.addSubview(itemView) self.scrollView.addSubview(itemView)