diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 0d1d5581b4..555f592121 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8847,3 +8847,7 @@ Sorry for the inconvenience."; "Chat.PlaceholderTextNotAllowed" = "Text not allowed"; "CallList.DeleteAll" = "Delete All"; + +"DataUsage.TopSectionAll" = "All"; +"DataUsage.TopSectionMobile" = "Mobile"; +"DataUsage.TopSectionWifi" = "Wifi"; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h index 990b94fba6..92b07e7673 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h @@ -76,4 +76,6 @@ + (NSData *)_manuallyEncryptedMessage:(NSData *)preparedData messageId:(int64_t)messageId authKey:(MTDatacenterAuthKey *)authKey; +- (void)simulateDisconnection; + @end diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h index 24143e7ba0..9349935610 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h @@ -62,4 +62,6 @@ - (void)updateSchemes:(NSArray * _Nonnull)schemes; +- (void)simulateDisconnection; + @end diff --git a/submodules/MtProtoKit/Sources/MTContext.m b/submodules/MtProtoKit/Sources/MTContext.m index b3cce52b46..46b9b05bc2 100644 --- a/submodules/MtProtoKit/Sources/MTContext.m +++ b/submodules/MtProtoKit/Sources/MTContext.m @@ -228,7 +228,7 @@ static int32_t fixedTimeDifferenceValue = 0; _isTestingEnvironment = isTestingEnvironment; _useTempAuthKeys = useTempAuthKeys; #if DEBUG - _tempKeyExpiration = 10 * 60; + _tempKeyExpiration = 30; #else _tempKeyExpiration = 24 * 60 * 60; #endif diff --git a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m index 271707bf12..7a2050fe9c 100644 --- a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m +++ b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m @@ -247,6 +247,12 @@ typedef enum { } case MTDatacenterAuthStageReqDH: { +#if DEBUG + if (arc4random_uniform(100) < 50) { + [mtProto simulateDisconnection]; + } +#endif + MTBuffer *reqDhBuffer = [[MTBuffer alloc] init]; [reqDhBuffer appendInt32:(int32_t)0xd712e4be]; [reqDhBuffer appendBytes:_nonce.bytes length:_nonce.length]; diff --git a/submodules/MtProtoKit/Sources/MTProto.m b/submodules/MtProtoKit/Sources/MTProto.m index d15b5accb1..e4c8058f85 100644 --- a/submodules/MtProtoKit/Sources/MTProto.m +++ b/submodules/MtProtoKit/Sources/MTProto.m @@ -657,6 +657,15 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; }]; } +- (void)simulateDisconnection { + [[MTProto managerQueue] dispatchOnQueue:^ + { + if (_transport != nil) { + [_transport simulateDisconnection]; + } + }]; +} + - (bool)canAskForTransactions { return (_mtState & (MTProtoStateAwaitingDatacenterScheme | MTProtoStateAwaitingDatacenterAuthorization | MTProtoStateAwaitingDatacenterAuthToken | MTProtoStateAwaitingTimeFixAndSalts | MTProtoStateStopped)) == 0; @@ -770,7 +779,10 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return; } if (_useUnauthorizedMode) { +#if DEBUG +#else return; +#endif } if (hasConnectionProblems) { diff --git a/submodules/MtProtoKit/Sources/MTTcpTransport.m b/submodules/MtProtoKit/Sources/MTTcpTransport.m index fe01d9e39d..7c95f93c83 100644 --- a/submodules/MtProtoKit/Sources/MTTcpTransport.m +++ b/submodules/MtProtoKit/Sources/MTTcpTransport.m @@ -804,4 +804,18 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0; }]; } +- (void)simulateDisconnection { + MTTcpTransportContext *transportContext = _transportContext; + [[MTTcpTransport tcpTransportQueue] dispatchOnQueue:^ { + if (transportContext.connection.scheme != nil) { + MTTransportScheme *scheme = transportContext.connection.scheme; + __weak MTTcpTransport *weakSelf = self; + dispatch_async([MTTcpTransport tcpTransportQueue].nativeQueue, ^{ + __strong MTTcpTransport *strongSelf = weakSelf; + [strongSelf connectionWatchdogTimeout:scheme]; + }); + } + }]; +} + @end diff --git a/submodules/MtProtoKit/Sources/MTTransport.m b/submodules/MtProtoKit/Sources/MTTransport.m index d7e42ec43e..ab2511226f 100644 --- a/submodules/MtProtoKit/Sources/MTTransport.m +++ b/submodules/MtProtoKit/Sources/MTTransport.m @@ -92,4 +92,7 @@ - (void)updateSchemes:(NSArray * _Nonnull)schemes { } +- (void)simulateDisconnection { +} + @end diff --git a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift index 39b02b74d5..1ec1793cbe 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionContextNode.swift @@ -1475,6 +1475,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: items )] } @@ -1528,6 +1529,7 @@ public final class ReactionContextNode: ASDisplayNode, UIScrollViewDelegate { collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: items )], isFinalResult)) } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift index 0d5113526a..a04b582960 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift @@ -397,10 +397,14 @@ final class StickerPackEmojisItemNode: GridItemNode { if let current = strongSelf.visibleItemPlaceholderViews[itemId] { placeholderView = current } else { + var placeholderContent: EmojiPagerContentComponent.View.ItemPlaceholderView.Content? + if let immediateThumbnailData = item.file.immediateThumbnailData { + placeholderContent = .thumbnail(immediateThumbnailData) + } placeholderView = EmojiPagerContentComponent.View.ItemPlaceholderView( context: context, dimensions: item.file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0), - immediateThumbnailData: item.file.immediateThumbnailData, + content: placeholderContent, shimmerView: nil,//strongSelf.shimmerHostView, color: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08), size: itemNativeFitSize diff --git a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift index f291623ed0..8a5b48d65a 100644 --- a/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift +++ b/submodules/StickerPeekUI/Sources/StickerPreviewPeekContent.swift @@ -233,11 +233,11 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC let imageSize = dimensitons.cgSize.aspectFitted(boundingSize) self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() - var imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: textSize.height + textSpacing - topOffset), size: imageSize) + var imageFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0), y: textSize.height + textSpacing - topOffset), size: imageSize) var centerOffset: CGFloat = 0.0 if self.item.file.isPremiumSticker { let originalImageFrame = imageFrame - imageFrame.origin.x = size.width - imageFrame.width - 18.0 + imageFrame.origin.x = min(imageFrame.minX + imageFrame.width * 0.1, size.width - imageFrame.width - 18.0) centerOffset = imageFrame.minX - originalImageFrame.minX } self.imageNode.frame = imageFrame @@ -254,9 +254,9 @@ public final class StickerPreviewPeekContentNode: ASDisplayNode, PeekControllerC self.textNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.size.width - textSize.width) / 2.0) - centerOffset, y: -textSize.height - textSpacing), size: textSize) if self.item.file.isCustomEmoji { - return CGSize(width: size.width, height: imageFrame.height) + return CGSize(width: boundingSize.width, height: imageFrame.height) } else { - return CGSize(width: size.width, height: imageFrame.height + textSize.height + textSpacing) + return CGSize(width: boundingSize.width, height: imageFrame.height + textSize.height + textSpacing) } } else { return CGSize(width: size.width, height: 10.0) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift index 08b2e85901..dd77464a69 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/SearchStickers.swift @@ -476,6 +476,9 @@ func _internal_searchEmoji(account: Account, query: [String], scope: SearchStick if let currentCached = cached, currentTime > currentCached.timestamp + searchStickersConfiguration.cacheTimeout { cached = nil } + #if DEBUG + cached = nil + #endif return (result, cached, isPremium, searchStickersConfiguration) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index 0585869b22..017a5c1683 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -189,32 +189,6 @@ public extension TelegramEngine { |> map { items, isFinalResult -> (items: [TelegramMediaFile], isFinalResult: Bool) in return (items.map(\.file), isFinalResult) } - - /*return self.account.network.request(Api.functions.messages.searchCustomEmoji(emoticon: emojiString.joined(separator: ""), hash: 0)) - |> map(Optional.init) - |> `catch` { _ -> Signal in - return .single(nil) - } - |> mapToSignal { result -> Signal<[TelegramMediaFile], NoError> in - guard let result = result else { - return .single([]) - } - switch result { - case let .emojiList(_, documentIds): - return self.resolveInlineStickers(fileIds: documentIds) - |> map { result -> [TelegramMediaFile] in - var files: [TelegramMediaFile] = [] - for id in documentIds { - if let file = result[id] { - files.append(file) - } - } - return files - } - default: - return .single([]) - } - }*/ } } } diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift index 9010aeaacc..4200658310 100644 --- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift +++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift @@ -435,6 +435,7 @@ final class AvatarEditorScreenComponent: Component { collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: emojiItems ) ) @@ -454,6 +455,7 @@ final class AvatarEditorScreenComponent: Component { collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: stickerItems ) ) @@ -510,6 +512,7 @@ final class AvatarEditorScreenComponent: Component { collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: items )], isFinalResult)) } diff --git a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift index 11d965be51..5534b909e0 100644 --- a/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/ChatEntityKeyboardInputNode/Sources/ChatEntityKeyboardInputNode.swift @@ -1087,6 +1087,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: items )] } @@ -1140,6 +1141,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: items )], isFinalResult)) } @@ -1155,12 +1157,31 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { return } if group.items.isEmpty && !result.isFinalResult { - self.emojiSearchStateValue.isSearching = true + //self.emojiSearchStateValue.isSearching = true + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: [ + EmojiPagerContentComponent.ItemGroup( + supergroupId: "search", + groupId: "search", + title: nil, + subtitle: nil, + actionButtonTitle: nil, + isFeatured: false, + isPremiumLocked: false, + isEmbedded: false, + hasClear: false, + collapsedLineCount: nil, + displayPremiumBadges: false, + headerItem: nil, + fillWithLoadingPlaceholders: true, + items: [] + ) + ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) return } - - self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) - version += 1 + //DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0, execute: { + self.emojiSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) + version += 1 + //}) })) } }, @@ -1416,6 +1437,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: items )], files.isFinalResult)) } @@ -1430,7 +1452,25 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { return } if group.items.isEmpty && !result.isFinalResult { - strongSelf.stickerSearchStateValue.isSearching = true + //strongSelf.stickerSearchStateValue.isSearching = true + strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: [ + EmojiPagerContentComponent.ItemGroup( + supergroupId: "search", + groupId: "search", + title: nil, + subtitle: nil, + actionButtonTitle: nil, + isFeatured: false, + isPremiumLocked: false, + isEmbedded: false, + hasClear: false, + collapsedLineCount: nil, + displayPremiumBadges: false, + headerItem: nil, + fillWithLoadingPlaceholders: true, + items: [] + ) + ], id: AnyHashable(value), version: version, isPreset: true), isSearching: false) return } strongSelf.stickerSearchStateValue = EmojiSearchState(result: EmojiSearchResult(groups: result.items, id: AnyHashable(value), version: version, isPreset: true), isSearching: false) @@ -1467,7 +1507,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { if let emojiSearchResult = emojiSearchState.result { var emptySearchResults: EmojiPagerContentComponent.EmptySearchResults? - if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty }) { + if !emojiSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { emptySearchResults = EmojiPagerContentComponent.EmptySearchResults( text: presentationData.strings.EmojiSearch_SearchEmojiEmptyResult, iconFile: nil @@ -1485,7 +1525,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode { if let stickerSearchResult = stickerSearchState.result { var stickerSearchResults: EmojiPagerContentComponent.EmptySearchResults? - if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty }) { + if !stickerSearchResult.groups.contains(where: { !$0.items.isEmpty || $0.fillWithLoadingPlaceholders }) { stickerSearchResults = EmojiPagerContentComponent.EmptySearchResults( text: presentationData.strings.EmojiSearch_SearchStickersEmptyResult, iconFile: nil diff --git a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift index 6100057c76..aedaa8273c 100644 --- a/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusSelectionComponent/Sources/EmojiStatusSelectionComponent.swift @@ -568,6 +568,7 @@ public final class EmojiStatusSelectionController: ViewController { collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: items )] } @@ -621,6 +622,7 @@ public final class EmojiStatusSelectionController: ViewController { collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: items )], isFinalResult)) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 558d0cd0da..77e737ac36 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -2472,6 +2472,7 @@ public final class EmojiPagerContentComponent: Component { public let collapsedLineCount: Int? public let displayPremiumBadges: Bool public let headerItem: EntityKeyboardAnimationData? + public let fillWithLoadingPlaceholders: Bool public let items: [Item] public init( @@ -2487,6 +2488,7 @@ public final class EmojiPagerContentComponent: Component { collapsedLineCount: Int?, displayPremiumBadges: Bool, headerItem: EntityKeyboardAnimationData?, + fillWithLoadingPlaceholders: Bool, items: [Item] ) { self.supergroupId = supergroupId @@ -2501,6 +2503,7 @@ public final class EmojiPagerContentComponent: Component { self.collapsedLineCount = collapsedLineCount self.displayPremiumBadges = displayPremiumBadges self.headerItem = headerItem + self.fillWithLoadingPlaceholders = fillWithLoadingPlaceholders self.items = items } @@ -2544,6 +2547,9 @@ public final class EmojiPagerContentComponent: Component { if lhs.headerItem != rhs.headerItem { return false } + if lhs.fillWithLoadingPlaceholders != rhs.fillWithLoadingPlaceholders { + return false + } if lhs.items != rhs.items { return false } @@ -3035,6 +3041,11 @@ public final class EmojiPagerContentComponent: Component { } public final class ItemPlaceholderView: UIView { + public enum Content { + case thumbnail(Data) + case template(UIImage) + } + private let shimmerView: PortalSourceView? private var placeholderView: PortalView? private let placeholderMaskLayer: SimpleLayer @@ -3043,7 +3054,7 @@ public final class EmojiPagerContentComponent: Component { public init( context: AccountContext, dimensions: CGSize?, - immediateThumbnailData: Data?, + content: Content?, shimmerView: PortalSourceView?, color: UIColor, size: CGSize @@ -3063,19 +3074,31 @@ public final class EmojiPagerContentComponent: Component { } let useDirectContent = self.placeholderView == nil - Queue.concurrentDefaultQueue().async { [weak self] in - if let image = generateStickerPlaceholderImage(data: immediateThumbnailData, size: size, scale: min(2.0, UIScreenScale), imageSize: dimensions ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: useDirectContent ? color : .black) { - Queue.mainQueue().async { - guard let strongSelf = self else { - return - } - - if useDirectContent { - strongSelf.layer.contents = image.cgImage - } else { - strongSelf.placeholderMaskLayer.contents = image.cgImage + if let content { + switch content { + case let .thumbnail(immediateThumbnailData): + Queue.concurrentDefaultQueue().async { [weak self] in + if let image = generateStickerPlaceholderImage(data: immediateThumbnailData, size: size, scale: min(2.0, UIScreenScale), imageSize: dimensions ?? CGSize(width: 512.0, height: 512.0), backgroundColor: nil, foregroundColor: useDirectContent ? color : .black) { + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + + if useDirectContent { + strongSelf.layer.contents = image.cgImage + } else { + strongSelf.placeholderMaskLayer.contents = image.cgImage + } + } } } + case let .template(templateImage): + if useDirectContent { + self.layer.contents = templateImage.cgImage + self.tintColor = color + } else { + self.placeholderMaskLayer.contents = templateImage.cgImage + } } } } @@ -3590,6 +3613,7 @@ public final class EmojiPagerContentComponent: Component { private var visibleSearchHeader: EmojiSearchHeaderView? private var visibleEmptySearchResultsView: EmptySearchResultsView? private var visibleItemPlaceholderViews: [ItemLayer.Key: ItemPlaceholderView] = [:] + private var visibleFillPlaceholdersViews: [Int: ItemPlaceholderView] = [:] private var visibleItemSelectionLayers: [ItemLayer.Key: ItemSelectionLayer] = [:] private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:] private var visibleGroupHeaders: [AnyHashable: GroupHeaderLayer] = [:] @@ -3600,6 +3624,16 @@ public final class EmojiPagerContentComponent: Component { private var ignoreScrolling: Bool = false private var keepTopPanelVisibleUntilScrollingInput: Bool = false + private struct FillPlaceholderParams: Equatable { + var size: CGSize + + init(size: CGSize) { + self.size = size + } + } + + private var fillPlaceholder: (params: FillPlaceholderParams, image: UIImage)? + private var component: EmojiPagerContentComponent? private weak var state: EmptyComponentState? private var pagerEnvironment: PagerComponentChildEnvironment? @@ -5243,6 +5277,7 @@ public final class EmojiPagerContentComponent: Component { var validGroupBorderIds = Set() var validGroupPremiumButtonIds = Set() var validGroupExpandActionButtons = Set() + var validFillPlaceholdersIndices = Set() let effectiveVisibleBounds = CGRect(origin: self.scrollView.bounds.origin, size: self.effectiveVisibleSize) let topVisibleDetectionBounds = effectiveVisibleBounds.offsetBy(dx: 0.0, dy: pagerEnvironment.containerInsets.top) @@ -5540,7 +5575,6 @@ public final class EmojiPagerContentComponent: Component { if !itemGroup.isEmbedded, let groupItemRange = groupItems.groupItems { for index in groupItemRange.lowerBound ..< groupItemRange.upperBound { let item = itemGroup.items[index] - if assignTopVisibleSubgroupId { if let subgroupId = item.subgroupId { @@ -5600,10 +5634,14 @@ public final class EmojiPagerContentComponent: Component { if let current = strongSelf.visibleItemPlaceholderViews[itemId] { placeholderView = current } else { + var placeholderContent: ItemPlaceholderView.Content? + if let immediateThumbnailData = animationData.immediateThumbnailData { + placeholderContent = .thumbnail(immediateThumbnailData) + } placeholderView = ItemPlaceholderView( context: component.context, dimensions: animationData.dimensions, - immediateThumbnailData: animationData.immediateThumbnailData, + content: placeholderContent, shimmerView: strongSelf.shimmerHostView, color: placeholderColor, size: itemNativeFitSize @@ -5756,6 +5794,61 @@ public final class EmojiPagerContentComponent: Component { itemLayer.isVisibleForAnimations = keyboardChildEnvironment.isContentInFocus } } + if itemGroup.fillWithLoadingPlaceholders { + let placeholderSizeFactor: CGFloat = 0.9 + let placeholderColor = keyboardChildEnvironment.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1) + let fillPlaceholderImage: UIImage? + let fillPlaceholderParams = FillPlaceholderParams(size: CGSize(width: floor(itemLayout.nativeItemSize * placeholderSizeFactor), height: floor(itemLayout.nativeItemSize * placeholderSizeFactor))) + if let current = self.fillPlaceholder, current.params == fillPlaceholderParams { + fillPlaceholderImage = current.image + } else { + switch component.itemLayoutType { + case .compact: + fillPlaceholderImage = generateFilledCircleImage(diameter: fillPlaceholderParams.size.width, color: .black) + case .detailed: + fillPlaceholderImage = generateFilledRoundedRectImage(size: fillPlaceholderParams.size, cornerRadius: floor(fillPlaceholderParams.size.width * 0.2), color: .black) + } + if let fillPlaceholderImage { + self.fillPlaceholder = (fillPlaceholderParams, fillPlaceholderImage) + } + } + let fillPlaceholderContent: ItemPlaceholderView.Content? = fillPlaceholderImage.flatMap(ItemPlaceholderView.Content.template) + + var placeholderIndex = groupItems.groupItems?.lowerBound ?? 0 + while true { + var itemFrame = itemLayout.frame(groupIndex: groupItems.groupIndex, itemIndex: placeholderIndex) + if itemFrame.minY >= effectiveVisibleBounds.maxY { + break + } + let visibleItemSize = CGSize(width: floor(itemFrame.width * placeholderSizeFactor), height: floor(itemFrame.height * placeholderSizeFactor)) + itemFrame = CGRect(origin: CGPoint(x: floor(itemFrame.midX - visibleItemSize.width * 0.5), y: floor(itemFrame.midY - visibleItemSize.height * 0.5)), size: visibleItemSize) + + validFillPlaceholdersIndices.insert(placeholderIndex) + + let placeholderView: ItemPlaceholderView + if let current = self.visibleFillPlaceholdersViews[placeholderIndex] { + placeholderView = current + } else { + placeholderView = ItemPlaceholderView( + context: component.context, + dimensions: nil, + content: fillPlaceholderContent, + shimmerView: self.shimmerHostView, + color: placeholderColor, + size: itemFrame.size + ) + self.visibleFillPlaceholdersViews[placeholderIndex] = placeholderView + self.placeholdersContainerView.addSubview(placeholderView) + } + + placeholderView.frame = itemFrame + placeholderView.update(size: itemFrame.size) + + placeholderIndex += 1 + } + + self.updateShimmerIfNeeded() + } } var removedPlaceholerViews = false @@ -5869,6 +5962,26 @@ public final class EmojiPagerContentComponent: Component { self.visibleItemSelectionLayers.removeValue(forKey: id) } + var removedFillPlaceholderIndices: [Int] = [] + for (index, placeholderView) in self.visibleFillPlaceholdersViews { + if !validFillPlaceholdersIndices.contains(index) { + if !transition.animation.isImmediate { + placeholderView.alpha = 0.0 + placeholderView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderView] _ in + placeholderView?.removeFromSuperview() + }) + } else { + placeholderView.removeFromSuperview() + } + + removedFillPlaceholderIndices.append(index) + removedPlaceholerViews = true + } + } + for index in removedFillPlaceholderIndices { + self.visibleFillPlaceholdersViews.removeValue(forKey: index) + } + var removedGroupHeaderIds: [AnyHashable] = [] for (id, groupHeaderLayer) in self.visibleGroupHeaders { if !validGroupHeaderIds.contains(id) { @@ -6679,7 +6792,7 @@ public final class EmojiPagerContentComponent: Component { var animateContentCrossfade = false if let previousComponent, previousComponent.itemContentUniqueId != component.itemContentUniqueId, itemTransition.animation.isImmediate { - if previousComponent.itemContentUniqueId?.id != component.itemContentUniqueId?.id { + if !(previousComponent.contentItemGroups.contains(where: { $0.fillWithLoadingPlaceholders }) && component.contentItemGroups.contains(where: { $0.fillWithLoadingPlaceholders })) && previousComponent.itemContentUniqueId?.id != component.itemContentUniqueId?.id { animateContentCrossfade = true } } @@ -6705,6 +6818,15 @@ public final class EmojiPagerContentComponent: Component { snapshotLayer.animateScale(from: 1.0, to: crossfadeMinScale, duration: 0.2, removeOnCompletion: false) } } + for (_, placeholderView) in self.visibleFillPlaceholdersViews { + if let snapshotLayer = placeholderView.layer.snapshotContentTree() { + placeholderView.layer.superlayer?.insertSublayer(snapshotLayer, above: placeholderView.layer) + snapshotLayer.animateAlpha(from: CGFloat(snapshotLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotLayer] _ in + snapshotLayer?.removeFromSuperlayer() + }) + snapshotLayer.animateScale(from: 1.0, to: crossfadeMinScale, duration: 0.2, removeOnCompletion: false) + } + } for (_, selectionLayer) in self.visibleItemSelectionLayers { if let snapshotLayer = selectionLayer.snapshotContentTree() { selectionLayer.superlayer?.insertSublayer(snapshotLayer, above: selectionLayer) @@ -6758,6 +6880,10 @@ public final class EmojiPagerContentComponent: Component { placeholderView.layer.animateAlpha(from: 0.0, to: CGFloat(placeholderView.layer.opacity), duration: 0.2) placeholderView.layer.animateScale(from: crossfadeMinScale, to: 1.0, duration: 0.2) } + for (_, placeholderView) in self.visibleFillPlaceholdersViews { + placeholderView.layer.animateAlpha(from: 0.0, to: CGFloat(placeholderView.layer.opacity), duration: 0.2) + placeholderView.layer.animateScale(from: crossfadeMinScale, to: 1.0, duration: 0.2) + } for (_, selectionLayer) in self.visibleItemSelectionLayers { selectionLayer.animateAlpha(from: 0.0, to: CGFloat(selectionLayer.opacity), duration: 0.2) } @@ -7718,6 +7844,7 @@ public final class EmojiPagerContentComponent: Component { collapsedLineCount: group.collapsedLineCount, displayPremiumBadges: false, headerItem: headerItem, + fillWithLoadingPlaceholders: false, items: group.items ) } @@ -8244,6 +8371,7 @@ public final class EmojiPagerContentComponent: Component { collapsedLineCount: nil, displayPremiumBadges: group.displayPremiumBadges, headerItem: group.headerItem, + fillWithLoadingPlaceholders: false, items: group.items ) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift index 49c742c471..b67a811ef7 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchContent.swift @@ -123,6 +123,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode collapsedLineCount: 3, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: groupItems )) } @@ -286,6 +287,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: items )] } @@ -339,6 +341,7 @@ public final class EmojiSearchContent: ASDisplayNode, EntitySearchContainerNode collapsedLineCount: nil, displayPremiumBadges: false, headerItem: nil, + fillWithLoadingPlaceholders: false, items: items )], isFinalResult)) } diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift index ec1d0a397e..c87fe101c6 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift @@ -215,10 +215,14 @@ final class EntityKeyboardAnimationTopPanelComponent: Component { private func updateDisplayPlaceholder(displayPlaceholder: Bool, duration: Double) { if displayPlaceholder { if self.placeholderView == nil, let component = self.component { + var placeholderContent: EmojiPagerContentComponent.View.ItemPlaceholderView.Content? + if let immediateThumbnailData = component.item.immediateThumbnailData { + placeholderContent = .thumbnail(immediateThumbnailData) + } let placeholderView = EmojiPagerContentComponent.View.ItemPlaceholderView( context: component.context, dimensions: component.item.dimensions, - immediateThumbnailData: component.item.immediateThumbnailData, + content: placeholderContent, shimmerView: nil, color: component.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08), size: CGSize(width: 28.0, height: 28.0) diff --git a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift index b1f87a59a5..8bd6292eee 100644 --- a/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift +++ b/submodules/TelegramUI/Components/StorageUsageScreen/Sources/DataUsageScreen.swift @@ -913,9 +913,9 @@ final class DataUsageScreenComponent: Component { component: AnyComponent(SegmentControlComponent( theme: environment.theme, items: [ - SegmentControlComponent.Item(id: AnyHashable(SelectedStats.all), title: "All"), - SegmentControlComponent.Item(id: AnyHashable(SelectedStats.mobile), title: "Mobile"), - SegmentControlComponent.Item(id: AnyHashable(SelectedStats.wifi), title: "WiFi") + SegmentControlComponent.Item(id: AnyHashable(SelectedStats.all), title: environment.strings.DataUsage_TopSectionAll), + SegmentControlComponent.Item(id: AnyHashable(SelectedStats.mobile), title: environment.strings.DataUsage_TopSectionMobile), + SegmentControlComponent.Item(id: AnyHashable(SelectedStats.wifi), title: environment.strings.DataUsage_TopSectionWifi) ], selectedId: "total", action: { [weak self] id in diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 8c0a70d353..fca3e1ac8f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -2479,7 +2479,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { textView.inputView = updatedInputView if textView.isFirstResponder { if self.chatPresentationInterfaceStateRequiresInputFocus(chatPresentationInterfaceState) { - waitForKeyboardLayout = true + if let validLayout = self.validLayout, validLayout.0.inputHeight != nil { + waitForKeyboardLayout = true + } } textView.reloadInputViews() }