diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c8f5f104ae..53bdfbb7c0 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10934,7 +10934,7 @@ Sorry for the inconvenience."; "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.EmptyStateMessagingRestrictedToPremium.Text" = "Subscribe to **Premium**\nto message **%@**."; @@ -10974,3 +10974,5 @@ Sorry for the inconvenience."; "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."; + +"Privacy.VoiceMessages.NonPremiumHelp" = "Subscribe to Telegram Premium to restrict who can send you voice or video messages.\n\n[What is Telegram Premium?]()"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index eb7203647a..f4e511baba 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -356,6 +356,7 @@ public enum ChatSearchDomain: Equatable { case everything case members case member(Peer) + case tag(MessageReaction.Reaction) public static func ==(lhs: ChatSearchDomain, rhs: ChatSearchDomain) -> Bool { switch lhs { @@ -377,6 +378,12 @@ public enum ChatSearchDomain: Equatable { } else { return false } + case let .tag(reaction): + if case .tag(reaction) = rhs { + return true + } else { + return false + } } } } diff --git a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift index ea516e88a4..1f2ef066a7 100644 --- a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift +++ b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift @@ -821,7 +821,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView { guard let layout = self.layout else { 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) { @@ -1061,7 +1061,7 @@ public final class ReactionButtonComponent: Equatable { public let isTag: Bool public let count: Int public let chosenOrder: Int? - public let action: (MessageReaction.Reaction) -> Void + public let action: (ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void public init( context: AccountContext, @@ -1071,7 +1071,7 @@ public final class ReactionButtonComponent: Equatable { isTag: Bool, count: Int, chosenOrder: Int?, - action: @escaping (MessageReaction.Reaction) -> Void + action: @escaping (ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void ) { self.context = context self.colors = colors @@ -1218,7 +1218,7 @@ public final class ReactionButtonsAsyncLayoutContainer { public func update( context: AccountContext, - action: @escaping (MessageReaction.Reaction) -> Void, + action: @escaping (ReactionButtonAsyncNode, MessageReaction.Reaction) -> Void, reactions: [ReactionButtonsAsyncLayoutContainer.Reaction], colors: ReactionButtonComponent.Colors, isTag: Bool, diff --git a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift index 75d68e4d2b..04acdb4281 100644 --- a/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerExtractedPresentationNode.swift @@ -1586,6 +1586,11 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo if self.reactionContextNodeIsAnimatingOut, let reactionContextNode = self.reactionContextNode { reactionContextNode.bounds = reactionContextNode.bounds.offsetBy(dx: 0.0, dy: 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) + } } } } diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index ce2f60b0d0..2cfe0d9871 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -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) } + 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 }) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil && additionalScrollDistance.isZero { if let updateOpaqueState = updateOpaqueState { @@ -2640,6 +2647,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if let updateSizeAndInsets = updateSizeAndInsets { if updateSizeAndInsets.size != self.visibleSize || updateSizeAndInsets.insets != self.insets { sizeOrInsetsUpdated = true + if updateSizeAndInsets.insets.top == 83.0 && updateSizeAndInsets.duration < 0.5 { + assert(true) + } } } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index f7fa749839..aaa964e47c 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -133,9 +133,16 @@ private final class TitleLabelView: UIView { } 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( 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: {}, 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) } + 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 { reactionComponentView.alpha = 0.0 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) { + if self.allPresetReactionsAreAvailable { + return + } + let highlightedReaction = self.previewReaction(at: location)?.reaction if self.highlightedReaction != highlightedReaction { self.highlightedReaction = highlightedReaction @@ -2489,10 +2504,18 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { } public func highlightGestureFinished(performAction: Bool) { + if self.allPresetReactionsAreAvailable { + return + } + self.highlightGestureFinished(performAction: performAction, isLarge: false) } private func highlightGestureFinished(performAction: Bool, isLarge: Bool) { + if self.allPresetReactionsAreAvailable { + return + } + if let highlightedReaction = self.highlightedReaction { self.highlightedReaction = nil if performAction { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index 86651729cb..9b1a27d4bc 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -391,7 +391,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { arguments.openGroupsPrivacy() }) 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() }) case let .messagePrivacy(theme, value, hasPremium): @@ -933,7 +933,6 @@ public func privacyAndSecurityController( } })) }, openVoiceMessagePrivacy: { - let presentationData = context.sharedContext.currentPresentationData.with { $0 } let signal = combineLatest( context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), privacySettingsPromise.get() @@ -941,42 +940,22 @@ public func privacyAndSecurityController( |> take(1) |> deliverOnMainQueue currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] peer, info in - let isPremium = peer?.isPremium ?? false - - if isPremium { - if let info = info { - pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceMessages, current: info.voiceMessages, updated: { updated, _, _, _ in - if let currentInfoDisposable = currentInfoDisposable { - let applySetting: Signal = privacySettingsPromise.get() - |> filter { $0 != nil } - |> take(1) - |> deliverOnMainQueue - |> mapToSignal { value -> Signal in - if let value = value { - privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: updated, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) - } - return .complete() - } - currentInfoDisposable.set(applySetting.start()) + if let info = info { + pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceMessages, current: info.voiceMessages, updated: { updated, _, _, _ in + if let currentInfoDisposable = currentInfoDisposable { + let applySetting: Signal = privacySettingsPromise.get() + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue + |> mapToSignal { value -> Signal in + if let value = value { + privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: updated, bio: value.bio, globalSettings: value.globalSettings, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: value.messageAutoremoveTimeout))) + } + return .complete() } - }), 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 + currentInfoDisposable.set(applySetting.start()) } - return false - })) + }), true) } })) }, openBioPrivacy: { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index 7ed9d32999..1208292028 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -59,6 +59,7 @@ private final class SelectivePrivacySettingsControllerArguments { let removePublicPhoto: (() -> Void)? let updateHideReadTime: ((Bool) -> Void)? let openPremiumIntro: () -> Void + let displayLockedInfo: () -> Void init( context: AccountContext, @@ -71,7 +72,8 @@ private final class SelectivePrivacySettingsControllerArguments { setPublicPhoto: (() -> Void)?, removePublicPhoto: (() -> Void)?, updateHideReadTime: ((Bool) -> Void)?, - openPremiumIntro: @escaping () -> Void + openPremiumIntro: @escaping () -> Void, + displayLockedInfo: @escaping () -> Void ) { self.context = context self.updateType = updateType @@ -84,6 +86,7 @@ private final class SelectivePrivacySettingsControllerArguments { self.removePublicPhoto = removePublicPhoto self.updateHideReadTime = updateHideReadTime self.openPremiumIntro = openPremiumIntro + self.displayLockedInfo = displayLockedInfo } } @@ -116,9 +119,9 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case forwardsPreviewHeader(PresentationTheme, String) case forwardsPreview(PresentationTheme, TelegramWallpaper, PresentationFontSize, PresentationChatBubbleCorners, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, String, Bool, String) case settingHeader(PresentationTheme, String) - case everybody(PresentationTheme, String, Bool) - case contacts(PresentationTheme, String, Bool) - case nobody(PresentationTheme, String, Bool) + case everybody(PresentationTheme, String, Bool, Bool) + case contacts(PresentationTheme, String, Bool, Bool) + case nobody(PresentationTheme, String, Bool, Bool) case settingInfo(PresentationTheme, String, String) case exceptionsHeader(PresentationTheme, String) case disableFor(PresentationTheme, String, String) @@ -260,20 +263,20 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { } else { return false } - case let .everybody(lhsTheme, lhsText, lhsValue): - if case let .everybody(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + case let .everybody(lhsTheme, lhsText, lhsValue, lhsIsLocked): + if case let .everybody(rhsTheme, rhsText, rhsValue, rhsIsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsIsLocked == rhsIsLocked { return true } else { return false } - case let .contacts(lhsTheme, lhsText, lhsValue): - if case let .contacts(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + case let .contacts(lhsTheme, lhsText, lhsValue, lhsIsLocked): + if case let .contacts(rhsTheme, rhsText, rhsValue, rhsIsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsIsLocked == rhsIsLocked { return true } else { return false } - case let .nobody(lhsTheme, lhsText, lhsValue): - if case let .nobody(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + case let .nobody(lhsTheme, lhsText, lhsValue, lhsIsLocked): + if case let .nobody(rhsTheme, rhsText, rhsValue, rhsIsLocked) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue, lhsIsLocked == rhsIsLocked { return true } else { 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) case let .settingHeader(_, text): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, multiline: true, sectionId: self.section) - case let .everybody(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { - arguments.updateType(.everybody) + case let .everybody(_, text, value, isLocked): + 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) + } }) - case let .contacts(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { - arguments.updateType(.contacts) + case let .contacts(_, text, value, isLocked): + 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) + } }) - case let .nobody(_, text, value): - return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .left, checked: value, zeroSeparatorInsets: false, sectionId: self.section, action: { - arguments.updateType(.nobody) + case let .nobody(_, text, value, isLocked): + 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) + } }) case let .settingInfo(_, text, link): 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] { var entries: [SelectivePrivacySettingsEntry] = [] + let isPremium = peer?.isPremium ?? false + let settingTitle: String let settingInfoText: String? let disableForText: String let enableForText: String + + let phoneLink = "https://t.me/+\(phoneNumber)" + var settingInfoLink = phoneLink + switch kind { case .presence: settingTitle = presentationData.strings.PrivacyLastSeenSettings_WhoCanSeeMyTimestamp @@ -737,7 +757,12 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present enableForText = presentationData.strings.PrivacyLastSeenSettings_AlwaysShareWith case .voiceMessages: settingTitle = presentationData.strings.Privacy_VoiceMessages_WhoCanSend - settingInfoText = presentationData.strings.Privacy_VoiceMessages_CustomHelp + if isPremium { + settingInfoText = presentationData.strings.Privacy_VoiceMessages_CustomHelp + } else { + settingInfoText = presentationData.strings.Privacy_VoiceMessages_NonPremiumHelp + settingInfoLink = "premium" + } disableForText = presentationData.strings.Privacy_GroupsAndChannels_NeverAllow enableForText = presentationData.strings.Privacy_GroupsAndChannels_AlwaysAllow case .bio: @@ -767,13 +792,18 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present entries.append(.settingHeader(presentationData.theme, settingTitle)) - entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody)) - entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts)) - entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody)) - - let phoneLink = "https://t.me/+\(phoneNumber)" + if case .voiceMessages = kind { + entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody || !isPremium, false)) + 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)) + } + 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 { @@ -782,32 +812,36 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present entries.append(.phoneDiscoveryMyContacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.phoneDiscoveryEnabled == false)) entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomPublicLink("+\(phoneNumber)").string : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp, phoneLink)) } + + if case .voiceMessages = kind, !isPremium { - entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions)) - - switch state.setting { - case .everybody: - entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, strings: presentationData.strings))) - case .contacts: - entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, strings: presentationData.strings))) - entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, strings: presentationData.strings))) - case .nobody: - entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, strings: presentationData.strings))) - } - let exceptionsInfo: String - if case .profilePhoto = kind { - switch state.setting { - case .nobody: - exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideAddInfo - case .contacts: - exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideBothInfo - case .everybody: - exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideInfo - } } else { - exceptionsInfo = presentationData.strings.PrivacyLastSeenSettings_CustomShareSettingsHelp + entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions)) + + switch state.setting { + case .everybody: + entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, strings: presentationData.strings))) + case .contacts: + entries.append(.disableFor(presentationData.theme, disableForText, stringForUserCount(state.disableFor, strings: presentationData.strings))) + entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, strings: presentationData.strings))) + case .nobody: + entries.append(.enableFor(presentationData.theme, enableForText, stringForUserCount(state.enableFor, strings: presentationData.strings))) + } + let exceptionsInfo: String + if case .profilePhoto = kind { + switch state.setting { + case .nobody: + exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideAddInfo + case .contacts: + exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideBothInfo + case .everybody: + exceptionsInfo = presentationData.strings.Privacy_ProfilePhoto_CustomOverrideInfo + } + } else { + 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 { entries.append(.callsP2PHeader(presentationData.theme, presentationData.strings.Privacy_Calls_P2P.uppercased())) @@ -1149,10 +1183,15 @@ func selectivePrivacySettingsController( return state.withUpdatedPhoneDiscoveryEnabled(value) } }, copyPhoneLink: { link in - UIPasteboard.general.string = link - - 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) + if link == "premium" { + let controller = context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil) + pushControllerImpl?(controller, true) + } else { + UIPasteboard.general.string = link + + 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) + } }, setPublicPhoto: { requestPublicPhotoSetup?({ result in var result = result @@ -1183,6 +1222,16 @@ func selectivePrivacySettingsController( }, openPremiumIntro: { let controller = context.sharedContext.makePremiumIntroController(context: context, source: .presence, forceDark: false, dismissed: nil) 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)) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift index 9ed290d7a3..f128baed3e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/SearchMessages.swift @@ -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> { + 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> switch location { case let .peer(peerId, fromId, tags, reactions, threadId, minDate, maxDate): diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 82cb5b1475..d88a54c12e 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -214,6 +214,7 @@ public enum PresentationResourceKey: Int32 { case chatInputSearchPanelCalendarImage case chatInputSearchPanelMembersImage + case chatHistoryNavigationButtonBackground case chatHistoryNavigationButtonImage case chatHistoryNavigationUpButtonImage case chatHistoryMentionsButtonImage diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 561da4688d..4e334ff8c8 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -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? { return theme.image(PresentationResourceKey.chatHistoryNavigationButtonImage.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))) - context.setStrokeColor(theme.chat.historyNavigation.foregroundColor.cgColor) + + context.setStrokeColor(theme.rootController.navigationBar.accentTextColor.cgColor) context.setLineWidth(1.5) 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 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))) - context.setStrokeColor(theme.chat.historyNavigation.foregroundColor.cgColor) + + context.setStrokeColor(theme.rootController.navigationBar.accentTextColor.cgColor) context.setLineWidth(1.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 generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in 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 { 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 generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in 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 { 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)) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift index cc4c9ecd40..806fe1c87a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAttachedContentNode/Sources/ChatMessageAttachedContentNode.swift @@ -1279,7 +1279,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode { self.statusNode = statusNode self.addSubnode(statusNode) - statusNode.reactionSelected = { [weak self] value in + statusNode.reactionSelected = { [weak self] _, value in guard let self, let message = self.message else { return } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift index 2007ed9f71..4edfda2b05 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageContactBubbleContentNode/Sources/ChatMessageContactBubbleContentNode.swift @@ -55,7 +55,7 @@ public class ChatMessageContactBubbleContentNode: ChatMessageBubbleContentNode { self.addButtonNode.addTarget(self, action: #selector(self.addButtonPressed), 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 { return } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift index e5b2bfb4ab..5c4fd88b1b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift @@ -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)? override public init() { @@ -739,11 +739,11 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { resultingHeight = layoutSize.height reactionButtonsResult = reactionButtonsContainer.update( context: arguments.context, - action: { value in + action: { itemNode, value in guard let strongSelf = self else { return } - strongSelf.reactionSelected?(value) + strongSelf.reactionSelected?(itemNode, value) }, reactions: [], colors: reactionColors, @@ -752,6 +752,8 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { ) case let .trailingContent(contentWidth, reactionSettings): if let reactionSettings = reactionSettings, !reactionSettings.displayInline { + let isTag = arguments.areReactionsTags + var totalReactionCount: Int = 0 for reaction in arguments.reactions { totalReactionCount += Int(reaction.count) @@ -759,11 +761,15 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { reactionButtonsResult = reactionButtonsContainer.update( context: arguments.context, - action: { value in + action: { itemNode, value in guard let strongSelf = self else { return } - strongSelf.reactionSelected?(value) + if isTag { + strongSelf.openReactionPreview?(nil, itemNode.containerView, value) + } else { + strongSelf.reactionSelected?(itemNode, value) + } }, reactions: arguments.reactions.map { reaction in var centerAnimation: TelegramMediaFile? @@ -815,11 +821,11 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { } else { reactionButtonsResult = reactionButtonsContainer.update( context: arguments.context, - action: { value in + action: { itemNode, value in guard let strongSelf = self else { return } - strongSelf.reactionSelected?(value) + strongSelf.reactionSelected?(itemNode, value) }, reactions: [], colors: reactionColors, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift index 386ffb0fd3..ccc5e9d838 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageFileBubbleContentNode/Sources/ChatMessageFileBubbleContentNode.swift @@ -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 { return } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index bc3e25fceb..1393d68bf5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -130,7 +130,7 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, 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 { return } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift index 71bcca0c73..46dabc66dc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoBubbleContentNode/Sources/ChatMessageInstantVideoBubbleContentNode.swift @@ -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 { return } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift index c73151e2ec..c8fda01799 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageReactionsFooterContentNode/Sources/ChatMessageReactionsFooterContentNode.swift @@ -120,11 +120,15 @@ public final class MessageReactionButtonsNode: ASDisplayNode { let reactionButtonsResult = self.container.update( context: context, - action: { [weak self] value in - guard let strongSelf = self else { + action: { [weak self] itemView, value in + guard let self else { return } - strongSelf.reactionSelected?(value) + if reactions.isTags { + self.openReactionPreview?(nil, itemView.containerView, value) + } else { + self.reactionSelected?(value) + } }, reactions: reactions.reactions.map { reaction in var centerAnimation: TelegramMediaFile? diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index f08f74aaee..7c2ce808ab 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -714,7 +714,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.addSubnode(statusNode) - statusNode.reactionSelected = { [weak strongSelf] value in + statusNode.reactionSelected = { [weak strongSelf] _, value in guard let strongSelf, let item = strongSelf.item else { return } diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/Contents.json new file mode 100644 index 0000000000..d2403414fe --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tag_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/tag_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/tag_24.pdf new file mode 100644 index 0000000000..e962456c80 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Tag.imageset/tag_24.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/Contents.json new file mode 100644 index 0000000000..9cdaca5f2d --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagfilter_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/tagfilter_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/tagfilter_24.pdf new file mode 100644 index 0000000000..69572192e2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagFilter.imageset/tagfilter_24.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/Contents.json new file mode 100644 index 0000000000..7d5bd6818e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "tagcrossed_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/tagcrossed_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/tagcrossed_24.pdf new file mode 100644 index 0000000000..ed941d07ea --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/TagRemove.imageset/tagcrossed_24.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 0bc700c38e..d9946e3b38 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -519,4 +519,6 @@ func updateChatPresentationInterfaceStateImpl( } else { selfController.chatDisplayNode.historyNode.updateTag(tag: nil) } + + selfController.updateDownButtonVisibility() } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 2d30768ac4..851ffc10a3 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -6953,7 +6953,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } if self.presentationInterfaceState.search != nil && self.presentationInterfaceState.historyFilter != nil { - self.chatDisplayNode.dismissInput() + self.chatDisplayNode.historyNode.addAfterTransactionsCompleted { [weak self] in + guard let self else { + return + } + + self.chatDisplayNode.dismissInput() + } } } @@ -6987,6 +6993,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.chatDisplayNode.loadingPlaceholderNode?.addContentOffset(offset: offset, transition: transition) } strongSelf.chatDisplayNode.messageTransitionNode.addExternalOffset(offset: offset, transition: transition, itemNode: itemNode) + } 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 - if let strongSelf = self, strongSelf.isNodeLoaded { - if let messageId = strongSelf.historyNavigationStack.removeLast() { - strongSelf.navigateToMessage(from: nil, to: .id(messageId.id, NavigateToMessageParams(timestamp: nil, quote: nil)), rememberInStack: false) + guard let self else { + return + } + + if self.presentationInterfaceState.search?.resultsState != nil { + self.interfaceInteraction?.navigateMessageSearch(.later) + } else { + if let messageId = self.historyNavigationStack.removeLast() { + self.navigateToMessage(from: nil, to: .id(messageId.id, NavigateToMessageParams(timestamp: nil, quote: nil)), rememberInStack: false) } else { - if case .known = strongSelf.chatDisplayNode.historyNode.visibleContentOffset() { - strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() - } else if case .peer = strongSelf.chatLocation { - strongSelf.scrollToEndOfHistory() - } else if case .replyThread = strongSelf.chatLocation { - strongSelf.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 { - strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() + 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 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)) } else if let search = state.search { switch search.domain { - case .everything: + case .everything, .tag: return state case .members: 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 if let strongSelf = self, let layout = strongSelf.validLayout { @@ -15665,8 +15693,34 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } func updateDownButtonVisibility() { - let recordingMediaMessage = self.audioRecorderValue != nil || self.videoRecorderValue != nil || self.presentationInterfaceState.recordedMediaPreview != nil - self.chatDisplayNode.navigateButtons.displayDownButton = self.shouldDisplayDownButton && !recordingMediaMessage + 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 + + self.chatDisplayNode.navigateButtons.directionButtonState = ChatHistoryNavigationButtons.DirectionState( + up: nil, + down: (self.shouldDisplayDownButton && !recordingMediaMessage) ? ChatHistoryNavigationButtons.ButtonState(isEnabled: true) : nil + ) + } } func updateTextInputState(_ textInputState: ChatTextInputState) { diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index e0ba51e1c8..e0193c9f7b 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -920,6 +920,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if hasChatThemeScreen { return true } + + if strongSelf.chatPresentationInterfaceState.search != nil { + return true + } + return false } @@ -2655,7 +2660,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { if let _ = self.chatPresentationInterfaceState.search, let interfaceInteraction = self.interfaceInteraction { var activate = false if self.searchNavigationNode == nil { - activate = true + if !self.chatPresentationInterfaceState.hasSearchTags { + 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.navigationBar?.setContentNode(self.searchNavigationNode, animated: transitionIsAnimated) diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift index a8572dacda..90c759f813 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenMessageReactionContextMenu.swift @@ -19,23 +19,26 @@ extension ChatControllerImpl { if message.areReactionsTags(accountPeerId: self.context.account.peerId) { var items: [ContextMenuItem] = [] - items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Chat_ReactionContextMenu_FilterByTag, icon: { _ in - return nil - }, action: { [weak self] _, a in - guard let self else { + let tags: [EngineMessage.CustomTag] = [ReactionsMessageAttribute.messageTag(reaction: value)] + + 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 + guard let self else { + a(.default) + return + } + self.interfaceInteraction?.updateHistoryFilter { _ in + return ChatPresentationInterfaceState.HistoryFilter(customTags: tags, isActive: true) + } + a(.default) - return - } - self.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in - return state - .updatedSearch(ChatSearchData()) - .updatedHistoryFilter(ChatPresentationInterfaceState.HistoryFilter(customTags: [ReactionsMessageAttribute.messageTag(reaction: value)], isActive: true)) - }) - - 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 a(.dismissWithoutContent) guard let self else { diff --git a/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift b/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift index f6fcc52e1f..bebaeba770 100644 --- a/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift +++ b/submodules/TelegramUI/Sources/ChatControllerUpdateSearch.swift @@ -47,6 +47,8 @@ extension ChatControllerImpl { switch search.domain { 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)) + 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: derivedSearchState = nil case let .member(peer): diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 55e919f1a0..f41ea99096 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -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) { + /*if updateSizeAndInsets.insets.top == 83.0 { + if !transition.isAnimated { + assert(true) + } + }*/ var scrollToItem: ListViewScrollToItem? var postScrollToItem: ListViewScrollToItem? if scrollToTop, case .known = self.visibleContentOffset() { diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift index 7dc96eea3e..9648d94310 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtonNode.swift @@ -17,10 +17,11 @@ enum ChatHistoryNavigationButtonType { class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { let containerNode: ContextExtractedContentContainingNode - private let buttonNode: HighlightTrackingButtonNode + let buttonNode: HighlightTrackingButtonNode private let backgroundNode: NavigationBackgroundNode private var backgroundContent: WallpaperBubbleBackgroundNode? - private let imageNode: ASImageNode + let backgroundImageNode: ASImageNode + let imageNode: ASImageNode private let badgeBackgroundNode: ASImageNode private let badgeTextNode: ImmediateAnimatedCountLabelNode @@ -56,6 +57,11 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { 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.displayWithoutProcessing = true switch type { @@ -99,7 +105,9 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) 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.backgroundImageNode.frame = CGRect(origin: CGPoint(), size: size) self.imageNode.frame = CGRect(origin: CGPoint(), size: size) self.buttonNode.addSubnode(self.badgeBackgroundNode) @@ -123,6 +131,7 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode { case .reactions: self.imageNode.image = PresentationResourcesChat.chatHistoryReactionsButtonImage(theme) } + self.backgroundImageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBackground(theme) self.badgeBackgroundNode.image = PresentationResourcesChat.chatHistoryNavigationButtonBadgeImage(theme) var segments: [AnimatedCountLabelNode.Segment] = [] diff --git a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift index 513bc1b85d..bf49d88053 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryNavigationButtons.swift @@ -1,4 +1,5 @@ import Foundation +import Foundation import UIKit import Display import AsyncDisplayKit @@ -6,13 +7,32 @@ import TelegramPresentationData import WallpaperBackgroundNode 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 dateTimeFormat: PresentationDateTimeFormat private let isChatRotated: Bool let reactionsButton: ChatHistoryNavigationButtonNode let mentionsButton: ChatHistoryNavigationButtonNode - private let downButton: ChatHistoryNavigationButtonNode + let downButton: ChatHistoryNavigationButtonNode + let upButton: ChatHistoryNavigationButtonNode var downPressed: (() -> Void)? { didSet { @@ -20,12 +40,18 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { } } + var upPressed: (() -> Void)? { + didSet { + self.upButton.tapped = self.upPressed + } + } + var reactionsPressed: (() -> Void)? var mentionsPressed: (() -> Void)? - var displayDownButton: Bool = false { + var directionButtonState: DirectionState = DirectionState(up: nil, down: nil) { didSet { - if oldValue != self.displayDownButton { + if oldValue != self.directionButtonState { 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.isHidden = true + self.upButton = ChatHistoryNavigationButtonNode(theme: theme, backgroundNode: backgroundNode, type: isChatRotated ? .up : .down) + self.upButton.alpha = 0.0 + self.upButton.isHidden = true + super.init() self.addSubnode(self.reactionsButton) self.addSubnode(self.mentionsButton) self.addSubnode(self.downButton) + self.addSubnode(self.upButton) self.reactionsButton.tapped = { [weak self] in self?.reactionsPressed?() @@ -101,6 +132,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { } self.downButton.isGestureEnabled = false + self.upButton.isGestureEnabled = false } override func didLoad() { @@ -114,6 +146,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { self.reactionsButton.updateTheme(theme: theme, backgroundNode: backgroundNode) self.mentionsButton.updateTheme(theme: theme, backgroundNode: backgroundNode) self.downButton.updateTheme(theme: theme, backgroundNode: backgroundNode) + self.upButton.updateTheme(theme: theme, backgroundNode: backgroundNode) } private var absoluteRect: (CGRect, CGSize)? @@ -130,6 +163,11 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { mentionsFrame.origin.y += rect.minY 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 downFrame.origin.x += rect.minX downFrame.origin.y += rect.minY @@ -139,11 +177,16 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { func updateLayout(transition: ContainedViewLayoutTransition) -> CGSize { let buttonSize = CGSize(width: 38.0, height: 38.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 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 + upOffset += buttonSize.height + 12.0 self.downButton.isHidden = false transition.updateAlpha(node: self.downButton, alpha: 1.0) @@ -158,6 +201,25 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { 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 { reactionsOffset += buttonSize.height + 12.0 @@ -190,10 +252,12 @@ final class ChatHistoryNavigationButtons: ASDisplayNode { 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.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.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset - reactionsOffset), size: buttonSize).center) } else { 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.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: mentionsOffset + reactionsOffset), size: buttonSize).center) } diff --git a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift index c620d0e2c3..2004cc0d1b 100644 --- a/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchInputPanelNode.swift @@ -65,8 +65,8 @@ final class ChatSearchInputPanelNode: ChatInputPanelNode { super.init() - self.addSubnode(self.upButton) - self.addSubnode(self.downButton) + //self.addSubnode(self.upButton) + //self.addSubnode(self.downButton) self.addSubnode(self.calendarButton) self.addSubnode(self.membersButton) self.addSubnode(self.resultsButton) diff --git a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift index 35bbdc093c..fdbd23678c 100644 --- a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift @@ -105,7 +105,7 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode { self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), strings: presentationInterfaceState.strings) switch search.domain { - case .everything: + case .everything, .tag: self.searchBar.tokens = [] self.searchBar.prefixString = nil let placeholderText: String diff --git a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift index 3838cb869b..5773890e3a 100644 --- a/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatSearchTitleAccessoryPanelNode.swift @@ -372,6 +372,10 @@ final class ChatSearchTitleAccessoryPanelNode: ChatTitleAccessoryPanelNode, UISc 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.scrollView.addSubview(itemView)