Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-01-22 16:17:18 +04:00
commit bb536aec28
17 changed files with 179 additions and 96 deletions

View File

@ -299,7 +299,7 @@ public struct Transition {
}
}
public func attachAnimation(view: UIView, completion: @escaping (Bool) -> Void) {
public func attachAnimation(view: UIView, id: String, completion: @escaping (Bool) -> Void) {
switch self.animation {
case .none:
completion(true)
@ -307,7 +307,7 @@ public struct Transition {
view.layer.animate(
from: 0.0 as NSNumber,
to: 1.0 as NSNumber,
keyPath: "attached\(UInt32.random(in: 0 ... UInt32.max))",
keyPath: id,
duration: duration,
delay: 0.0,
curve: curve,

View File

@ -1205,7 +1205,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
if query.isSingleEmoji {
signals = .single([context.engine.stickers.searchStickers(query: text.basicEmoji.0)
|> map { (nil, $0) }])
|> map { (nil, $0.items) }])
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
if !languageCode.lowercased().hasPrefix("en") {
@ -1228,7 +1228,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
for emoji in emoticons {
signals.append(context.engine.stickers.searchStickers(query: emoji.basicEmoji.0)
|> take(1)
|> map { (emoji, $0) })
|> map { (emoji, $0.items) })
}
return signals
}

View File

@ -523,12 +523,12 @@ let publicGroupRestrictedPermissions: TelegramChatBannedRightsFlags = [
]
func groupPermissionDependencies(_ right: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags {
if right.contains(.banSendMedia) || banSendMediaSubList().contains(where: { $0.0 == right }) {
if right.contains(.banEmbedLinks) {
return [.banSendText]
} else if right.contains(.banSendMedia) || banSendMediaSubList().contains(where: { $0.0 == right }) {
return []
} else if right.contains(.banSendGifs) {
return []
} else if right.contains(.banEmbedLinks) {
return []
} else if right.contains(.banSendPolls) {
return []
} else if right.contains(.banChangeInfo) {
@ -762,6 +762,11 @@ public func channelPermissionsController(context: AccountContext, updatedPresent
effectiveRightsFlags.insert(right)
}
}
for (right, _) in banSendMediaSubList() {
if groupPermissionDependencies(right).contains(rights) {
effectiveRightsFlags.insert(right)
}
}
}
}
if banSendMediaSubList().allSatisfy({ !effectiveRightsFlags.contains($0.0) }) {

View File

@ -7,16 +7,42 @@ private final class LinkHelperClass: NSObject {
}
public extension TelegramGroup {
enum Permission {
case sendSomething
}
func hasPermission(_ permission: Permission) -> Bool {
switch permission {
case .sendSomething:
let flags: TelegramChatBannedRightsFlags = [
.banSendText,
.banSendInstantVideos,
.banSendVoice,
.banSendPhotos,
.banSendVideos,
.banSendStickers,
.banSendPolls,
.banSendFiles,
.banSendInline
]
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.intersection(flags) == flags {
return false
}
return true
}
}
func hasBannedPermission(_ rights: TelegramChatBannedRightsFlags) -> Bool {
switch self.role {
case .creator, .admin:
case .creator, .admin:
return false
default:
if let bannedRights = self.defaultBannedRights {
return bannedRights.flags.contains(rights)
} else {
return false
default:
if let bannedRights = self.defaultBannedRights {
return bannedRights.flags.contains(rights)
} else {
return false
}
}
}
}
}

View File

@ -81,9 +81,9 @@ func _internal_randomGreetingSticker(account: Account) -> Signal<FoundStickerIte
}
}
func _internal_searchStickers(account: Account, query: String, scope: SearchStickersScope = [.installed, .remote]) -> Signal<[FoundStickerItem], NoError> {
func _internal_searchStickers(account: Account, query: String, scope: SearchStickersScope = [.installed, .remote]) -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> {
if scope.isEmpty {
return .single([])
return .single(([], true))
}
var query = query
if query == "\u{2764}" {
@ -203,9 +203,10 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
}
return (result, cached, isPremium, searchStickersConfiguration)
} |> mapToSignal { localItems, cached, isPremium, searchStickersConfiguration -> Signal<[FoundStickerItem], NoError> in
}
|> mapToSignal { localItems, cached, isPremium, searchStickersConfiguration -> Signal<(items: [FoundStickerItem], isFinalResult: Bool), NoError> in
if !scope.contains(.remote) {
return .single(localItems)
return .single((localItems, true))
}
var tempResult: [FoundStickerItem] = []
@ -281,8 +282,8 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
|> `catch` { _ -> Signal<Api.messages.Stickers, NoError> in
return .single(.stickersNotModified)
}
|> mapToSignal { result -> Signal<[FoundStickerItem], NoError> in
return account.postbox.transaction { transaction -> [FoundStickerItem] in
|> 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] = []
@ -358,14 +359,14 @@ func _internal_searchStickers(account: Account, query: String, scope: SearchStic
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerQueryResults, key: CachedStickerQueryResult.cacheKey(query)), entry: entry)
}
return result
return (result, true)
case .stickersNotModified:
break
}
return tempResult
return (tempResult, true)
}
}
return .single(tempResult)
return .single((tempResult, false))
|> then(remote)
}
}

View File

@ -30,7 +30,7 @@ public extension TelegramEngine {
return _internal_randomGreetingSticker(account: self.account)
}
public func searchStickers(query: String, scope: SearchStickersScope = [.installed, .remote]) -> Signal<[FoundStickerItem], NoError> {
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)
}

View File

@ -281,7 +281,11 @@ final class AvatarEditorScreenComponent: Component {
|> mapToSignal { keywords -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
return combineLatest(
context.account.postbox.itemCollectionsView(orderedItemListCollectionIds: [], namespaces: [Namespaces.ItemCollection.CloudEmojiPacks], aroundIndex: nil, count: 10000000) |> take(1),
combineLatest(keywords.map { context.engine.stickers.searchStickers(query: $0.emoticons.first!) })
combineLatest(keywords.map { context.engine.stickers.searchStickers(query: $0.emoticons.first!)
|> map { items -> [FoundStickerItem] in
return items.items
}
})
)
|> map { view, stickers -> [EmojiPagerContentComponent.ItemGroup] in
let hasPremium = true

View File

@ -1300,11 +1300,11 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
strongSelf.stickerSearchResult.set(.single(nil))
case let .category(value):
let resultSignal = strongSelf.context.engine.stickers.searchStickers(query: value, scope: [.installed, .remote])
|> mapToSignal { files -> Signal<[EmojiPagerContentComponent.ItemGroup], NoError> in
|> mapToSignal { files -> Signal<(items: [EmojiPagerContentComponent.ItemGroup], isFinalResult: Bool), NoError> in
var items: [EmojiPagerContentComponent.Item] = []
var existingIds = Set<MediaId>()
for item in files {
for item in files.items {
let itemFile = item.file
if existingIds.contains(itemFile.fileId) {
continue
@ -1321,7 +1321,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
items.append(item)
}
return .single([EmojiPagerContentComponent.ItemGroup(
return .single(([EmojiPagerContentComponent.ItemGroup(
supergroupId: "search",
groupId: "search",
title: nil,
@ -1335,7 +1335,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
displayPremiumBadges: false,
headerItem: nil,
items: items
)])
)], files.isFinalResult))
}
strongSelf.stickerSearchDisposable.set((resultSignal
@ -1343,7 +1343,13 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
guard let strongSelf = self else {
return
}
strongSelf.stickerSearchResult.set(.single((result, AnyHashable(value))))
guard let group = result.items.first else {
return
}
if group.items.isEmpty && !result.isFinalResult {
return
}
strongSelf.stickerSearchResult.set(.single((result.items, AnyHashable(value))))
}))
}
},

View File

@ -339,7 +339,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
let query = text.trimmingCharacters(in: .whitespacesAndNewlines)
if query.isSingleEmoji {
signals = .single([context.engine.stickers.searchStickers(query: text.basicEmoji.0)
|> map { (nil, $0) }])
|> map { (nil, $0.items) }])
} else if query.count > 1, let languageCode = languageCode, !languageCode.isEmpty && languageCode != "emoji" {
var signal = context.engine.stickers.searchEmojiKeywords(inputLanguageCode: languageCode, query: query.lowercased(), completeMatch: query.count < 3)
if !languageCode.lowercased().hasPrefix("en") {
@ -362,7 +362,7 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
for emoji in emoticons {
signals.append(context.engine.stickers.searchStickers(query: emoji.basicEmoji.0)
// |> take(1)
|> map { (emoji, $0) })
|> map { (emoji, $0.items) })
}
return signals
}

View File

@ -5008,6 +5008,15 @@ public final class EmojiPagerContentComponent: Component {
scrollView.bounds = presentation.bounds
scrollView.layer.removeAllAnimations()
}
if self.isSearchActivated, let visibleSearchHeader = self.visibleSearchHeader, visibleSearchHeader.currentPresetSearchTerm == nil {
scrollView.isScrollEnabled = false
DispatchQueue.main.async {
scrollView.isScrollEnabled = true
}
self.visibleSearchHeader?.deactivate()
}
self.component?.inputInteractionHolder.inputInteraction?.onScroll()
}
public func ensureSearchUnfocused() {
@ -5026,11 +5035,6 @@ public final class EmojiPagerContentComponent: Component {
self.updateVisibleItems(transition: .immediate, attemptSynchronousLoads: false, previousItemPositions: nil, updatedItemPositions: nil)
self.updateScrollingOffset(isReset: false, transition: .immediate)
if self.isSearchActivated, let visibleSearchHeader = self.visibleSearchHeader, visibleSearchHeader.currentPresetSearchTerm == nil {
self.visibleSearchHeader?.deactivate()
}
self.component?.inputInteractionHolder.inputInteraction?.onScroll()
}
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
@ -6375,7 +6379,7 @@ public final class EmojiPagerContentComponent: Component {
}
}
} else {
/*if component.inputInteractionHolder.inputInteraction?.externalBackground == nil {
/*if useOpaqueTheme {
if visibleSearchHeader.superview != self.scrollView {
self.scrollView.addSubview(visibleSearchHeader)
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
@ -6431,7 +6435,7 @@ public final class EmojiPagerContentComponent: Component {
let searchHeaderFrame = CGRect(origin: CGPoint(x: itemLayout.searchInsets.left, y: itemLayout.searchInsets.top), size: CGSize(width: itemLayout.width - itemLayout.searchInsets.left - itemLayout.searchInsets.right, height: itemLayout.searchHeight))
visibleSearchHeader.update(context: component.context, theme: keyboardChildEnvironment.theme, strings: keyboardChildEnvironment.strings, text: displaySearchWithPlaceholder, useOpaqueTheme: useOpaqueTheme, isActive: self.isSearchActivated, size: searchHeaderFrame.size, canFocus: !component.searchIsPlaceholderOnly, searchCategories: component.searchCategories, transition: transition)
transition.attachAnimation(view: visibleSearchHeader, completion: { [weak self] completed in
/*transition.attachAnimation(view: visibleSearchHeader, id: "search_transition", completion: { [weak self] completed in
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
return
}
@ -6440,8 +6444,41 @@ public final class EmojiPagerContentComponent: Component {
strongSelf.scrollView.addSubview(visibleSearchHeader)
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
}
})
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame)
})*/
if visibleSearchHeader.frame != searchHeaderFrame {
transition.setFrame(view: visibleSearchHeader, frame: searchHeaderFrame, completion: { [weak self] completed in
if !useOpaqueTheme {
guard let strongSelf = self, completed, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
return
}
if !strongSelf.isSearchActivated && visibleSearchHeader.superview != strongSelf.scrollView {
strongSelf.scrollView.addSubview(visibleSearchHeader)
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
}
}
})
// Temporary workaround for status selection; use a separate search container (see GIF)
if useOpaqueTheme {
if case let .curve(duration, _) = transition.animation, duration != 0.0 {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration, execute: { [weak self] in
guard let strongSelf = self, let visibleSearchHeader = strongSelf.visibleSearchHeader else {
return
}
if !strongSelf.isSearchActivated && visibleSearchHeader.superview != strongSelf.scrollView {
strongSelf.scrollView.addSubview(visibleSearchHeader)
strongSelf.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
}
})
} else {
if !self.isSearchActivated && visibleSearchHeader.superview != self.scrollView {
self.scrollView.addSubview(visibleSearchHeader)
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
}
}
}
}
} else {
if let visibleSearchHeader = self.visibleSearchHeader {
self.visibleSearchHeader = nil

View File

@ -288,6 +288,7 @@ final class EmojiSearchSearchBarComponent: Component {
component.searchTermUpdated(group.identifiers.joined(separator: ""))
} else {
component.searchTermUpdated(nil)
self.scrollView.setContentOffset(CGPoint(), animated: true)
}
break
@ -301,6 +302,7 @@ final class EmojiSearchSearchBarComponent: Component {
if self.selectedItem != nil {
self.selectedItem = nil
self.state?.updated(transition: .immediate)
self.scrollView.setContentOffset(CGPoint(), animated: true)
if dispatchEvent {
self.component?.searchTermUpdated(nil)
}

View File

@ -447,10 +447,10 @@ final class DataUsageScreenComponent: Component {
headerOffset = min(headerOffset, minOffset)
let animatedTransition = Transition(animation: .curve(duration: 0.18, curve: .easeInOut))
let animatedTransition = Transition(animation: .curve(duration: 0.2, curve: .easeInOut))
let navigationBackgroundAlpha: CGFloat = abs(headerOffset - minOffset) < 4.0 ? 1.0 : 0.0
let navigationButtonAlpha: CGFloat = abs(headerOffset - minOffset) < 62.0 ? 0.0 : 1.0
let navigationButtonAlpha: CGFloat = scrollBounds.minY >= navigationMetrics.navigationHeight ? 0.0 : 1.0
animatedTransition.setAlpha(view: self.navigationBackgroundView, alpha: navigationBackgroundAlpha)
animatedTransition.setAlpha(layer: self.navigationSeparatorLayerContainer, alpha: navigationBackgroundAlpha)
@ -468,17 +468,19 @@ final class DataUsageScreenComponent: Component {
transition.setBounds(view: self.headerOffsetContainer, bounds: CGRect(origin: CGPoint(x: 0.0, y: headerOffset), size: self.headerOffsetContainer.bounds.size))
if let controller = self.controller?(), let backButtonNode = controller.navigationBar?.backButtonNode {
if backButtonNode.isHidden {
backButtonNode.alpha = 0.0
backButtonNode.isHidden = false
}
animatedTransition.setAlpha(layer: backButtonNode.layer, alpha: navigationButtonAlpha, completion: { [weak backButtonNode] completed in
if let backButtonNode, completed {
if navigationButtonAlpha.isZero {
backButtonNode.isHidden = true
}
if backButtonNode.alpha != navigationButtonAlpha {
if backButtonNode.isHidden {
backButtonNode.alpha = 0.0
backButtonNode.isHidden = false
}
})
animatedTransition.setAlpha(layer: backButtonNode.layer, alpha: navigationButtonAlpha, completion: { [weak backButtonNode] completed in
if let backButtonNode, completed {
if navigationButtonAlpha.isZero {
backButtonNode.isHidden = true
}
}
})
}
}
}
}

View File

@ -18184,17 +18184,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var itemList: [String] = []
var flags: TelegramChatBannedRightsFlags = []
if let channel = peer as? TelegramChannel {
if let bannedRights = channel.bannedRights {
flags = bannedRights.flags
}
} else if let group = peer as? TelegramGroup {
if let bannedRights = group.defaultBannedRights {
flags = bannedRights.flags
}
}
let order: [TelegramChatBannedRightsFlags] = [
.banSendText,
.banSendPhotos,
@ -18207,32 +18196,40 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
]
for right in order {
if !flags.contains(right) {
var title: String?
switch right {
case .banSendText:
title = "text messages"
case .banSendPhotos:
title = "photos"
case .banSendVideos:
title = "videos"
case .banSendVoice:
title = "voice messages"
case .banSendInstantVideos:
title = "video messages"
case .banSendFiles:
title = "files"
case .banSendMusic:
title = "music"
case .banSendStickers:
title = "Stickers & GIFs"
default:
break
if let channel = peer as? TelegramChannel {
if channel.hasBannedPermission(right) != nil {
continue
}
if let title {
itemList.append(title)
} else if let group = peer as? TelegramGroup {
if group.hasBannedPermission(right) {
continue
}
}
var title: String?
switch right {
case .banSendText:
title = "text messages"
case .banSendPhotos:
title = "photos"
case .banSendVideos:
title = "videos"
case .banSendVoice:
title = "voice messages"
case .banSendInstantVideos:
title = "video messages"
case .banSendFiles:
title = "files"
case .banSendMusic:
title = "music"
case .banSendStickers:
title = "Stickers & GIFs"
default:
break
}
if let title {
itemList.append(title)
}
}
if itemList.isEmpty {

View File

@ -128,6 +128,9 @@ private func updatedContextQueryResultStateForQuery(context: AccountContext, pee
scope = [.installed]
}
return context.engine.stickers.searchStickers(query: query.basicEmoji.0, scope: scope)
|> map { items -> [FoundStickerItem] in
return items.items
}
|> castError(ChatContextQueryError.self)
}
|> map { stickers -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in

View File

@ -196,15 +196,15 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
}
}
if isMember && channel.hasBannedPermission(.banSendText) != nil && !channel.flags.contains(.isGigagroup) {
/*if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
if isMember && !channel.hasPermission(.sendSomething) && !channel.flags.contains(.isGigagroup) {
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
return (currentPanel, nil)
} else {
let panel = ChatRestrictedInputPanelNode()
panel.context = context
panel.interfaceInteraction = interfaceInteraction
return (panel, nil)
}*/
}
}
switch channel.info {
@ -280,15 +280,15 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
break
}
if group.hasBannedPermission(.banSendText) {
/*if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
if !group.hasPermission(.sendSomething) {
if let currentPanel = (currentPanel as? ChatRestrictedInputPanelNode) ?? (currentSecondaryPanel as? ChatRestrictedInputPanelNode) {
return (currentPanel, nil)
} else {
let panel = ChatRestrictedInputPanelNode()
panel.context = context
panel.interfaceInteraction = interfaceInteraction
return (panel, nil)
}*/
}
}
}

View File

@ -2126,9 +2126,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
mediaInputDisabled = true
} else if interfaceState.hasActiveGroupCall {
mediaInputDisabled = true
} else if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel, channel.hasBannedPermission(.banSendMedia) != nil {
} else if let channel = interfaceState.renderedPeer?.peer as? TelegramChannel, channel.hasBannedPermission(.banSendVoice) != nil, channel.hasBannedPermission(.banSendInstantVideos) != nil {
mediaInputDisabled = true
} else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup, group.hasBannedPermission(.banSendMedia) {
} else if let group = interfaceState.renderedPeer?.peer as? TelegramGroup, group.hasBannedPermission(.banSendVoice), group.hasBannedPermission(.banSendInstantVideos) {
mediaInputDisabled = true
} else {
mediaInputDisabled = false

View File

@ -191,7 +191,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 2
self.textNode.maximumNumberOfLines = 10
displayUndo = false
self.originalRemainingSeconds = Double(max(5, min(8, text.count / 14)))