diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 071970e418..9ed8012508 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -12139,3 +12139,46 @@ Sorry for the inconvenience."; "Channel.AdminLog.ShowMoreMessages_1" = "Show %@ More Message"; "Channel.AdminLog.ShowMoreMessages_any" = "Show %@ More Messages"; + +"CreatePoll.OptionCountFooterFormat_1" = "You can add {count} more option."; +"CreatePoll.OptionCountFooterFormat_any" = "You can add {count} more options."; + +"Chat.AdminActionSheet.DeleteTitle_1" = "Delete 1 Message"; +"Chat.AdminActionSheet.DeleteTitle_any" = "Delete %d Messages"; +"Chat.AdminActionSheet.ReportSpam" = "Report Spam"; +"Chat.AdminActionSheet.DeleteAllSingle" = "Delete All from %@"; +"Chat.AdminActionSheet.DeleteAllMultiple" = "Delete All from Users"; +"Chat.AdminActionSheet.BanSingle" = "Ban %@"; +"Chat.AdminActionSheet.BanMultiple" = "Ban Users"; +"Chat.AdminActionSheet.RestrictSingle" = "Ban %@"; +"Chat.AdminActionSheet.RestrictMultiple" = "Ban Users"; +"Chat.AdminActionSheet.RestrictSectionHeader" = "ADDITIONAL ACTIONS"; +"Chat.AdminActionSheet.BanFooterSingle" = "Fully ban this user"; +"Chat.AdminActionSheet.RestrictFooterSingle" = "Partially restrict this user"; +"Chat.AdminActionSheet.BanFooterMultiple" = "Fully ban users"; +"Chat.AdminActionSheet.RestrictFooterMultiple" = "Partially restrict users"; +"Chat.AdminActionSheet.PermissionsSectionHeader" = "WHAT CAN THIS USER DO?"; +"Chat.AdminActionSheet.ActionButton" = "Proceed"; + +"Chat.AdminAction.ToastMessagesDeletedTitleSingle" = "Message Deleted"; +"Chat.AdminAction.ToastMessagesDeletedTitleMultiple" = "Messages Deleted"; +"Chat.AdminAction.ToastMessagesDeletedTextSingle" = "Message Deleted."; +"Chat.AdminAction.ToastMessagesDeletedTextMultiple" = "Messages Deleted."; +"Chat.AdminAction.ToastReportedSpamText_1" = "**1** user reported for spam."; +"Chat.AdminAction.ToastReportedSpamText_any" = "**%d** users reported for spam."; +"Chat.AdminAction.ToastBannedText_1" = "**1** user banned."; +"Chat.AdminAction.ToastBannedText_any" = "**%d** users banned."; +"Chat.AdminAction.ToastRestrictedText_1" = "**1** user restricted."; +"Chat.AdminAction.ToastRestrictedText_any" = "**%d** users restricted."; + +"Chat.MessageForwardInfo.StoryHeader" = "Forwarded story from"; +"Chat.MessageForwardInfo.ExpiredStoryHeader" = "Expired story from"; +"Chat.MessageForwardInfo.UnavailableStoryHeader" = "Expired story from"; +"Chat.MessageForwardInfo.MessageHeader" = "Forwarded from"; + +"Chat.NavigationNoTopics" = "You have no unread topics"; + +"Chat.NextSuggestedChannelSwipeProgress" = "Swipe up to go to the next channel"; +"Chat.NextSuggestedChannelSwipeAction" = "Release to go to the next channel"; +"Chat.NextUnreadTopicSwipeProgress" = "Swipe up to go to the next topic"; +"Chat.NextUnreadTopicSwipeAction" = "Release to go to the next topic"; diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index aa0c9bd066..a343b776ab 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -89,6 +89,7 @@ public enum AttachmentButtonType: Equatable { public protocol AttachmentContainable: ViewController { var requestAttachmentMenuExpansion: () -> Void { get set } var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void { get set } + var parentController: () -> ViewController? { get set } var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void { get set } var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void { get set } var cancelPanGesture: () -> Void { get set } @@ -560,6 +561,12 @@ public class AttachmentController: ViewController { } } } + controller.parentController = { [weak self] in + guard let self else { + return nil + } + return self.controller + } controller.updateTabBarAlpha = { [weak self, weak controller] alpha, transition in if let strongSelf = self, strongSelf.currentControllers.contains(where: { $0 === controller }) { strongSelf.panel.updateBackgroundAlpha(alpha, transition: transition) diff --git a/submodules/ComposePollUI/Sources/ComposePollScreen.swift b/submodules/ComposePollUI/Sources/ComposePollScreen.swift index e7a314753c..6bfda6e714 100644 --- a/submodules/ComposePollUI/Sources/ComposePollScreen.swift +++ b/submodules/ComposePollUI/Sources/ComposePollScreen.swift @@ -204,9 +204,19 @@ final class ComposePollScreenComponent: Component { } } - var mappedSolution: String? + var mappedSolution: (String, [MessageTextEntity])? if self.isQuiz && self.quizAnswerTextInputState.text.length != 0 { - mappedSolution = self.quizAnswerTextInputState.text.string + var solutionTextEntities: [MessageTextEntity] = [] + for entity in generateChatInputTextEntities(self.quizAnswerTextInputState.text) { + switch entity.type { + case .CustomEmoji: + solutionTextEntities.append(entity) + default: + break + } + } + + mappedSolution = (self.quizAnswerTextInputState.text.string, solutionTextEntities) } var textEntities: [MessageTextEntity] = [] @@ -232,7 +242,7 @@ final class ComposePollScreenComponent: Component { totalVoters: nil, recentVoters: [], solution: mappedSolution.flatMap { mappedSolution in - return TelegramMediaPollResults.Solution(text: mappedSolution, entities: []) + return TelegramMediaPollResults.Solution(text: mappedSolution.0, entities: mappedSolution.1) } ), deadlineTimeout: nil, @@ -591,11 +601,21 @@ final class ComposePollScreenComponent: Component { } self.environment?.controller()?.presentInGlobalOverlay(c, with: a) }, - getNavigationController: { [weak self] in + getNavigationController: { [weak self] () -> NavigationController? in guard let self else { return nil } - return self.environment?.controller()?.navigationController as? NavigationController + guard let controller = self.environment?.controller() as? ComposePollScreen else { + return nil + } + + if let navigationController = controller.navigationController as? NavigationController { + return navigationController + } + if let parentController = controller.parentController() { + return parentController.navigationController as? NavigationController + } + return nil }, requestLayout: { [weak self] transition in guard let self else { @@ -668,14 +688,13 @@ final class ComposePollScreenComponent: Component { )))) self.resetPollText = nil - //TODO:localize let pollTextSectionSize = self.pollTextSection.update( transition: transition, component: AnyComponent(ListSectionComponent( theme: environment.theme, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "QUESTION", + string: environment.strings.CreatePoll_TextHeader, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -696,7 +715,7 @@ final class ComposePollScreenComponent: Component { transition.setFrame(view: pollTextSectionView, frame: pollTextSectionFrame) if let itemView = pollTextSectionView.itemView(id: 0) as? ListComposePollOptionComponent.View { - itemView.updateCustomPlaceholder(value: "Ask a Question", size: itemView.bounds.size, transition: .immediate) + itemView.updateCustomPlaceholder(value: environment.strings.CreatePoll_TextPlaceholder, size: itemView.bounds.size, transition: .immediate) } } contentHeight += pollTextSectionSize.height @@ -838,9 +857,9 @@ final class ComposePollScreenComponent: Component { for i in 0 ..< pollOptionsSectionReadyItems.count { let placeholder: String if i == pollOptionsSectionReadyItems.count - 1 { - placeholder = "Add an Option" + placeholder = environment.strings.CreatePoll_AddOption } else { - placeholder = "Option" + placeholder = environment.strings.CreatePoll_OptionPlaceholder } if let itemView = pollOptionsSectionReadyItems[i].itemView.contents.view as? ListComposePollOptionComponent.View { @@ -866,7 +885,7 @@ final class ComposePollScreenComponent: Component { transition: .immediate, component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "POLL OPTIONS", + string: environment.strings.CreatePoll_OptionsHeader, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -917,26 +936,36 @@ final class ComposePollScreenComponent: Component { if pollOptionsLimitReached { pollOptionsFooterTransition = pollOptionsFooterTransition.withAnimation(.none) pollOptionsComponent = AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "You have added the maximum number of options.", font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor)), + text: .plain(NSAttributedString(string: environment.strings.CreatePoll_AllOptionsAdded, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor)), maximumNumberOfLines: 0 )) } else { + let remainingCount = 10 - self.pollOptions.count + let rawString = environment.strings.CreatePoll_OptionCountFooterFormat(Int32(remainingCount)) + var pollOptionsFooterItems: [AnimatedTextComponent.Item] = [] - pollOptionsFooterItems.append(AnimatedTextComponent.Item( - id: 0, - isUnbreakable: true, - content: .text("You can add ") - )) - pollOptionsFooterItems.append(AnimatedTextComponent.Item( - id: 1, - isUnbreakable: true, - content: .number(10 - self.pollOptions.count, minDigits: 1) - )) - pollOptionsFooterItems.append(AnimatedTextComponent.Item( - id: 2, - isUnbreakable: true, - content: .text(" more options.") - )) + if let range = rawString.range(of: "{count}") { + if range.lowerBound != rawString.startIndex { + pollOptionsFooterItems.append(AnimatedTextComponent.Item( + id: 0, + isUnbreakable: true, + content: .text(String(rawString[rawString.startIndex ..< range.lowerBound])) + )) + } + pollOptionsFooterItems.append(AnimatedTextComponent.Item( + id: 1, + isUnbreakable: true, + content: .number(remainingCount, minDigits: 1) + )) + if range.upperBound != rawString.endIndex { + pollOptionsFooterItems.append(AnimatedTextComponent.Item( + id: 2, + isUnbreakable: true, + content: .text(String(rawString[range.upperBound ..< rawString.endIndex])) + )) + } + } + pollOptionsComponent = AnyComponent(AnimatedTextComponent( font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), color: environment.theme.list.freeTextColor, @@ -977,7 +1006,7 @@ final class ComposePollScreenComponent: Component { title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Anonymous Voting", + string: environment.strings.CreatePoll_Anonymous, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), @@ -998,7 +1027,7 @@ final class ComposePollScreenComponent: Component { title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Multiple Answers", + string: environment.strings.CreatePoll_MultipleChoice, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), @@ -1022,7 +1051,7 @@ final class ComposePollScreenComponent: Component { title: AnyComponent(VStack([ AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Quiz Mode", + string: environment.strings.CreatePoll_Quiz, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), @@ -1049,7 +1078,7 @@ final class ComposePollScreenComponent: Component { header: nil, footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Polls in Quiz Mode have one correct answer. Users can't revoke their answers.", + string: environment.strings.CreatePoll_QuizInfo, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -1078,7 +1107,7 @@ final class ComposePollScreenComponent: Component { theme: environment.theme, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "EXPLANATION", + string: environment.strings.CreatePoll_ExplanationHeader, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -1086,7 +1115,7 @@ final class ComposePollScreenComponent: Component { )), footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Users will see this comment after choosing a wrong answer, good for educational purposes.", + string: environment.strings.CreatePoll_ExplanationInfo, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -1401,6 +1430,11 @@ final class ComposePollScreenComponent: Component { if sendButtonItem.isEnabled != isValid { sendButtonItem.isEnabled = isValid } + + let controllerTitle = self.isQuiz ? presentationData.strings.CreatePoll_QuizTitle : presentationData.strings.CreatePoll_Title + if controller.title != controllerTitle { + controller.title = controllerTitle + } } if let currentEditingTag = self.currentEditingTag, previousEditingTag !== currentEditingTag, self.currentInputMode != .keyboard { @@ -1450,6 +1484,9 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in @@ -1491,12 +1528,13 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont completion: completion ), navigationBarAppearance: .default, theme: .default) - //TODO:localize - self.title = "New Poll" + let presentationData = context.sharedContext.currentPresentationData.with { $0 } - self.navigationItem.setLeftBarButton(UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false) + self.title = isQuiz == true ? presentationData.strings.CreatePoll_QuizTitle : presentationData.strings.CreatePoll_Title - let sendButtonItem = UIBarButtonItem(title: "Send", style: .done, target: self, action: #selector(self.sendPressed)) + self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed)), animated: false) + + let sendButtonItem = UIBarButtonItem(title: presentationData.strings.CreatePoll_Create, style: .done, target: self, action: #selector(self.sendPressed)) self.sendButtonItem = sendButtonItem self.navigationItem.setRightBarButton(sendButtonItem, animated: false) sendButtonItem.isEnabled = false diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index e6b6a83b86..5fa3f3b45a 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -563,6 +563,9 @@ private final class CreatePollContext: AttachmentMediaPickerContext { public class CreatePollControllerImpl: ItemListController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } diff --git a/submodules/LocationUI/Sources/LocationPickerController.swift b/submodules/LocationUI/Sources/LocationPickerController.swift index 63272c8937..d6430907db 100644 --- a/submodules/LocationUI/Sources/LocationPickerController.swift +++ b/submodules/LocationUI/Sources/LocationPickerController.swift @@ -80,6 +80,9 @@ public final class LocationPickerController: ViewController, AttachmentContainab public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } diff --git a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift index ebb6d36a09..93289e5059 100644 --- a/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaGroupsScreen.swift @@ -152,6 +152,9 @@ private func preparedTransition(from fromEntries: [MediaGroupsEntry], to toEntri public final class MediaGroupsScreen: ViewController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index b0abae0272..cb72a77235 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -202,6 +202,9 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = { } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 99fdb21828..d413abc2b8 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -2010,7 +2010,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { })) } case let .category(value): - let resultSignal = self.context.engine.stickers.searchEmoji(emojiString: value) + let resultSignal = self.context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -2082,11 +2082,11 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) version += 1 })) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift index b9198a4b06..70ff4f5190 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift @@ -385,6 +385,391 @@ func _internal_searchStickers(account: Account, query: [String], scope: SearchSt } } +func _internal_searchStickers(account: Account, category: EmojiSearchCategories.Group, scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> { + if scope.isEmpty { + return .single(([], true)) + } + + var query = category.identifiers + if query == ["\u{2764}"] { + query = ["\u{2764}\u{FE0F}"] + } + + return account.postbox.transaction { transaction -> ([FoundStickerItem], CachedStickerQueryResult?, Bool, SearchStickersConfiguration) in + let isPremium = transaction.getPeer(account.peerId)?.isPremium ?? false + + var result: [FoundStickerItem] = [] + if scope.contains(.installed) { + for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudSavedStickers) { + if let item = entry.contents.get(SavedStickerItem.self) { + if case .premium = category.kind { + if item.file.isPremiumSticker { + result.append(FoundStickerItem(file: item.file, stringRepresentations: item.stringRepresentations)) + } + } else { + for representation in item.stringRepresentations { + for queryItem in query { + if representation.hasPrefix(queryItem) { + result.append(FoundStickerItem(file: item.file, stringRepresentations: item.stringRepresentations)) + break + } + } + } + } + } + } + + var currentItems = Set(result.map { $0.file.fileId }) + var recentItems: [TelegramMediaFile] = [] + var recentAnimatedItems: [TelegramMediaFile] = [] + var recentItemsIds = Set() + var matchingRecentItemsIds = Set() + + for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentStickers) { + if let item = entry.contents.get(RecentMediaItem.self) { + let file = item.media + if file.isPremiumSticker && !isPremium { + continue + } + + if !currentItems.contains(file.fileId) { + currentItems.insert(file.fileId) + + for case let .Sticker(displayText, _, _) in file.attributes { + if case .premium = category.kind { + if file.isPremiumSticker { + matchingRecentItemsIds.insert(file.fileId) + } + } else { + for queryItem in query { + if displayText.hasPrefix(queryItem) { + matchingRecentItemsIds.insert(file.fileId) + break + } + } + } + recentItemsIds.insert(file.fileId) + if file.isAnimatedSticker || file.isVideoSticker { + recentAnimatedItems.append(file) + } else { + recentItems.append(file) + } + break + } + } + } + } + + var searchQueries: [ItemCollectionSearchQuery] = query.map { queryItem -> ItemCollectionSearchQuery in + return .exact(ValueBoxKey(queryItem)) + } + if query == ["\u{2764}"] { + searchQueries = [.any([ValueBoxKey("\u{2764}"), ValueBoxKey("\u{2764}\u{FE0F}")])] + } + + var installedItems: [FoundStickerItem] = [] + var installedAnimatedItems: [FoundStickerItem] = [] + var installedPremiumItems: [FoundStickerItem] = [] + + if case .premium = category.kind { + for (_, _, items) in transaction.getCollectionsItems(namespace: Namespaces.ItemCollection.CloudStickerPacks) { + for item in items { + guard let item = item as? StickerPackItem else { + continue + } + if item.file.isPremiumSticker { + if currentItems.contains(item.file.fileId) { + continue + } + currentItems.insert(item.file.fileId) + + if !recentItemsIds.contains(item.file.fileId) { + if item.file.isPremiumSticker { + installedPremiumItems.append(FoundStickerItem(file: item.file, stringRepresentations: [])) + } else if item.file.isAnimatedSticker || item.file.isVideoSticker { + installedAnimatedItems.append(FoundStickerItem(file: item.file, stringRepresentations: [])) + } else { + installedItems.append(FoundStickerItem(file: item.file, stringRepresentations: [])) + } + } else { + matchingRecentItemsIds.insert(item.file.fileId) + if item.file.isAnimatedSticker || item.file.isVideoSticker { + recentAnimatedItems.append(item.file) + } else { + recentItems.append(item.file) + } + } + } + } + } + + for item in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudAllPremiumStickers) { + guard let item = item.contents.get(RecentMediaItem.self) else { + continue + } + if currentItems.contains(item.media.fileId) { + continue + } + currentItems.insert(item.media.fileId) + + if !recentItemsIds.contains(item.media.fileId) { + if item.media.isPremiumSticker { + installedPremiumItems.append(FoundStickerItem(file: item.media, stringRepresentations: [])) + } else if item.media.isAnimatedSticker || item.media.isVideoSticker { + installedAnimatedItems.append(FoundStickerItem(file: item.media, stringRepresentations: [])) + } else { + installedItems.append(FoundStickerItem(file: item.media, stringRepresentations: [])) + } + } else { + matchingRecentItemsIds.insert(item.media.fileId) + if item.media.isAnimatedSticker || item.media.isVideoSticker { + recentAnimatedItems.append(item.media) + } else { + recentItems.append(item.media) + } + } + } + } else { + for searchQuery in searchQueries { + for item in transaction.searchItemCollection(namespace: Namespaces.ItemCollection.CloudStickerPacks, query: searchQuery) { + if let item = item as? StickerPackItem { + if !currentItems.contains(item.file.fileId) { + currentItems.insert(item.file.fileId) + + let stringRepresentations = item.getStringRepresentationsOfIndexKeys() + if !recentItemsIds.contains(item.file.fileId) { + if item.file.isPremiumSticker { + installedPremiumItems.append(FoundStickerItem(file: item.file, stringRepresentations: stringRepresentations)) + } else if item.file.isAnimatedSticker || item.file.isVideoSticker { + installedAnimatedItems.append(FoundStickerItem(file: item.file, stringRepresentations: stringRepresentations)) + } else { + installedItems.append(FoundStickerItem(file: item.file, stringRepresentations: stringRepresentations)) + } + } else { + matchingRecentItemsIds.insert(item.file.fileId) + if item.file.isAnimatedSticker || item.file.isVideoSticker { + recentAnimatedItems.append(item.file) + } else { + recentItems.append(item.file) + } + } + } + } + } + } + } + + for file in recentAnimatedItems { + if file.isPremiumSticker && !isPremium { + continue + } + if matchingRecentItemsIds.contains(file.fileId) { + result.append(FoundStickerItem(file: file, stringRepresentations: query)) + } + } + + for file in recentItems { + if file.isPremiumSticker && !isPremium { + continue + } + if matchingRecentItemsIds.contains(file.fileId) { + result.append(FoundStickerItem(file: file, stringRepresentations: query)) + } + } + + result.append(contentsOf: installedPremiumItems) + result.append(contentsOf: installedAnimatedItems) + result.append(contentsOf: installedItems) + } + + var cached: CachedStickerQueryResult? + let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue + let searchStickersConfiguration = SearchStickersConfiguration.with(appConfiguration: appConfiguration) + + if case .premium = category.kind { + } else { + let combinedQuery = query.joined(separator: "") + cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(combinedQuery)))?.get(CachedStickerQueryResult.self) + + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + + if let currentCached = cached, currentTime > currentCached.timestamp + searchStickersConfiguration.cacheTimeout { + cached = nil + } + } + + return (result, cached, isPremium, searchStickersConfiguration) + } + |> mapToSignal { localItems, cached, isPremium, searchStickersConfiguration -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in + if !scope.contains(.remote) { + return .single((localItems, true)) + } + if case .premium = category.kind { + return .single((localItems, true)) + } + + var tempResult: [FoundStickerItem] = [] + let currentItemIds = Set(localItems.map { $0.file.fileId }) + + var premiumItems: [FoundStickerItem] = [] + var otherItems: [FoundStickerItem] = [] + + for item in localItems { + if item.file.isPremiumSticker { + premiumItems.append(item) + } else { + otherItems.append(item) + } + } + + if let cached = cached { + var cachedItems: [FoundStickerItem] = [] + var cachedAnimatedItems: [FoundStickerItem] = [] + var cachedPremiumItems: [FoundStickerItem] = [] + + for file in cached.items { + if !currentItemIds.contains(file.fileId) { + if file.isPremiumSticker { + cachedPremiumItems.append(FoundStickerItem(file: file, stringRepresentations: [])) + } else if file.isAnimatedSticker || file.isVideoSticker { + cachedAnimatedItems.append(FoundStickerItem(file: file, stringRepresentations: [])) + } else { + cachedItems.append(FoundStickerItem(file: file, stringRepresentations: [])) + } + } + } + + otherItems.append(contentsOf: cachedAnimatedItems) + otherItems.append(contentsOf: cachedItems) + + let allPremiumItems = premiumItems + cachedPremiumItems + let allOtherItems = otherItems + cachedAnimatedItems + cachedItems + + if isPremium { + let batchCount = Int(searchStickersConfiguration.normalStickersPerPremiumCount) + if batchCount == 0 { + tempResult.append(contentsOf: allPremiumItems) + tempResult.append(contentsOf: allOtherItems) + } else { + if allPremiumItems.isEmpty { + tempResult.append(contentsOf: allOtherItems) + } else { + var i = 0 + for premiumItem in allPremiumItems { + if i < allOtherItems.count { + for j in i ..< min(i + batchCount, allOtherItems.count) { + tempResult.append(allOtherItems[j]) + } + i += batchCount + } + tempResult.append(premiumItem) + } + if i < allOtherItems.count { + for j in i ..< allOtherItems.count { + tempResult.append(allOtherItems[j]) + } + } + } + } + } else { + tempResult.append(contentsOf: allOtherItems) + tempResult.append(contentsOf: allPremiumItems.prefix(max(0, Int(searchStickersConfiguration.premiumStickersCount)))) + } + } + + let remote = account.network.request(Api.functions.messages.getStickers(emoticon: query.joined(separator: ""), hash: cached?.hash ?? 0)) + |> `catch` { _ -> Signal in + return .single(.stickersNotModified) + } + |> mapToSignal { result -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in + return account.postbox.transaction { transaction -> (items: [FoundStickerItem], isFinalResult: Bool) in + switch result { + case let .stickers(hash, stickers): + var result: [FoundStickerItem] = [] + let currentItemIds = Set(localItems.map { $0.file.fileId }) + + var premiumItems: [FoundStickerItem] = [] + var otherItems: [FoundStickerItem] = [] + + for item in localItems { + if item.file.isPremiumSticker { + premiumItems.append(item) + } else { + otherItems.append(item) + } + } + + var foundItems: [FoundStickerItem] = [] + var foundAnimatedItems: [FoundStickerItem] = [] + var foundPremiumItems: [FoundStickerItem] = [] + + var files: [TelegramMediaFile] = [] + for sticker in stickers { + if let file = telegramMediaFileFromApiDocument(sticker), let id = file.id { + files.append(file) + if !currentItemIds.contains(id) { + if file.isPremiumSticker { + foundPremiumItems.append(FoundStickerItem(file: file, stringRepresentations: [])) + } else if file.isAnimatedSticker || file.isVideoSticker { + foundAnimatedItems.append(FoundStickerItem(file: file, stringRepresentations: [])) + } else { + foundItems.append(FoundStickerItem(file: file, stringRepresentations: [])) + } + } + } + } + + let allPremiumItems = premiumItems + foundPremiumItems + let allOtherItems = otherItems + foundAnimatedItems + foundItems + + if isPremium { + let batchCount = Int(searchStickersConfiguration.normalStickersPerPremiumCount) + if batchCount == 0 { + result.append(contentsOf: allPremiumItems) + result.append(contentsOf: allOtherItems) + } else { + if allPremiumItems.isEmpty { + result.append(contentsOf: allOtherItems) + } else { + var i = 0 + for premiumItem in allPremiumItems { + if i < allOtherItems.count { + for j in i ..< min(i + batchCount, allOtherItems.count) { + result.append(allOtherItems[j]) + } + i += batchCount + } + result.append(premiumItem) + } + if i < allOtherItems.count { + for j in i ..< allOtherItems.count { + result.append(allOtherItems[j]) + } + } + } + } + } else { + result.append(contentsOf: allOtherItems) + result.append(contentsOf: allPremiumItems.prefix(max(0, Int(searchStickersConfiguration.premiumStickersCount)))) + } + + let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if let entry = CodableEntry(CachedStickerQueryResult(items: files, hash: hash, timestamp: currentTime)) { + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query.joined(separator: ""))), entry: entry) + } + + return (result, true) + case .stickersNotModified: + break + } + return (tempResult, true) + } + } + return .single((tempResult, false)) + |> then(remote) + } +} + func _internal_searchEmoji(account: Account, query: [String], scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> { if scope.isEmpty { return .single(([], true)) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index a29f884883..42ee3b92fa 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -33,6 +33,10 @@ public extension TelegramEngine { public func searchStickers(query: [String], scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> { return _internal_searchStickers(account: self.account, query: query, scope: scope) } + + public func searchStickers(category: EmojiSearchCategories.Group, scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> { + return _internal_searchStickers(account: self.account, category: category, scope: scope) + } public func searchStickerSetsRemotely(query: String) -> Signal { return _internal_searchStickerSetsRemotely(network: self.account.network, query: query) @@ -286,6 +290,13 @@ public extension TelegramEngine { } } + public func searchEmoji(category: EmojiSearchCategories.Group) -> Signal<(items: [TelegramMediaFile], isFinalResult: Bool), NoError> { + return _internal_searchEmoji(account: self.account, query: category.identifiers) + |> map { items, isFinalResult -> (items: [TelegramMediaFile], isFinalResult: Bool) in + return (items.map(\.file), isFinalResult) + } + } + public func addRecentlyUsedSticker(fileReference: FileMediaReference) { let _ = self.account.postbox.transaction({ transaction -> Void in TelegramCore.addRecentlyUsedSticker(transaction: transaction, fileReference: fileReference) diff --git a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift index 049fd4aa3f..2a042c2b4c 100644 --- a/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift +++ b/submodules/TelegramUI/Components/AdminUserActionsSheet/Sources/AdminUserActionsSheet.swift @@ -651,16 +651,16 @@ private final class AdminUserActionsSheetComponent: Component { selectedPeers = self.optionReportSelectedPeers isExpanded = self.isOptionReportExpanded - title = "Report Spam" + title = environment.strings.Chat_AdminActionSheet_ReportSpam case .deleteAll: sectionId = "delete-all" selectedPeers = self.optionDeleteAllSelectedPeers isExpanded = self.isOptionDeleteAllExpanded if component.peers.count == 1 { - title = "Delete All from \(EnginePeer(component.peers[0].peer).compactDisplayTitle)" + title = environment.strings.Chat_AdminActionSheet_DeleteAllSingle(EnginePeer(component.peers[0].peer).compactDisplayTitle).string } else { - title = "Delete All from Users" + title = environment.strings.Chat_AdminActionSheet_DeleteAllMultiple } case .ban: sectionId = "ban" @@ -670,11 +670,11 @@ private final class AdminUserActionsSheetComponent: Component { let banTitle: String let restrictTitle: String if component.peers.count == 1 { - banTitle = "Ban \(EnginePeer(component.peers[0].peer).compactDisplayTitle)" - restrictTitle = "Restrict \(EnginePeer(component.peers[0].peer).compactDisplayTitle)" + banTitle = environment.strings.Chat_AdminActionSheet_BanSingle(EnginePeer(component.peers[0].peer).compactDisplayTitle).string + restrictTitle = environment.strings.Chat_AdminActionSheet_RestrictSingle(EnginePeer(component.peers[0].peer).compactDisplayTitle).string } else { - banTitle = "Ban Users" - restrictTitle = "Restrict Users" + banTitle = environment.strings.Chat_AdminActionSheet_BanMultiple + restrictTitle = environment.strings.Chat_AdminActionSheet_RestrictMultiple } title = self.isConfigurationExpanded ? restrictTitle : banTitle } @@ -870,13 +870,7 @@ private final class AdminUserActionsSheetComponent: Component { ))) } - //TODO:localize - let titleString: String - if component.messageCount == 1 { - titleString = "Delete 1 Message?" - } else { - titleString = "Delete \(component.messageCount) Messages?" - } + let titleString: String = environment.strings.Chat_AdminActionSheet_DeleteTitle(Int32(component.messageCount)) let titleSize = self.title.update( transition: .immediate, component: AnyComponent(MultilineTextComponent( @@ -928,7 +922,7 @@ private final class AdminUserActionsSheetComponent: Component { theme: environment.theme, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "ADDITIONAL ACTIONS", + string: environment.strings.Chat_AdminActionSheet_RestrictSectionHeader, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -954,11 +948,11 @@ private final class AdminUserActionsSheetComponent: Component { let partiallyRestrictTitle: String let fullyBanTitle: String if component.peers.count == 1 { - partiallyRestrictTitle = "Partially restrict this user" - fullyBanTitle = "Fully ban this user" + partiallyRestrictTitle = environment.strings.Chat_AdminActionSheet_RestrictFooterSingle + fullyBanTitle = environment.strings.Chat_AdminActionSheet_BanFooterSingle } else { - partiallyRestrictTitle = "Partially restrict users" - fullyBanTitle = "Fully ban users" + partiallyRestrictTitle = environment.strings.Chat_AdminActionSheet_RestrictFooterMultiple + fullyBanTitle = environment.strings.Chat_AdminActionSheet_BanFooterMultiple } let optionsFooterSize = self.optionsFooter.update( @@ -1029,7 +1023,7 @@ private final class AdminUserActionsSheetComponent: Component { case .sendMessages: itemTitle = AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Send Messages", + string: environment.strings.Channel_BanUser_PermissionSendMessages, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), @@ -1041,7 +1035,7 @@ private final class AdminUserActionsSheetComponent: Component { itemTitle = AnyComponent(HStack([ AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Send Media", + string: environment.strings.Channel_BanUser_PermissionSendMedia, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), @@ -1056,7 +1050,7 @@ private final class AdminUserActionsSheetComponent: Component { } else { itemTitle = AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Send Media", + string: environment.strings.Channel_BanUser_PermissionSendMedia, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), @@ -1068,7 +1062,7 @@ private final class AdminUserActionsSheetComponent: Component { case .addUsers: itemTitle = AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Add Users", + string: environment.strings.Channel_BanUser_PermissionAddMembers, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), @@ -1078,7 +1072,7 @@ private final class AdminUserActionsSheetComponent: Component { case .pinMessages: itemTitle = AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Pin Messages", + string: environment.strings.Channel_EditAdmin_PermissionPinMessages, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), @@ -1088,7 +1082,7 @@ private final class AdminUserActionsSheetComponent: Component { case .changeInfo: itemTitle = AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "Change Chat Info", + string: environment.strings.Channel_BanUser_PermissionChangeGroupInfo, font: Font.regular(presentationData.listsFontSize.baseDisplaySize), textColor: environment.theme.list.itemPrimaryTextColor )), @@ -1163,23 +1157,23 @@ private final class AdminUserActionsSheetComponent: Component { let mediaItemTitle: String switch possibleMediaItem { case .photos: - mediaItemTitle = "Send Photos" + mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendPhoto case .videos: - mediaItemTitle = "Send Videos" + mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVideo case .stickersAndGifs: - mediaItemTitle = "Send Stickers & GIFs" + mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendStickersAndGifs case .music: - mediaItemTitle = "Send Music" + mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendMusic case .files: - mediaItemTitle = "Send Files" + mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendFile case .voiceMessages: - mediaItemTitle = "Send Voice Messages" + mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVoiceMessage case .videoMessages: - mediaItemTitle = "Send Video Messages" + mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendVideoMessage case .links: - mediaItemTitle = "Embed Links" + mediaItemTitle = environment.strings.Channel_BanUser_PermissionEmbedLinks case .polls: - mediaItemTitle = "Send Polls" + mediaItemTitle = environment.strings.Channel_BanUser_PermissionSendPolls default: continue mediaRightsLoop } @@ -1244,7 +1238,7 @@ private final class AdminUserActionsSheetComponent: Component { theme: environment.theme, header: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: "WHAT CAN THIS USER DO?", + string: environment.strings.Chat_AdminActionSheet_PermissionsSectionHeader, font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize), textColor: environment.theme.list.freeTextColor )), @@ -1318,7 +1312,7 @@ private final class AdminUserActionsSheetComponent: Component { content: AnyComponentWithIdentity( id: AnyHashable(0), component: AnyComponent(ButtonTextContentComponent( - text: "Proceed", + text: environment.strings.Chat_AdminActionSheet_ActionButton, badge: 0, textColor: environment.theme.list.itemCheckColors.foregroundColor, badgeBackground: environment.theme.list.itemCheckColors.foregroundColor, diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 123dfeb747..563cd08d21 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -483,7 +483,7 @@ final class AvatarEditorScreenComponent: Component { })) } case let .category(value): - let resultSignal = context.engine.stickers.searchEmoji(emojiString: value) + let resultSignal = context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -557,11 +557,11 @@ final class AvatarEditorScreenComponent: Component { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) version += 1 })) } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift index 7347c9ce43..8d79dea5e7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift @@ -264,21 +264,20 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { titleColor = presentationData.theme.theme.chat.message.outgoing.accentTextColor } - //TODO:localize if let storyData = storyData { switch storyData.storyType { case .regular: - titleString = PresentationStrings.FormattedString(string: "Forwarded story from", ranges: []) + titleString = PresentationStrings.FormattedString(string: presentationData.strings.Chat_MessageForwardInfo_StoryHeader, ranges: []) authorString = peerString case .expired: - titleString = PresentationStrings.FormattedString(string: "Expired story from", ranges: []) + titleString = PresentationStrings.FormattedString(string: presentationData.strings.Chat_MessageForwardInfo_ExpiredStoryHeader, ranges: []) authorString = peerString case .unavailable: - titleString = PresentationStrings.FormattedString(string: "Expired story from", ranges: []) + titleString = PresentationStrings.FormattedString(string: presentationData.strings.Chat_MessageForwardInfo_UnavailableStoryHeader, ranges: []) authorString = peerString } } else { - titleString = PresentationStrings.FormattedString(string: "Forwarded from", ranges: []) + titleString = PresentationStrings.FormattedString(string: presentationData.strings.Chat_MessageForwardInfo_MessageHeader, ranges: []) authorString = peerString } } @@ -309,7 +308,7 @@ public class ChatMessageForwardInfoNode: ASDisplayNode { titleString = strings.Message_GenericForwardedPsa(peerString) } } else { - titleString = PresentationStrings.FormattedString(string: "Forwarded from", ranges: []) + titleString = PresentationStrings.FormattedString(string: presentationData.strings.Chat_MessageForwardInfo_MessageHeader, ranges: []) authorString = peerString } } diff --git a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift index b505081b99..629a1bcea3 100644 --- a/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatOverscrollControl/Sources/ChatOverscrollControl.swift @@ -752,6 +752,7 @@ final class OverscrollContentsComponent: Component { let foregroundColor: UIColor let peer: EnginePeer? let threadData: ChatOverscrollThreadData? + let isForumThread: Bool let unreadCount: Int let location: TelegramEngine.NextUnreadChannelLocation let expandOffset: CGFloat @@ -766,6 +767,7 @@ final class OverscrollContentsComponent: Component { foregroundColor: UIColor, peer: EnginePeer?, threadData: ChatOverscrollThreadData?, + isForumThread: Bool, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation, expandOffset: CGFloat, @@ -779,6 +781,7 @@ final class OverscrollContentsComponent: Component { self.foregroundColor = foregroundColor self.peer = peer self.threadData = threadData + self.isForumThread = isForumThread self.unreadCount = unreadCount self.location = location self.expandOffset = expandOffset @@ -804,6 +807,9 @@ final class OverscrollContentsComponent: Component { if lhs.threadData != rhs.threadData { return false } + if lhs.isForumThread != rhs.isForumThread { + return false + } if lhs.unreadCount != rhs.unreadCount { return false } @@ -965,6 +971,8 @@ final class OverscrollContentsComponent: Component { titleText = threadData.data.info.title } else if let peer = component.peer { titleText = peer.compactDisplayTitle + } else if component.isForumThread { + titleText = component.context.sharedContext.currentPresentationData.with({ $0 }).strings.Chat_NavigationNoTopics } else { titleText = component.context.sharedContext.currentPresentationData.with({ $0 }).strings.Chat_NavigationNoChannels } @@ -1083,6 +1091,7 @@ public final class ChatOverscrollControl: CombinedComponent { let foregroundColor: UIColor let peer: EnginePeer? let threadData: ChatOverscrollThreadData? + let isForumThread: Bool let unreadCount: Int let location: TelegramEngine.NextUnreadChannelLocation let context: AccountContext @@ -1097,6 +1106,7 @@ public final class ChatOverscrollControl: CombinedComponent { foregroundColor: UIColor, peer: EnginePeer?, threadData: ChatOverscrollThreadData?, + isForumThread: Bool, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation, context: AccountContext, @@ -1110,6 +1120,7 @@ public final class ChatOverscrollControl: CombinedComponent { self.foregroundColor = foregroundColor self.peer = peer self.threadData = threadData + self.isForumThread = isForumThread self.unreadCount = unreadCount self.location = location self.context = context @@ -1133,6 +1144,9 @@ public final class ChatOverscrollControl: CombinedComponent { if lhs.threadData != rhs.threadData { return false } + if lhs.isForumThread != rhs.isForumThread { + return false + } if lhs.unreadCount != rhs.unreadCount { return false } @@ -1171,6 +1185,7 @@ public final class ChatOverscrollControl: CombinedComponent { foregroundColor: context.component.foregroundColor, peer: context.component.peer, threadData: context.component.threadData, + isForumThread: context.component.isForumThread, unreadCount: context.component.unreadCount, location: context.component.location, expandOffset: context.component.expandDistance, diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 69ed4bd50d..81555f268c 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -697,7 +697,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { let presentationData = context.sharedContext.currentPresentationData.with { $0 } premiumToastCounter += 1 - let suggestSavedMessages = premiumToastCounter % 2 == 0 + var suggestSavedMessages = premiumToastCounter % 2 == 0 + if chatPeerId == nil { + suggestSavedMessages = false + } let text: String let actionTitle: String if suggestSavedMessages { @@ -1091,7 +1094,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { })) } case let .category(value): - let resultSignal = self.context.engine.stickers.searchEmoji(emojiString: value) + let resultSignal = self.context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -1105,7 +1108,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { let item = EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), - itemFile: itemFile, subgroupId: nil, + itemFile: itemFile, + subgroupId: nil, icon: .none, tintMode: animationData.isTemplate ? .primary : .none ) @@ -1162,10 +1166,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) version += 1 })) } @@ -1434,7 +1438,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { strongSelf.stickerSearchDisposable.set(nil) strongSelf.stickerSearchStateValue = EmojiSearchState(result: nil, isSearching: false) case let .category(value): - let resultSignal = strongSelf.context.engine.stickers.searchStickers(query: value, scope: [.installed, .remote]) + let resultSignal = strongSelf.context.engine.stickers.searchStickers(category: value, scope: [.installed, .remote]) |> mapToSignal { files -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -1506,10 +1510,10 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) return } - strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) version += 1 })) } diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index a668a5f97d..5fca0d36fc 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -615,7 +615,7 @@ public final class EmojiStatusSelectionController: ViewController { })) } case let .category(value): - let resultSignal = self.context.engine.stickers.searchEmoji(emojiString: value) + let resultSignal = self.context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -687,11 +687,11 @@ public final class EmojiStatusSelectionController: ViewController { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) version += 1 })) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index f71777f833..30d4ec87a1 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -1744,7 +1744,7 @@ public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate { private var textField: EmojiSearchTextField? private var tapRecognizer: UITapGestureRecognizer? - private(set) var currentPresetSearchTerm: [String]? + private(set) var currentPresetSearchTerm: EmojiSearchCategories.Group? private var params: Params? @@ -2570,7 +2570,7 @@ public final class EmojiPagerContentComponent: Component { public enum SearchQuery: Equatable { case text(value: String, language: String) - case category(value: [String]) + case category(value: EmojiSearchCategories.Group) } public enum ItemContent: Equatable { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift index 6c95270a34..d02f26e64f 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentSignals.swift @@ -1658,7 +1658,6 @@ public extension EmojiPagerContentComponent { let strings = context.sharedContext.currentPresentationData.with({ $0 }).strings - //TODO:localize let searchCategories: Signal switch subject { case .groupPhotoEmojiSelection, .profilePhotoEmojiSelection: diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift index 2ab27b0967..e8fb0bd8c9 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift @@ -319,7 +319,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode })) } case let .category(value): - let resultSignal = self.context.engine.stickers.searchEmoji(emojiString: value) + let resultSignal = self.context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -333,7 +333,8 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode let item = EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), - itemFile: itemFile, subgroupId: nil, + itemFile: itemFile, + subgroupId: nil, icon: .none, tintMode: animationData.isTemplate ? .primary : .none ) @@ -393,11 +394,11 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: false), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: false), isSearching: false) version += 1 })) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift index d3b6f06900..93c8f1e164 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift @@ -104,7 +104,7 @@ final class EmojiSearchSearchBarComponent: Component { let useOpaqueTheme: Bool let textInputState: TextInputState let categories: EmojiSearchCategories? - let searchTermUpdated: ([String]?) -> Void + let searchTermUpdated: (EmojiSearchCategories.Group?) -> Void let activateTextInput: () -> Void init( @@ -115,7 +115,7 @@ final class EmojiSearchSearchBarComponent: Component { useOpaqueTheme: Bool, textInputState: TextInputState, categories: EmojiSearchCategories?, - searchTermUpdated: @escaping ([String]?) -> Void, + searchTermUpdated: @escaping (EmojiSearchCategories.Group?) -> Void, activateTextInput: @escaping () -> Void ) { self.context = context @@ -366,7 +366,7 @@ final class EmojiSearchSearchBarComponent: Component { self.componentState?.updated(transition: .easeInOut(duration: 0.2)) if let _ = self.selectedItem, let categories = component.categories, let group = categories.groups.first(where: { $0.id == itemId }) { - component.searchTermUpdated(group.identifiers) + component.searchTermUpdated(group) if let itemComponentView = itemView.view.view { var offset = self.scrollView.contentOffset.x diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift index c601dbf308..c4943a46a7 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GifPagerContentComponent.swift @@ -1081,7 +1081,7 @@ public final class GifPagerContentComponent: Component { case .text: break case let .category(value): - component.inputInteraction.updateSearchQuery(value) + component.inputInteraction.updateSearchQuery(value.identifiers) } }) self.visibleSearchHeader = visibleSearchHeader diff --git a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift index fbcd865a8f..6232f869d9 100644 --- a/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift +++ b/submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift @@ -11,6 +11,9 @@ import AttachmentUI public class PremiumGiftAttachmentScreen: PremiumGiftScreen, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } public var cancelPanGesture: () -> Void = { } public var isContainerPanning: () -> Bool = { return false } public var isContainerExpanded: () -> Bool = { return false } diff --git a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift index 81fb4a55ef..110d7b1671 100644 --- a/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/AutomaticBusinessMessageSetupScreen/Sources/QuickReplySetupScreen.swift @@ -1303,6 +1303,9 @@ public final class QuickReplySetupScreen: ViewControllerComponentContainer, Atta } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in diff --git a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift index 047ec57524..b06da137cc 100644 --- a/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift +++ b/submodules/TelegramUI/Components/Settings/BusinessIntroSetupScreen/Sources/BusinessIntroSetupScreen.swift @@ -518,7 +518,7 @@ final class BusinessIntroSetupScreenComponent: Component { })) } case let .category(value): - let resultSignal = component.context.engine.stickers.searchStickers(query: value, scope: [.installed, .remote]) + let resultSignal = component.context.engine.stickers.searchStickers(category: value, scope: [.installed, .remote]) |> mapToSignal { files -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -589,13 +589,13 @@ final class BusinessIntroSetupScreenComponent: Component { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) if !self.isUpdating { self.state?.updated(transition: .immediate) } return } - self.stickerSearchState = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + self.stickerSearchState = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) version += 1 if !self.isUpdating { self.state?.updated(transition: .immediate) diff --git a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift index 69902150f8..5892427c04 100644 --- a/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift +++ b/submodules/TelegramUI/Components/Settings/WallpaperGridScreen/Sources/ThemeColorsGridController.swift @@ -337,6 +337,9 @@ public final class ThemeColorsGridController: ViewController, AttachmentContaina public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } diff --git a/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift b/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift index f110a6643d..851c034270 100644 --- a/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift +++ b/submodules/TelegramUI/Components/StickerPickerScreen/Sources/StickerPickerScreen.swift @@ -1156,7 +1156,7 @@ public class StickerPickerScreen: ViewController { })) } case let .category(value): - let resultSignal = context.engine.stickers.searchEmoji(emojiString: value) + let resultSignal = context.engine.stickers.searchEmoji(category: value) |> mapToSignal { files, isFinalResult -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -1227,10 +1227,10 @@ public class StickerPickerScreen: ViewController { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) return } - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) version += 1 })) } @@ -1442,7 +1442,7 @@ public class StickerPickerScreen: ViewController { strongSelf.stickerSearchDisposable.set(nil) strongSelf.stickerSearchStateValue = EmojiSearchState(result: nil, isSearching: false) case let .category(value): - let resultSignal = context.engine.stickers.searchStickers(query: value, scope: [.installed, .remote]) + let resultSignal = context.engine.stickers.searchStickers(category: value, scope: [.installed, .remote]) |> mapToSignal { files -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in var items: [EmojiPagerContentComponent.Item] = [] @@ -1513,10 +1513,10 @@ public class StickerPickerScreen: ViewController { fillWithLoadingPlaceholders: true, items: [] ) - ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + ], id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) return } - strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value.id), version: version, isPreset: true), isSearching: false) version += 1 })) } diff --git a/submodules/TelegramUI/Sources/AttachmentFileController.swift b/submodules/TelegramUI/Sources/AttachmentFileController.swift index 8c56d16af5..c1c1126a1c 100644 --- a/submodules/TelegramUI/Sources/AttachmentFileController.swift +++ b/submodules/TelegramUI/Sources/AttachmentFileController.swift @@ -196,6 +196,9 @@ private final class AttachmentFileContext: AttachmentMediaPickerContext { class AttachmentFileControllerImpl: ItemListController, AttachmentFileController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = {} public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index fb1de9b97b..8919b7586d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -8451,7 +8451,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } - let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: solution.text, entities: solution.entities), icon: .animation(name: "anim_infotip", delay: 0.2, tintColor: nil), location: .top, shouldDismissOnTouch: { point, _ in + let tooltipScreen = TooltipScreen(context: self.context, account: self.context.account, sharedContext: self.context.sharedContext, text: .entities(text: solution.text, entities: solution.entities), icon: .animation(name: "anim_infotip", delay: 0.2, tintColor: nil), location: .top, shouldDismissOnTouch: { point, _ in return .ignore }, openActiveTextItem: { [weak self] item, action in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift index d9ee42aa39..0f361cc0ec 100644 --- a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift +++ b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift @@ -26,10 +26,9 @@ extension ChatControllerImpl { return } - //TODO:localize - var title: String? = messageIds.count == 1 ? "Message Deleted" : "Messages Deleted" + var title: String? = messageIds.count == 1 ? self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTitleSingle : self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTitleMultiple if !result.deleteAllFromPeers.isEmpty { - title = "Messages Deleted" + title = self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTitleMultiple } var text: String = "" var undoRights: [EnginePeer.Id: InitialBannedRights] = [:] @@ -38,21 +37,13 @@ extension ChatControllerImpl { if !text.isEmpty { text.append("\n") } - if result.reportSpamPeers.count == 1 { - text.append("**1** user reported for spam.") - } else { - text.append("**\(result.reportSpamPeers.count)** users reported for spam.") - } + text.append(self.presentationData.strings.Chat_AdminAction_ToastReportedSpamText(Int32(result.reportSpamPeers.count))) } if !result.banPeers.isEmpty { if !text.isEmpty { text.append("\n") } - if result.banPeers.count == 1 { - text.append("**1** user banned.") - } else { - text.append("**\(result.banPeers.count)** users banned.") - } + text.append(self.presentationData.strings.Chat_AdminAction_ToastBannedText(Int32(result.banPeers.count))) for id in result.banPeers { if let value = initialUserBannedRights[id] { undoRights[id] = value @@ -63,11 +54,7 @@ extension ChatControllerImpl { if !text.isEmpty { text.append("\n") } - if result.updateBannedRights.count == 1 { - text.append("**1** user restricted.") - } else { - text.append("**\(result.updateBannedRights.count)** users restricted.") - } + text.append(self.presentationData.strings.Chat_AdminAction_ToastRestrictedText(Int32(result.updateBannedRights.count))) for (id, _) in result.updateBannedRights { if let value = initialUserBannedRights[id] { undoRights[id] = value @@ -97,9 +84,9 @@ extension ChatControllerImpl { } if text.isEmpty { - text = messageIds.count == 1 ? "Message Deleted." : "Messages Deleted." + text = messageIds.count == 1 ? self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTextSingle : self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTextMultiple if !result.deleteAllFromPeers.isEmpty { - text = "Messages Deleted." + text = self.presentationData.strings.Chat_AdminAction_ToastMessagesDeletedTextMultiple } title = nil } diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 64e7814da4..3b768daac0 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -2243,12 +2243,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto switch nextChannelToRead.location { case .same: if let controllerNode = self.controllerInteraction.chatControllerNode() as? ChatControllerNode, let chatController = controllerNode.interfaceInteraction?.chatController() as? ChatControllerImpl, chatController.customChatNavigationStack != nil { - //TODO:localize - swipeText = ("Pull up to go to the next channel", []) - releaseText = ("Release to go to the next channel", []) + swipeText = (self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeProgress, []) + releaseText = (self.currentPresentationData.strings.Chat_NextSuggestedChannelSwipeAction, []) } else if nextChannelToRead.threadData != nil { - swipeText = ("Pull up to go to the next topic", []) - releaseText = ("Release to go to the next topic", []) + swipeText = (self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeProgress, []) + releaseText = (self.currentPresentationData.strings.Chat_NextUnreadTopicSwipeAction, []) } else { swipeText = (self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeProgress, []) releaseText = (self.currentPresentationData.strings.Chat_NextChannelSameLocationSwipeAction, []) @@ -2298,6 +2297,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto data: threadData.data ) }, + isForumThread: self.chatLocation.threadId != nil, unreadCount: self.nextChannelToRead?.unreadCount ?? 0, location: self.nextChannelToRead?.location ?? .same, context: self.context, diff --git a/submodules/TelegramUI/Sources/ContactSelectionController.swift b/submodules/TelegramUI/Sources/ContactSelectionController.swift index a4e96a6a95..65cf9921a3 100644 --- a/submodules/TelegramUI/Sources/ContactSelectionController.swift +++ b/submodules/TelegramUI/Sources/ContactSelectionController.swift @@ -78,6 +78,9 @@ class ContactSelectionControllerImpl: ViewController, ContactSelectionController var requestAttachmentMenuExpansion: () -> Void = {} var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } var cancelPanGesture: () -> Void = { } diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index 7d0975080a..330aa3ea3b 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -601,7 +601,16 @@ private final class TooltipScreenNode: ViewControllerTracingNode { } let textSize: CGSize - if case .attributedString = self.text, let context = self.context { + + var isTextWithEntities = false + switch self.text { + case .attributedString, .entities: + isTextWithEntities = true + default: + break + } + + if isTextWithEntities, let context = self.context { textSize = self.textView.update( transition: .immediate, component: AnyComponent(MultilineTextWithEntitiesComponent( diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 8c5adf21b4..fbef04c2fb 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -258,6 +258,9 @@ public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> public final class WebAppController: ViewController, AttachmentContainable { public var requestAttachmentMenuExpansion: () -> Void = { } public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in } + public var parentController: () -> ViewController? = { + return nil + } public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in } public var updateTabBarVisibility: (Bool, ContainedViewLayoutTransition) -> Void = { _, _ in } public var cancelPanGesture: () -> Void = { }