mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '1e0e0de7c83a5b0e9be7f7d7dbfc5f5ddce8e084'
This commit is contained in:
commit
ee7d4b125b
@ -1289,7 +1289,7 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
|
||||
self.didEndScrolling = { [weak self] in
|
||||
self.didEndScrolling = { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
@ -1407,7 +1407,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
})
|
||||
|
||||
self.listNode.didEndScrolling = { [weak self] in
|
||||
self.listNode.didEndScrolling = { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
let _ = strongSelf.contentScrollingEnded?(strongSelf.listNode)
|
||||
}
|
||||
|
@ -178,7 +178,7 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr
|
||||
}
|
||||
}
|
||||
|
||||
self.contactsNode.listNode.didEndScrolling = { [weak self] in
|
||||
self.contactsNode.listNode.didEndScrolling = { [weak self] _ in
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||
let _ = fixNavigationSearchableListNodeScrolling(strongSelf.contactsNode.listNode, searchNode: searchContentNode)
|
||||
}
|
||||
|
@ -262,6 +262,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
public final var synchronousNodes = false
|
||||
public final var debugInfo = false
|
||||
|
||||
public final var useSingleDimensionTouchPoint = false
|
||||
|
||||
public var enableExtractedBackgrounds: Bool = false {
|
||||
didSet {
|
||||
if self.enableExtractedBackgrounds != oldValue {
|
||||
@ -292,8 +294,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
||||
public final var visibleBottomContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
||||
public final var beganInteractiveDragging: (CGPoint) -> Void = { _ in }
|
||||
public final var endedInteractiveDragging: () -> Void = { }
|
||||
public final var didEndScrolling: (() -> Void)?
|
||||
public final var endedInteractiveDragging: (CGPoint) -> Void = { _ in }
|
||||
public final var didEndScrolling: ((Bool) -> Void)?
|
||||
|
||||
private var currentGeneralScrollDirection: GeneralScrollDirection?
|
||||
public final var generalScrollDirectionUpdated: (GeneralScrollDirection) -> Void = { _ in }
|
||||
@ -710,9 +712,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
self.resetScrollIndicatorFlashTimer(start: true)
|
||||
|
||||
self.lastContentOffsetTimestamp = 0.0
|
||||
self.didEndScrolling?()
|
||||
self.didEndScrolling?(false)
|
||||
}
|
||||
self.endedInteractiveDragging()
|
||||
self.endedInteractiveDragging(self.touchesPosition)
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
@ -722,7 +724,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
self.updateHeaderItemsFlashing(animated: true)
|
||||
self.resetScrollIndicatorFlashTimer(start: true)
|
||||
if !scrollView.isTracking {
|
||||
self.didEndScrolling?()
|
||||
self.didEndScrolling?(true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3135,16 +3137,38 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
reverseAnimation = reverseBasicAnimation
|
||||
}
|
||||
}
|
||||
animation.completion = { _ in
|
||||
for itemNode in temporaryPreviousNodes {
|
||||
itemNode.removeFromSupernode()
|
||||
itemNode.extractedBackgroundNode?.removeFromSupernode()
|
||||
}
|
||||
for headerNode in temporaryHeaderNodes {
|
||||
headerNode.removeFromSupernode()
|
||||
|
||||
if scrollToItem.displayLink {
|
||||
self.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, -offset, 0.0)
|
||||
let offsetAnimation = ListViewAnimation(from: -offset, to: 0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem, beginAt: timestamp, update: { [weak self] progress, currentValue in
|
||||
if let strongSelf = self {
|
||||
strongSelf.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, currentValue, 0.0)
|
||||
|
||||
if progress == 1.0 {
|
||||
for itemNode in temporaryPreviousNodes {
|
||||
itemNode.removeFromSupernode()
|
||||
itemNode.extractedBackgroundNode?.removeFromSupernode()
|
||||
}
|
||||
for headerNode in temporaryHeaderNodes {
|
||||
headerNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
self.animations.append(offsetAnimation)
|
||||
} else {
|
||||
animation.completion = { _ in
|
||||
for itemNode in temporaryPreviousNodes {
|
||||
itemNode.removeFromSupernode()
|
||||
itemNode.extractedBackgroundNode?.removeFromSupernode()
|
||||
}
|
||||
for headerNode in temporaryHeaderNodes {
|
||||
headerNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
self.layer.add(animation, forKey: nil)
|
||||
}
|
||||
self.layer.add(animation, forKey: nil)
|
||||
|
||||
for itemNode in self.itemNodes {
|
||||
itemNode.applyAbsoluteOffset(value: CGPoint(x: 0.0, y: -offset), animationCurve: animationCurve, duration: animationDuration)
|
||||
}
|
||||
@ -3926,7 +3950,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
animation.applyAt(timestamp)
|
||||
|
||||
if animation.completeAt(timestamp) {
|
||||
animations.remove(at: i)
|
||||
self.animations.remove(at: i)
|
||||
animationCount -= 1
|
||||
i -= 1
|
||||
} else {
|
||||
@ -4148,6 +4172,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
}
|
||||
|
||||
public func itemIndexAtPoint(_ point: CGPoint) -> Int? {
|
||||
var point = point
|
||||
if self.useSingleDimensionTouchPoint {
|
||||
point.x = 0.0
|
||||
}
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.apparentContentFrame.contains(point) {
|
||||
return itemNode.index
|
||||
|
@ -31,13 +31,15 @@ public struct ListViewScrollToItem {
|
||||
public let animated: Bool
|
||||
public let curve: ListViewAnimationCurve
|
||||
public let directionHint: ListViewScrollToItemDirectionHint
|
||||
public let displayLink: Bool
|
||||
|
||||
public init(index: Int, position: ListViewScrollPosition, animated: Bool, curve: ListViewAnimationCurve, directionHint: ListViewScrollToItemDirectionHint) {
|
||||
public init(index: Int, position: ListViewScrollPosition, animated: Bool, curve: ListViewAnimationCurve, directionHint: ListViewScrollToItemDirectionHint, displayLink: Bool = false) {
|
||||
self.index = index
|
||||
self.position = position
|
||||
self.animated = animated
|
||||
self.curve = curve
|
||||
self.directionHint = directionHint
|
||||
self.displayLink = displayLink
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -384,7 +384,7 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
||||
animation.applyAt(timestamp)
|
||||
|
||||
if animation.completeAt(timestamp) {
|
||||
animations.remove(at: i)
|
||||
self.animations.remove(at: i)
|
||||
animationCount -= 1
|
||||
i -= 1
|
||||
} else {
|
||||
|
@ -258,13 +258,11 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
|
||||
transition.updateFrame(node: navigationBar, frame: CGRect(origin: CGPoint(x: 0.0, y: self.areControlsHidden ? -navigationBarHeight : 0.0), size: CGSize(width: layout.size.width, height: navigationBarHeight)))
|
||||
}
|
||||
|
||||
let displayThumbnailPanel = layout.size.width < layout.size.height
|
||||
var thumbnailPanelHeight: CGFloat = 0.0
|
||||
if let currentThumbnailContainerNode = self.currentThumbnailContainerNode {
|
||||
let panelHeight: CGFloat = 52.0
|
||||
if displayThumbnailPanel {
|
||||
thumbnailPanelHeight = 52.0
|
||||
}
|
||||
thumbnailPanelHeight = panelHeight
|
||||
|
||||
let thumbnailsFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - 40.0 - panelHeight + 4.0 - layout.intrinsicInsets.bottom + (self.areControlsHidden ? 106.0 : 0.0)), size: CGSize(width: layout.size.width, height: panelHeight - 4.0))
|
||||
transition.updateFrame(node: currentThumbnailContainerNode, frame: thumbnailsFrame)
|
||||
currentThumbnailContainerNode.updateLayout(size: thumbnailsFrame.size, transition: transition)
|
||||
|
@ -70,7 +70,10 @@ public final class GalleryFooterNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
let effectiveThumbnailPanelHeight = self.currentThumbnailPanelHeight ?? thumbnailPanelHeight
|
||||
var effectiveThumbnailPanelHeight = self.currentThumbnailPanelHeight ?? thumbnailPanelHeight
|
||||
if layout.size.width > layout.size.height {
|
||||
effectiveThumbnailPanelHeight = 0.0
|
||||
}
|
||||
var backgroundHeight: CGFloat = 0.0
|
||||
let verticalOffset: CGFloat = isHidden ? (layout.size.width > layout.size.height ? 44.0 : (effectiveThumbnailPanelHeight > 0.0 ? 106.0 : 54.0)) : 0.0
|
||||
if let footerContentNode = self.currentFooterContentNode {
|
||||
|
@ -121,7 +121,7 @@ public final class ItemListStickerPackItem: ListViewItem, ItemListItem {
|
||||
|
||||
public enum StickerPackThumbnailItem: Equatable {
|
||||
case still(TelegramMediaImageRepresentation)
|
||||
case animated(MediaResource)
|
||||
case animated(MediaResource, PixelDimensions)
|
||||
|
||||
public static func ==(lhs: StickerPackThumbnailItem, rhs: StickerPackThumbnailItem) -> Bool {
|
||||
switch lhs {
|
||||
@ -131,8 +131,8 @@ public enum StickerPackThumbnailItem: Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .animated(lhsResource):
|
||||
if case let .animated(rhsResource) = rhs, lhsResource.isEqual(to: rhsResource) {
|
||||
case let .animated(lhsResource, lhsDimensions):
|
||||
if case let .animated(rhsResource, rhsDimensions) = rhs, lhsResource.isEqual(to: rhsResource), lhsDimensions == rhsDimensions {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -439,7 +439,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
var resourceReference: MediaResourceReference?
|
||||
if let thumbnail = item.packInfo.thumbnail {
|
||||
if item.packInfo.flags.contains(.isAnimated) {
|
||||
thumbnailItem = .animated(thumbnail.resource)
|
||||
thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions)
|
||||
resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: item.packInfo.id.id, accessHash: item.packInfo.accessHash), resource: thumbnail.resource)
|
||||
} else {
|
||||
thumbnailItem = .still(thumbnail)
|
||||
@ -447,7 +447,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
} else if let item = item.topItem {
|
||||
if item.file.isAnimatedSticker {
|
||||
thumbnailItem = .animated(item.file.resource)
|
||||
thumbnailItem = .animated(item.file.resource, item.file.dimensions ?? PixelDimensions(width: 100, height: 100))
|
||||
resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource)
|
||||
} else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource {
|
||||
thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil))
|
||||
@ -474,7 +474,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: stillImageSize, boundingSize: stillImageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
updatedImageSignal = chatMessageStickerPackThumbnail(postbox: item.account.postbox, resource: representation.resource, nilIfEmpty: true)
|
||||
}
|
||||
case let .animated(resource):
|
||||
case let .animated(resource, _):
|
||||
imageSize = imageBoundingSize
|
||||
|
||||
if fileUpdated {
|
||||
@ -706,10 +706,12 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
let boundingSize = CGSize(width: 34.0, height: 34.0)
|
||||
if let thumbnailItem = thumbnailItem, let imageSize = imageSize {
|
||||
let imageFrame = CGRect(origin: CGPoint(x: params.leftInset + revealOffset + editingOffset + 15.0 + floor((boundingSize.width - imageSize.width) / 2.0), y: floor((layout.contentSize.height - imageSize.height) / 2.0)), size: imageSize)
|
||||
var thumbnailDimensions = PixelDimensions(width: 512, height: 512)
|
||||
switch thumbnailItem {
|
||||
case .still:
|
||||
case let .still(representation):
|
||||
transition.updateFrame(node: strongSelf.imageNode, frame: imageFrame)
|
||||
case let .animated(resource):
|
||||
thumbnailDimensions = representation.dimensions
|
||||
case let .animated(resource, _):
|
||||
transition.updateFrame(node: strongSelf.imageNode, frame: imageFrame)
|
||||
|
||||
let animationNode: AnimatedStickerNode
|
||||
@ -733,7 +735,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
|
||||
if let placeholderNode = strongSelf.placeholderNode {
|
||||
placeholderNode.frame = imageFrame
|
||||
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: item.presentationData.theme.list.disclosureArrowColor.blitOver(item.presentationData.theme.list.itemBlocksBackgroundColor, alpha: 0.55), shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.packInfo.immediateThumbnailData, size: imageFrame.size, imageSize: CGSize(width: 100.0, height: 100.0))
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: item.presentationData.theme.list.disclosureArrowColor.blitOver(item.presentationData.theme.list.itemBlocksBackgroundColor, alpha: 0.55), shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.packInfo.immediateThumbnailData, size: imageFrame.size, imageSize: thumbnailDimensions.cgSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,7 +352,7 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.listNode.didEndScrolling = { [weak self] in
|
||||
self.listNode.didEndScrolling = { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
let _ = strongSelf.contentScrollingEnded?(strongSelf.listNode)
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
- (void)setZoomedProgress:(CGFloat)progress;
|
||||
|
||||
- (void)saveStartImage:(void (^)(void))completion;
|
||||
- (TGCameraPreviewView *)previewView;
|
||||
|
||||
@end
|
||||
|
@ -61,6 +61,7 @@
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context camera:(bool)hasCamera selfPortrait:(bool)selfPortrait forProfilePhoto:(bool)forProfilePhoto assetType:(TGMediaAssetType)assetType saveEditedPhotos:(bool)saveEditedPhotos allowGrouping:(bool)allowGrouping allowSelection:(bool)allowSelection allowEditing:(bool)allowEditing document:(bool)document selectionLimit:(int)selectionLimit;
|
||||
|
||||
- (void)saveStartImage;
|
||||
- (UIView *)getItemSnapshot:(NSString *)uniqueId;
|
||||
|
||||
@end
|
||||
|
@ -77,5 +77,6 @@ typedef enum {
|
||||
+ (UIInterfaceOrientation)_interfaceOrientationForDeviceOrientation:(UIDeviceOrientation)orientation;
|
||||
|
||||
+ (UIImage *)startImage;
|
||||
+ (void)generateStartImageWithImage:(UIImage *)frameImage;
|
||||
|
||||
@end
|
||||
|
@ -247,4 +247,15 @@
|
||||
_iconView.frame = CGRectMake((self.frame.size.width - _iconView.frame.size.width) / 2, (self.frame.size.height - _iconView.frame.size.height) / 2, _iconView.frame.size.width, _iconView.frame.size.height);
|
||||
}
|
||||
|
||||
- (void)saveStartImage:(void (^)(void))completion {
|
||||
[_camera captureNextFrameCompletion:^(UIImage *frameImage) {
|
||||
[[SQueue concurrentDefaultQueue] dispatch:^{
|
||||
[TGCameraController generateStartImageWithImage:frameImage];
|
||||
TGDispatchOnMainThread(^{
|
||||
completion();
|
||||
});
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -107,6 +107,8 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
|
||||
bool _saveEditedPhotos;
|
||||
|
||||
TGMenuSheetPallete *_pallete;
|
||||
|
||||
bool _savingStartImage;
|
||||
}
|
||||
@end
|
||||
|
||||
@ -347,6 +349,15 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
|
||||
[_itemsSizeChangedDisposable dispose];
|
||||
}
|
||||
|
||||
- (void)saveStartImage {
|
||||
_savingStartImage = true;
|
||||
__weak TGAttachmentCameraView *weakCameraView = _cameraView;
|
||||
[_cameraView saveStartImage:^{
|
||||
__strong TGAttachmentCameraView *strongCameraView = weakCameraView;
|
||||
[strongCameraView stopPreview];
|
||||
}];
|
||||
}
|
||||
|
||||
- (UIView *)getItemSnapshot:(NSString *)uniqueId {
|
||||
for (UIView *cell in _collectionView.visibleCells) {
|
||||
if ([cell isKindOfClass:[TGAttachmentAssetCell class]]) {
|
||||
@ -1227,7 +1238,9 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
|
||||
{
|
||||
[super menuView:menuView didDisappearAnimated:animated];
|
||||
menuView.tapDismissalAllowed = nil;
|
||||
[_cameraView stopPreview];
|
||||
if (!_savingStartImage) {
|
||||
[_cameraView stopPreview];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
@ -182,7 +182,14 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, chatLocati
|
||||
carouselItem.stickersContext = paintStickersContext
|
||||
carouselItem.suggestionContext = legacySuggestionContext(context: context, peerId: peer.id, chatLocation: chatLocation)
|
||||
carouselItem.recipientName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
var openedCamera = false
|
||||
controller.willDismiss = { [weak carouselItem] _ in
|
||||
if let carouselItem = carouselItem, !openedCamera {
|
||||
carouselItem.saveStartImage()
|
||||
}
|
||||
}
|
||||
carouselItem.cameraPressed = { [weak controller, weak parentController] cameraView in
|
||||
openedCamera = true
|
||||
if let controller = controller {
|
||||
if let parentController = parentController, parentController.context.currentlyInSplitView() {
|
||||
return
|
||||
|
@ -110,7 +110,7 @@ public final class ChannelMembersSearchController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
self.controllerNode.listNode.didEndScrolling = { [weak self] in
|
||||
self.controllerNode.listNode.didEndScrolling = { [weak self] _ in
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||
let _ = fixNavigationSearchableListNodeScrolling(strongSelf.controllerNode.listNode, searchNode: searchContentNode)
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ public class LocalizationListController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
self.controllerNode.listNode.didEndScrolling = { [weak self] in
|
||||
self.controllerNode.listNode.didEndScrolling = { [weak self] _ in
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||
let _ = fixNavigationSearchableListNodeScrolling(strongSelf.controllerNode.listNode, searchNode: searchContentNode)
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ public class NotificationExceptionsController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
self.controllerNode.listNode.didEndScrolling = { [weak self] in
|
||||
self.controllerNode.listNode.didEndScrolling = { [weak self] _ in
|
||||
if let strongSelf = self, let searchContentNode = strongSelf.searchContentNode {
|
||||
let _ = fixNavigationSearchableListNodeScrolling(strongSelf.controllerNode.listNode, searchNode: searchContentNode)
|
||||
}
|
||||
|
@ -5,10 +5,12 @@ import Display
|
||||
|
||||
public final class SolidRoundedButtonTheme {
|
||||
public let backgroundColor: UIColor
|
||||
public let gradientBackgroundColor: UIColor?
|
||||
public let foregroundColor: UIColor
|
||||
|
||||
public init(backgroundColor: UIColor, foregroundColor: UIColor) {
|
||||
public init(backgroundColor: UIColor, gradientBackgroundColor: UIColor? = nil, foregroundColor: UIColor) {
|
||||
self.backgroundColor = backgroundColor
|
||||
self.gradientBackgroundColor = gradientBackgroundColor
|
||||
self.foregroundColor = foregroundColor
|
||||
}
|
||||
}
|
||||
@ -59,6 +61,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
|
||||
self.title = title
|
||||
|
||||
self.buttonBackgroundNode = ASDisplayNode()
|
||||
self.buttonBackgroundNode.clipsToBounds = true
|
||||
self.buttonBackgroundNode.backgroundColor = theme.backgroundColor
|
||||
self.buttonBackgroundNode.cornerRadius = cornerRadius
|
||||
if #available(iOS 13.0, *) {
|
||||
|
@ -1382,8 +1382,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
} else {
|
||||
var outgoingAudioBitrateKbit: Int32?
|
||||
let appConfiguration = self.accountContext.currentAppConfiguration.with({ $0 })
|
||||
if let data = appConfiguration.data, let value = data["voice_chat_send_bitrate"] as? Int32 {
|
||||
outgoingAudioBitrateKbit = value
|
||||
if let data = appConfiguration.data, let value = data["voice_chat_send_bitrate"] as? Double {
|
||||
outgoingAudioBitrateKbit = Int32(value)
|
||||
}
|
||||
|
||||
genericCallContext = OngoingGroupCallContext(video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in
|
||||
|
@ -1119,7 +1119,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.scheduleTextNode.textAlignment = .center
|
||||
self.scheduleTextNode.maximumNumberOfLines = 4
|
||||
|
||||
self.scheduleCancelButton = SolidRoundedButtonNode(title: self.presentationData.strings.Common_Cancel, theme: SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0x2b2b2f), foregroundColor: .white), height: 52.0, cornerRadius: 10.0)
|
||||
self.scheduleCancelButton = SolidRoundedButtonNode(title: self.presentationData.strings.Common_Cancel, theme: SolidRoundedButtonTheme(backgroundColor: UIColor(rgb: 0x2b2b2f), foregroundColor: .white), height: 52.0, cornerRadius: 10.0)
|
||||
self.scheduleCancelButton.isHidden = !self.isScheduling
|
||||
|
||||
self.dateFormatter = DateFormatter()
|
||||
@ -2410,7 +2410,6 @@ public final class VoiceChatController: ViewController {
|
||||
return []
|
||||
}
|
||||
|
||||
let presentationData = strongSelf.presentationData
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if peers.count > 1 {
|
||||
@ -2582,14 +2581,22 @@ public final class VoiceChatController: ViewController {
|
||||
return
|
||||
}
|
||||
|
||||
let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_StartRecordingTitle, text: presentationData.strings.VoiceChat_StartRecordingText, placeholder: presentationData.strings.VoiceChat_RecordingTitlePlaceholder, value: nil, maxLength: 40, apply: { title in
|
||||
if let strongSelf = self, let title = title {
|
||||
strongSelf.call.setShouldBeRecording(true, title: title)
|
||||
let controller = VoiceChatRecordingSetupController(context: strongSelf.context, completion: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.call.setShouldBeRecording(true, title: "")
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .voiceChatRecording(text: strongSelf.presentationData.strings.VoiceChat_RecordingStarted), action: { _ in return false })
|
||||
strongSelf.call.playTone(.recordingStarted)
|
||||
}
|
||||
})
|
||||
// let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_StartRecordingTitle, text: presentationData.strings.VoiceChat_StartRecordingText, placeholder: presentationData.strings.VoiceChat_RecordingTitlePlaceholder, value: nil, maxLength: 40, apply: { title in
|
||||
// if let strongSelf = self, let title = title {
|
||||
// strongSelf.call.setShouldBeRecording(true, title: title)
|
||||
//
|
||||
// strongSelf.presentUndoOverlay(content: .voiceChatRecording(text: strongSelf.presentationData.strings.VoiceChat_RecordingStarted), action: { _ in return false })
|
||||
// strongSelf.call.playTone(.recordingStarted)
|
||||
// }
|
||||
// })
|
||||
self?.controller?.present(controller, in: .window(.root))
|
||||
})))
|
||||
}
|
||||
|
@ -0,0 +1,597 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import SolidRoundedButtonNode
|
||||
import PresentationDataUtils
|
||||
|
||||
private let accentColor: UIColor = UIColor(rgb: 0x007aff)
|
||||
|
||||
final class VoiceChatRecordingSetupController: ViewController {
|
||||
private var controllerNode: VoiceChatRecordingSetupControllerNode {
|
||||
return self.displayNode as! VoiceChatRecordingSetupControllerNode
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let completion: () -> Void
|
||||
|
||||
private var animatedIn = false
|
||||
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, completion: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.completion = completion
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
|
||||
self.presentationDataDisposable = (context.sharedContext.presentationData
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerNode.updatePresentationData(presentationData)
|
||||
}
|
||||
})
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDataDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = VoiceChatRecordingSetupControllerNode(controller: self, context: self.context)
|
||||
self.controllerNode.completion = { [weak self] in
|
||||
self?.completion()
|
||||
}
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
self.controllerNode.cancel = { [weak self] in
|
||||
self?.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override public func loadView() {
|
||||
super.loadView()
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if !self.animatedIn {
|
||||
self.animatedIn = true
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.controllerNode.animateOut(completion: completion)
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private class VoiceChatRecordingSetupControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
enum MediaMode {
|
||||
case videoAndAudio
|
||||
case audioOnly
|
||||
}
|
||||
|
||||
enum VideoMode {
|
||||
case portrait
|
||||
case landscape
|
||||
}
|
||||
|
||||
private weak var controller: VoiceChatRecordingSetupController?
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private let dimNode: ASDisplayNode
|
||||
private let wrappingScrollNode: ASScrollNode
|
||||
private let contentContainerNode: ASDisplayNode
|
||||
private let effectNode: ASDisplayNode
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let contentBackgroundNode: ASDisplayNode
|
||||
private let titleNode: ASTextNode
|
||||
private let doneButton: VoiceChatActionButton
|
||||
private let cancelButton: SolidRoundedButtonNode
|
||||
private let modeContainerNode: ASDisplayNode
|
||||
|
||||
private let modeSeparatorNode: ASDisplayNode
|
||||
private let videoAudioButton: HighlightTrackingButtonNode
|
||||
private let videoAudioTitleNode: ImmediateTextNode
|
||||
private let videoAudioCheckNode: ASImageNode
|
||||
|
||||
private let audioButton: HighlightTrackingButtonNode
|
||||
private let audioTitleNode: ImmediateTextNode
|
||||
private let audioCheckNode: ASImageNode
|
||||
|
||||
private let portraitButton: HighlightTrackingButtonNode
|
||||
private let portraitIconNode: PreviewIconNode
|
||||
private let portraitTitleNode: ImmediateTextNode
|
||||
|
||||
private let landscapeButton: HighlightTrackingButtonNode
|
||||
private let landscapeIconNode: PreviewIconNode
|
||||
private let landscapeTitleNode: ImmediateTextNode
|
||||
|
||||
private let selectionNode: ASImageNode
|
||||
|
||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private let readyDisposable = MetaDisposable()
|
||||
|
||||
private var mediaMode: MediaMode = .videoAndAudio
|
||||
private var videoMode: VideoMode = .portrait
|
||||
|
||||
var completion: (() -> Void)?
|
||||
var dismiss: (() -> Void)?
|
||||
var cancel: (() -> Void)?
|
||||
|
||||
init(controller: VoiceChatRecordingSetupController, context: AccountContext) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.wrappingScrollNode = ASScrollNode()
|
||||
self.wrappingScrollNode.view.alwaysBounceVertical = true
|
||||
self.wrappingScrollNode.view.delaysContentTouches = false
|
||||
self.wrappingScrollNode.view.canCancelContentTouches = true
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
|
||||
self.contentContainerNode = ASDisplayNode()
|
||||
self.contentContainerNode.isOpaque = false
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.clipsToBounds = true
|
||||
self.backgroundNode.cornerRadius = 16.0
|
||||
|
||||
let backgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||
let textColor: UIColor = .white
|
||||
let buttonColor: UIColor = UIColor(rgb: 0x2b2b2f)
|
||||
let buttonTextColor: UIColor = .white
|
||||
let blurStyle: UIBlurEffect.Style = .dark
|
||||
|
||||
self.effectNode = ASDisplayNode(viewBlock: {
|
||||
return UIVisualEffectView(effect: UIBlurEffect(style: blurStyle))
|
||||
})
|
||||
|
||||
self.contentBackgroundNode = ASDisplayNode()
|
||||
self.contentBackgroundNode.backgroundColor = backgroundColor
|
||||
|
||||
let title = "Record Voice Chat"
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: textColor)
|
||||
|
||||
self.doneButton = VoiceChatActionButton()
|
||||
|
||||
self.cancelButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||
self.cancelButton.title = self.presentationData.strings.Common_Cancel
|
||||
|
||||
self.modeContainerNode = ASDisplayNode()
|
||||
self.modeContainerNode.clipsToBounds = true
|
||||
self.modeContainerNode.cornerRadius = 11.0
|
||||
self.modeContainerNode.backgroundColor = UIColor(rgb: 0x303032)
|
||||
|
||||
self.modeSeparatorNode = ASDisplayNode()
|
||||
self.modeSeparatorNode.backgroundColor = UIColor(rgb: 0x404041)
|
||||
|
||||
self.videoAudioButton = HighlightTrackingButtonNode()
|
||||
self.videoAudioTitleNode = ImmediateTextNode()
|
||||
self.videoAudioTitleNode.attributedText = NSAttributedString(string: "Video and Audio", font: Font.regular(17.0), textColor: .white, paragraphAlignment: .left)
|
||||
self.videoAudioCheckNode = ASImageNode()
|
||||
self.videoAudioCheckNode.displaysAsynchronously = false
|
||||
self.videoAudioCheckNode.image = UIImage(bundleImageName: "Call/Check")
|
||||
|
||||
self.audioButton = HighlightTrackingButtonNode()
|
||||
self.audioTitleNode = ImmediateTextNode()
|
||||
self.audioTitleNode.attributedText = NSAttributedString(string: "Only Audio", font: Font.regular(17.0), textColor: .white, paragraphAlignment: .left)
|
||||
self.audioCheckNode = ASImageNode()
|
||||
self.audioCheckNode.displaysAsynchronously = false
|
||||
self.audioCheckNode.image = UIImage(bundleImageName: "Call/Check")
|
||||
|
||||
self.portraitButton = HighlightTrackingButtonNode()
|
||||
self.portraitButton.backgroundColor = UIColor(rgb: 0x303032)
|
||||
self.portraitButton.cornerRadius = 11.0
|
||||
self.portraitIconNode = PreviewIconNode()
|
||||
self.portraitTitleNode = ImmediateTextNode()
|
||||
self.portraitTitleNode.attributedText = NSAttributedString(string: "Portrait", font: Font.semibold(15.0), textColor: UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
|
||||
|
||||
self.landscapeButton = HighlightTrackingButtonNode()
|
||||
self.landscapeButton.backgroundColor = UIColor(rgb: 0x303032)
|
||||
self.landscapeButton.cornerRadius = 11.0
|
||||
self.landscapeIconNode = PreviewIconNode()
|
||||
self.landscapeTitleNode = ImmediateTextNode()
|
||||
self.landscapeTitleNode.attributedText = NSAttributedString(string: "Landscape", font: Font.semibold(15.0), textColor: UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
|
||||
|
||||
self.selectionNode = ASImageNode()
|
||||
self.selectionNode.displaysAsynchronously = false
|
||||
self.selectionNode.image = generateImage(CGSize(width: 174.0, height: 140.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let lineWidth: CGFloat = 2.0
|
||||
|
||||
let path = UIBezierPath(roundedRect: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0), cornerRadius: 11.0)
|
||||
let cgPath = path.cgPath.copy(strokingWithWidth: lineWidth, lineCap: .round, lineJoin: .round, miterLimit: 10.0)
|
||||
context.addPath(cgPath)
|
||||
context.clip()
|
||||
|
||||
let colors: [CGColor] = [UIColor(rgb: 0x5064fd).cgColor, UIColor(rgb: 0xe76598).cgColor]
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: size.width, y: 0.0), options: CGGradientDrawingOptions())
|
||||
})
|
||||
self.selectionNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = nil
|
||||
self.isOpaque = false
|
||||
|
||||
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
self.addSubnode(self.dimNode)
|
||||
|
||||
self.wrappingScrollNode.view.delegate = self
|
||||
self.addSubnode(self.wrappingScrollNode)
|
||||
|
||||
self.wrappingScrollNode.addSubnode(self.backgroundNode)
|
||||
self.wrappingScrollNode.addSubnode(self.contentContainerNode)
|
||||
|
||||
self.backgroundNode.addSubnode(self.effectNode)
|
||||
self.backgroundNode.addSubnode(self.contentBackgroundNode)
|
||||
self.contentContainerNode.addSubnode(self.titleNode)
|
||||
self.contentContainerNode.addSubnode(self.doneButton)
|
||||
self.contentContainerNode.addSubnode(self.cancelButton)
|
||||
self.contentContainerNode.addSubnode(self.modeContainerNode)
|
||||
|
||||
self.contentContainerNode.addSubnode(self.videoAudioTitleNode)
|
||||
self.contentContainerNode.addSubnode(self.videoAudioCheckNode)
|
||||
self.contentContainerNode.addSubnode(self.videoAudioButton)
|
||||
|
||||
self.contentContainerNode.addSubnode(self.modeSeparatorNode)
|
||||
|
||||
self.contentContainerNode.addSubnode(self.audioTitleNode)
|
||||
self.contentContainerNode.addSubnode(self.audioCheckNode)
|
||||
self.contentContainerNode.addSubnode(self.audioButton)
|
||||
|
||||
self.contentContainerNode.addSubnode(self.portraitButton)
|
||||
self.contentContainerNode.addSubnode(self.portraitIconNode)
|
||||
self.contentContainerNode.addSubnode(self.portraitTitleNode)
|
||||
|
||||
self.contentContainerNode.addSubnode(self.landscapeButton)
|
||||
self.contentContainerNode.addSubnode(self.landscapeIconNode)
|
||||
self.contentContainerNode.addSubnode(self.landscapeTitleNode)
|
||||
|
||||
self.contentContainerNode.addSubnode(self.selectionNode)
|
||||
|
||||
self.videoAudioButton.addTarget(self, action: #selector(self.videoAudioPressed), forControlEvents: .touchUpInside)
|
||||
self.audioButton.addTarget(self, action: #selector(self.audioPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.portraitButton.addTarget(self, action: #selector(self.portraitPressed), forControlEvents: .touchUpInside)
|
||||
self.landscapeButton.addTarget(self, action: #selector(self.landscapePressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.doneButton.addTarget(self, action: #selector(self.donePressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.cancelButton.pressed = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.cancel?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func donePressed() {
|
||||
self.completion?()
|
||||
self.dismiss?()
|
||||
}
|
||||
|
||||
@objc private func videoAudioPressed() {
|
||||
self.mediaMode = .videoAndAudio
|
||||
|
||||
if let (layout, navigationHeight) = self.containerLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func audioPressed() {
|
||||
self.mediaMode = .audioOnly
|
||||
|
||||
if let (layout, navigationHeight) = self.containerLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func portraitPressed() {
|
||||
self.mediaMode = .videoAndAudio
|
||||
self.videoMode = .portrait
|
||||
|
||||
if let (layout, navigationHeight) = self.containerLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func landscapePressed() {
|
||||
self.mediaMode = .videoAndAudio
|
||||
self.videoMode = .landscape
|
||||
|
||||
if let (layout, navigationHeight) = self.containerLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
func updatePresentationData(_ presentationData: PresentationData) {
|
||||
self.presentationData = presentationData
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
}
|
||||
|
||||
@objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.cancel?()
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
|
||||
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||
let dimPosition = self.dimNode.layer.position
|
||||
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
let targetBounds = self.bounds
|
||||
self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset)
|
||||
self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset)
|
||||
transition.animateView({
|
||||
self.bounds = targetBounds
|
||||
self.dimNode.position = dimPosition
|
||||
})
|
||||
}
|
||||
|
||||
func animateOut(completion: (() -> Void)? = nil) {
|
||||
var dimCompleted = false
|
||||
var offsetCompleted = false
|
||||
|
||||
let internalCompletion: () -> Void = { [weak self] in
|
||||
if let strongSelf = self, dimCompleted && offsetCompleted {
|
||||
strongSelf.dismiss?()
|
||||
}
|
||||
completion?()
|
||||
}
|
||||
|
||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
dimCompleted = true
|
||||
internalCompletion()
|
||||
})
|
||||
|
||||
let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY
|
||||
let dimPosition = self.dimNode.layer.position
|
||||
self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
|
||||
offsetCompleted = true
|
||||
internalCompletion()
|
||||
})
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.bounds.contains(point) {
|
||||
if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) {
|
||||
return self.dimNode.view
|
||||
}
|
||||
}
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
let contentOffset = scrollView.contentOffset
|
||||
let additionalTopHeight = max(0.0, -contentOffset.y)
|
||||
|
||||
if additionalTopHeight >= 30.0 {
|
||||
self.cancel?()
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (layout, navigationBarHeight)
|
||||
|
||||
let isLandscape: Bool
|
||||
if layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass {
|
||||
isLandscape = true
|
||||
} else {
|
||||
isLandscape = false
|
||||
}
|
||||
|
||||
|
||||
var insets = layout.insets(options: [.statusBar, .input])
|
||||
let cleanInsets = layout.insets(options: [.statusBar])
|
||||
insets.top = max(10.0, insets.top)
|
||||
|
||||
let buttonOffset: CGFloat = 60.0
|
||||
|
||||
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||
let titleHeight: CGFloat = 54.0
|
||||
var contentHeight = titleHeight + bottomInset + 52.0 + 17.0
|
||||
let innerContentHeight: CGFloat = 287.0
|
||||
var width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
|
||||
if isLandscape {
|
||||
contentHeight = layout.size.height
|
||||
width = layout.size.width
|
||||
} else {
|
||||
contentHeight = titleHeight + bottomInset + 52.0 + 17.0 + innerContentHeight + buttonOffset
|
||||
}
|
||||
|
||||
let inset: CGFloat = 16.0
|
||||
let sideInset = floor((layout.size.width - width) / 2.0)
|
||||
let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight))
|
||||
let contentFrame = contentContainerFrame
|
||||
|
||||
var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0))
|
||||
if backgroundFrame.minY < contentFrame.minY {
|
||||
backgroundFrame.origin.y = contentFrame.minY
|
||||
}
|
||||
transition.updateAlpha(node: self.titleNode, alpha: isLandscape ? 0.0 : 1.0)
|
||||
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||
transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||
transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight))
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 18.0), size: titleSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
||||
|
||||
let itemHeight: CGFloat = 44.0
|
||||
|
||||
transition.updateFrame(node: self.modeContainerNode, frame: CGRect(x: inset, y: 56.0, width: contentFrame.width - inset * 2.0, height: itemHeight * 2.0))
|
||||
|
||||
transition.updateFrame(node: self.videoAudioButton, frame: CGRect(x: inset, y: 56.0, width: contentFrame.width - inset * 2.0, height: itemHeight))
|
||||
transition.updateFrame(node: self.videoAudioCheckNode, frame: CGRect(x: contentFrame.width - inset - 16.0 - 20.0, y: 56.0 + floorToScreenPixels((itemHeight - 16.0) / 2.0), width: 16.0, height: 16.0))
|
||||
self.videoAudioCheckNode.isHidden = self.mediaMode != .videoAndAudio
|
||||
|
||||
let videoAudioSize = self.videoAudioTitleNode.updateLayout(CGSize(width: contentFrame.width - inset * 2.0, height: itemHeight))
|
||||
transition.updateFrame(node: self.videoAudioTitleNode, frame: CGRect(x: inset + 16.0, y: 56.0 + floorToScreenPixels((itemHeight - videoAudioSize.height) / 2.0), width: videoAudioSize.width, height: videoAudioSize.height))
|
||||
|
||||
transition.updateFrame(node: self.audioButton, frame: CGRect(x: inset, y: 56.0 + itemHeight, width: contentFrame.width - inset * 2.0, height: itemHeight))
|
||||
transition.updateFrame(node: self.audioCheckNode, frame: CGRect(x: contentFrame.width - inset - 16.0 - 20.0, y: 56.0 + itemHeight + floorToScreenPixels((itemHeight - 16.0) / 2.0), width: 16.0, height: 16.0))
|
||||
self.audioCheckNode.isHidden = self.mediaMode != .audioOnly
|
||||
|
||||
let audioSize = self.audioTitleNode.updateLayout(CGSize(width: contentFrame.width - inset * 2.0, height: itemHeight))
|
||||
transition.updateFrame(node: self.audioTitleNode, frame: CGRect(x: inset + 16.0, y: 56.0 + itemHeight + floorToScreenPixels((itemHeight - audioSize.height) / 2.0), width: audioSize.width, height: audioSize.height))
|
||||
|
||||
transition.updateFrame(node: self.modeSeparatorNode, frame: CGRect(x: inset + 16.0, y: 56.0 + itemHeight, width: contentFrame.width - inset * 2.0 - 16.0, height: UIScreenPixel))
|
||||
|
||||
var buttonsAlpha: CGFloat = 1.0
|
||||
if case .audioOnly = self.mediaMode {
|
||||
buttonsAlpha = 0.3
|
||||
}
|
||||
|
||||
transition.updateAlpha(node: self.portraitButton, alpha: buttonsAlpha)
|
||||
transition.updateAlpha(node: self.portraitIconNode, alpha: buttonsAlpha)
|
||||
transition.updateAlpha(node: self.portraitTitleNode, alpha: buttonsAlpha)
|
||||
|
||||
transition.updateAlpha(node: self.landscapeButton, alpha: buttonsAlpha)
|
||||
transition.updateAlpha(node: self.landscapeIconNode, alpha: buttonsAlpha)
|
||||
transition.updateAlpha(node: self.landscapeTitleNode, alpha: buttonsAlpha)
|
||||
|
||||
transition.updateAlpha(node: self.selectionNode, alpha: buttonsAlpha)
|
||||
|
||||
self.portraitTitleNode.attributedText = NSAttributedString(string: "Portrait", font: Font.semibold(15.0), textColor: self.videoMode == .portrait ? UIColor(rgb: 0xb56df4) : UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
|
||||
self.landscapeTitleNode.attributedText = NSAttributedString(string: "Landscape", font: Font.semibold(15.0), textColor: self.videoMode == .landscape ? UIColor(rgb: 0xb56df4) : UIColor(rgb: 0x8e8e93), paragraphAlignment: .left)
|
||||
|
||||
let buttonWidth = floorToScreenPixels((contentFrame.width - inset * 2.0 - 11.0) / 2.0)
|
||||
let portraitButtonFrame = CGRect(x: inset, y: 56.0 + itemHeight * 2.0 + 25.0, width: buttonWidth, height: 140.0)
|
||||
transition.updateFrame(node: self.portraitButton, frame: portraitButtonFrame)
|
||||
transition.updateFrame(node: self.portraitIconNode, frame: CGRect(x: portraitButtonFrame.minX + floorToScreenPixels((portraitButtonFrame.width - 72.0) / 2.0), y: portraitButtonFrame.minY + floorToScreenPixels((portraitButtonFrame.height - 122.0) / 2.0), width: 76.0, height: 122.0))
|
||||
self.portraitIconNode.updateLayout(landscape: false)
|
||||
let portraitSize = self.portraitTitleNode.updateLayout(CGSize(width: buttonWidth, height: 30.0))
|
||||
transition.updateFrame(node: self.portraitTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(portraitButtonFrame.center.x - portraitSize.width / 2.0), y: portraitButtonFrame.maxY + 7.0), size: portraitSize))
|
||||
|
||||
let landscapeButtonFrame = CGRect(x: portraitButtonFrame.maxX + 11.0, y: portraitButtonFrame.minY, width: portraitButtonFrame.width, height: portraitButtonFrame.height)
|
||||
transition.updateFrame(node: self.landscapeButton, frame: landscapeButtonFrame)
|
||||
transition.updateFrame(node: self.landscapeIconNode, frame: CGRect(x: landscapeButtonFrame.minX + floorToScreenPixels((landscapeButtonFrame.width - 122.0) / 2.0), y: landscapeButtonFrame.minY + floorToScreenPixels((landscapeButtonFrame.height - 76.0) / 2.0), width: 122.0, height: 76.0))
|
||||
self.landscapeIconNode.updateLayout(landscape: true)
|
||||
let landscapeSize = self.landscapeTitleNode.updateLayout(CGSize(width: buttonWidth, height: 30.0))
|
||||
transition.updateFrame(node: self.landscapeTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(landscapeButtonFrame.center.x - landscapeSize.width / 2.0), y: landscapeButtonFrame.maxY + 7.0), size: landscapeSize))
|
||||
|
||||
let centralButtonSide = min(contentFrame.width, layout.size.height) - 32.0
|
||||
let centralButtonSize = CGSize(width: centralButtonSide, height: centralButtonSide)
|
||||
|
||||
let buttonInset: CGFloat = 16.0
|
||||
let doneButtonPreFrame = CGRect(x: buttonInset, y: contentHeight - 50.0 - insets.bottom - 16.0 - buttonOffset, width: contentFrame.width - buttonInset * 2.0, height: 50.0)
|
||||
let doneButtonFrame = CGRect(origin: CGPoint(x: floor(doneButtonPreFrame.midX - centralButtonSize.width / 2.0), y: floor(doneButtonPreFrame.midY - centralButtonSize.height / 2.0)), size: centralButtonSize)
|
||||
transition.updateFrame(node: self.doneButton, frame: doneButtonFrame)
|
||||
|
||||
if self.videoMode == .portrait {
|
||||
self.selectionNode.frame = portraitButtonFrame.insetBy(dx: -1.0, dy: -1.0)
|
||||
} else {
|
||||
self.selectionNode.frame = landscapeButtonFrame.insetBy(dx: -1.0, dy: -1.0)
|
||||
}
|
||||
|
||||
self.doneButton.update(size: centralButtonSize, buttonSize: CGSize(width: 112.0, height: 112.0), state: .button(text: "Start Recording"), title: "", subtitle: "", dark: false, small: false)
|
||||
|
||||
let cancelButtonHeight = self.cancelButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
|
||||
transition.updateFrame(node: self.cancelButton, frame: CGRect(x: buttonInset, y: contentHeight - cancelButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: cancelButtonHeight))
|
||||
|
||||
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
|
||||
}
|
||||
}
|
||||
|
||||
private class PreviewIconNode: ASDisplayNode {
|
||||
private let avatar1Node: ASImageNode
|
||||
private let avatar2Node: ASImageNode
|
||||
private let avatar3Node: ASImageNode
|
||||
private let avatar4Node: ASImageNode
|
||||
|
||||
override init() {
|
||||
self.avatar1Node = ASImageNode()
|
||||
self.avatar1Node.cornerRadius = 4.0
|
||||
self.avatar1Node.displaysAsynchronously = false
|
||||
self.avatar1Node.backgroundColor = UIColor(rgb: 0x834fff)
|
||||
|
||||
self.avatar2Node = ASImageNode()
|
||||
self.avatar2Node.cornerRadius = 4.0
|
||||
self.avatar2Node.displaysAsynchronously = false
|
||||
self.avatar2Node.backgroundColor = UIColor(rgb: 0x63d5c9)
|
||||
|
||||
self.avatar3Node = ASImageNode()
|
||||
self.avatar3Node.cornerRadius = 4.0
|
||||
self.avatar3Node.displaysAsynchronously = false
|
||||
self.avatar3Node.backgroundColor = UIColor(rgb: 0xccff60)
|
||||
|
||||
self.avatar4Node = ASImageNode()
|
||||
self.avatar4Node.cornerRadius = 4.0
|
||||
self.avatar4Node.displaysAsynchronously = false
|
||||
self.avatar4Node.backgroundColor = UIColor(rgb: 0xf5512a)
|
||||
|
||||
super.init()
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.avatar1Node)
|
||||
self.addSubnode(self.avatar2Node)
|
||||
self.addSubnode(self.avatar3Node)
|
||||
self.addSubnode(self.avatar4Node)
|
||||
}
|
||||
|
||||
func updateLayout(landscape: Bool) {
|
||||
if landscape {
|
||||
self.avatar1Node.frame = CGRect(x: 0.0, y: 0.0, width: 96.0, height: 76.0)
|
||||
self.avatar2Node.frame = CGRect(x: 98.0, y: 0.0, width: 24.0, height: 24.0)
|
||||
self.avatar3Node.frame = CGRect(x: 98.0, y: 26.0, width: 24.0, height: 24.0)
|
||||
self.avatar4Node.frame = CGRect(x: 98.0, y: 52.0, width: 24.0, height: 24.0)
|
||||
} else {
|
||||
self.avatar1Node.frame = CGRect(x: 0.0, y: 0.0, width: 76.0, height: 96.0)
|
||||
self.avatar2Node.frame = CGRect(x: 0.0, y: 98.0, width: 24.0, height: 24.0)
|
||||
self.avatar3Node.frame = CGRect(x: 26.0, y: 98.0, width: 24.0, height: 24.0)
|
||||
self.avatar4Node.frame = CGRect(x: 52.0, y: 98.0, width: 24.0, height: 24.0)
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ import Postbox
|
||||
import TelegramApi
|
||||
import SwiftSignalKit
|
||||
|
||||
|
||||
private struct SearchStickersConfiguration {
|
||||
static var defaultValue: SearchStickersConfiguration {
|
||||
return SearchStickersConfiguration(cacheTimeout: 86400)
|
||||
@ -16,8 +15,8 @@ private struct SearchStickersConfiguration {
|
||||
}
|
||||
|
||||
static func with(appConfiguration: AppConfiguration) -> SearchStickersConfiguration {
|
||||
if let data = appConfiguration.data, let value = data["stickers_emoji_cache_time"] as? Int32 {
|
||||
return SearchStickersConfiguration(cacheTimeout: value)
|
||||
if let data = appConfiguration.data, let value = data["stickers_emoji_cache_time"] as? Double {
|
||||
return SearchStickersConfiguration(cacheTimeout: Int32(value))
|
||||
} else {
|
||||
return .defaultValue
|
||||
}
|
||||
|
@ -103,6 +103,33 @@ public final class ApplicationSpecificTimestampNotice: NoticeEntry {
|
||||
}
|
||||
}
|
||||
|
||||
public final class ApplicationSpecificInt64ArrayNotice: NoticeEntry {
|
||||
public let values: [Int64]
|
||||
|
||||
public init(values: [Int64]) {
|
||||
self.values = values
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.values = decoder.decodeInt64ArrayForKey("v")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64Array(self.values, forKey: "v")
|
||||
}
|
||||
|
||||
public func isEqual(to: NoticeEntry) -> Bool {
|
||||
if let to = to as? ApplicationSpecificInt64ArrayNotice {
|
||||
if self.values != to.values {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func noticeNamespace(namespace: Int32) -> ValueBoxKey {
|
||||
let key = ValueBoxKey(length: 4)
|
||||
key.setInt32(0, value: namespace)
|
||||
@ -138,6 +165,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
||||
case chatFolderTips = 19
|
||||
case locationProximityAlertTip = 20
|
||||
case nextChatSuggestionTip = 21
|
||||
case dismissedTrendingStickerPacks = 22
|
||||
|
||||
var key: ValueBoxKey {
|
||||
let v = ValueBoxKey(length: 4)
|
||||
@ -273,6 +301,10 @@ private struct ApplicationSpecificNoticeKeys {
|
||||
static func nextChatSuggestionTip() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.nextChatSuggestionTip.key)
|
||||
}
|
||||
|
||||
static func dismissedTrendingStickerPacks() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedTrendingStickerPacks.key)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificNotice {
|
||||
@ -763,6 +795,23 @@ public struct ApplicationSpecificNotice {
|
||||
}
|
||||
}
|
||||
|
||||
public static func dismissedTrendingStickerPacks(accountManager: AccountManager) -> Signal<[Int64]?, NoError> {
|
||||
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedTrendingStickerPacks())
|
||||
|> map { view -> [Int64]? in
|
||||
if let value = view.value as? ApplicationSpecificInt64ArrayNotice {
|
||||
return value.values
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func setDismissedTrendingStickerPacks(accountManager: AccountManager, values: [Int64]) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedTrendingStickerPacks(), ApplicationSpecificInt64ArrayNotice(values: values))
|
||||
}
|
||||
}
|
||||
|
||||
public static func reset(accountManager: AccountManager) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
}
|
||||
|
21
submodules/TelegramUI/Images.xcassets/Call/Check.imageset/Contents.json
vendored
Normal file
21
submodules/TelegramUI/Images.xcassets/Call/Check.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "RecordCheck.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Call/Check.imageset/RecordCheck.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Call/Check.imageset/RecordCheck.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 973 B |
@ -1103,8 +1103,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
return interfaceState
|
||||
}.updatedInputMode { current in
|
||||
if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil {
|
||||
return .media(mode: mode, expanded: nil)
|
||||
if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
|
||||
return .media(mode: mode, expanded: nil, focused: focused)
|
||||
}
|
||||
return current
|
||||
}
|
||||
@ -1123,8 +1123,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, { current in
|
||||
var current = current
|
||||
current = current.updatedInputMode { current in
|
||||
if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil {
|
||||
return .media(mode: mode, expanded: nil)
|
||||
if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
|
||||
return .media(mode: mode, expanded: nil, focused: focused)
|
||||
}
|
||||
return current
|
||||
}
|
||||
@ -1167,8 +1167,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
|
||||
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }.updatedInputMode { current in
|
||||
if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil {
|
||||
return .media(mode: mode, expanded: nil)
|
||||
if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
|
||||
return .media(mode: mode, expanded: nil, focused: focused)
|
||||
}
|
||||
return current
|
||||
}
|
||||
@ -7195,7 +7195,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return false
|
||||
}
|
||||
|
||||
if case let .media(_, expanded) = strongSelf.presentationInterfaceState.inputMode, expanded != nil {
|
||||
if case let .media(_, expanded, _) = strongSelf.presentationInterfaceState.inputMode, expanded != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -10033,8 +10033,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return interfaceState
|
||||
}
|
||||
state = state.updatedInputMode { current in
|
||||
if case let .media(mode, maybeExpanded) = current, maybeExpanded != nil {
|
||||
return .media(mode: mode, expanded: nil)
|
||||
if case let .media(mode, maybeExpanded, focused) = current, maybeExpanded != nil {
|
||||
return .media(mode: mode, expanded: nil, focused: focused)
|
||||
}
|
||||
return current
|
||||
}
|
||||
@ -12477,7 +12477,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in
|
||||
return state.updatedInterfaceState { interfaceState in
|
||||
return interfaceState.withUpdatedEffectiveInputState(interfaceState.effectiveInputState)
|
||||
}.updatedInputMode({ _ in ChatInputMode.media(mode: .other, expanded: nil) })
|
||||
}.updatedInputMode({ _ in ChatInputMode.media(mode: .other, expanded: nil, focused: false) })
|
||||
})
|
||||
}
|
||||
}),
|
||||
|
@ -820,7 +820,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
} else {
|
||||
insets = layout.insets(options: [.input])
|
||||
}
|
||||
|
||||
|
||||
if case .overlay = self.chatPresentationInterfaceState.mode {
|
||||
insets.top = 44.0
|
||||
} else {
|
||||
@ -1152,7 +1152,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
var displayTopDimNode = false
|
||||
let ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil
|
||||
var expandTopDimNode = false
|
||||
if case let .media(_, expanded) = self.chatPresentationInterfaceState.inputMode, expanded != nil {
|
||||
if case let .media(_, expanded, _) = self.chatPresentationInterfaceState.inputMode, expanded != nil {
|
||||
displayTopDimNode = true
|
||||
expandTopDimNode = true
|
||||
}
|
||||
@ -1230,7 +1230,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let apparentSecondaryInputPanelFrame = secondaryInputPanelFrame
|
||||
var apparentInputBackgroundFrame = inputBackgroundFrame
|
||||
var apparentNavigateButtonsFrame = navigateButtonsFrame
|
||||
if case let .media(_, maybeExpanded) = self.chatPresentationInterfaceState.inputMode, let expanded = maybeExpanded, case .search = expanded, let inputPanelFrame = inputPanelFrame {
|
||||
if case let .media(_, maybeExpanded, _) = self.chatPresentationInterfaceState.inputMode, let expanded = maybeExpanded, case .search = expanded, let inputPanelFrame = inputPanelFrame {
|
||||
let verticalOffset = -inputPanelFrame.height - 41.0
|
||||
apparentInputPanelFrame = inputPanelFrame.offsetBy(dx: 0.0, dy: verticalOffset)
|
||||
apparentInputBackgroundFrame.size.height -= verticalOffset
|
||||
@ -1854,11 +1854,11 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let inputNode = ChatMediaInputNode(context: self.context, peerId: peerId, chatLocation: self.chatPresentationInterfaceState.chatLocation, controllerInteraction: self.controllerInteraction, chatWallpaper: self.chatPresentationInterfaceState.chatWallpaper, theme: theme, strings: strings, fontSize: fontSize, gifPaneIsActiveUpdated: { [weak self] value in
|
||||
if let strongSelf = self, let interfaceInteraction = strongSelf.interfaceInteraction {
|
||||
interfaceInteraction.updateInputModeAndDismissedButtonKeyboardMessageId { state in
|
||||
if case let .media(_, expanded) = state.inputMode {
|
||||
if case let .media(_, expanded, focused) = state.inputMode {
|
||||
if value {
|
||||
return (.media(mode: .gif, expanded: expanded), nil)
|
||||
return (.media(mode: .gif, expanded: expanded, focused: focused), nil)
|
||||
} else {
|
||||
return (.media(mode: .other, expanded: expanded), nil)
|
||||
return (.media(mode: .other, expanded: expanded, focused: focused), nil)
|
||||
}
|
||||
} else {
|
||||
return (state.inputMode, nil)
|
||||
@ -2112,8 +2112,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
@objc func topDimNodeTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId { state in
|
||||
if case let .media(mode, expanded) = state.inputMode, expanded != nil {
|
||||
return (.media(mode: mode, expanded: nil), nil)
|
||||
if case let .media(mode, expanded, focused) = state.inputMode, expanded != nil {
|
||||
return (.media(mode: mode, expanded: nil, focused: focused), nil)
|
||||
} else {
|
||||
return (state.inputMode, nil)
|
||||
}
|
||||
@ -2122,10 +2122,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
func scrollToTop() {
|
||||
if case let .media(_, maybeExpanded) = self.chatPresentationInterfaceState.inputMode, maybeExpanded != nil {
|
||||
if case let .media(_, maybeExpanded, _) = self.chatPresentationInterfaceState.inputMode, maybeExpanded != nil {
|
||||
self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId { state in
|
||||
if case let .media(mode, expanded) = state.inputMode, expanded != nil {
|
||||
return (.media(mode: mode, expanded: expanded), nil)
|
||||
if case let .media(mode, expanded, focused) = state.inputMode, expanded != nil {
|
||||
return (.media(mode: mode, expanded: expanded, focused: focused), nil)
|
||||
} else {
|
||||
return (state.inputMode, nil)
|
||||
}
|
||||
@ -2259,7 +2259,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|> deliverOnMainQueue).start(next: { [weak self] in
|
||||
self?.openStickersDisposable = nil
|
||||
self?.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in
|
||||
return (.media(mode: .other, expanded: nil), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId)
|
||||
return (.media(mode: .other, expanded: nil, focused: false), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -175,6 +175,10 @@ final class ChatEmptyNodeGreetingChatContent: ASDisplayNode, ChatEmptyNodeSticke
|
||||
},
|
||||
openSettings: {
|
||||
},
|
||||
openTrending: { _ in
|
||||
},
|
||||
dismissTrendingPacks: { _ in
|
||||
},
|
||||
toggleSearch: { _, _, _ in
|
||||
},
|
||||
openPeerSpecificSettings: {
|
||||
@ -342,6 +346,10 @@ final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNodeStickerC
|
||||
},
|
||||
openSettings: {
|
||||
},
|
||||
openTrending: { _ in
|
||||
},
|
||||
dismissTrendingPacks: { _ in
|
||||
},
|
||||
toggleSearch: { _, _, _ in
|
||||
},
|
||||
openPeerSpecificSettings: {
|
||||
|
@ -1167,7 +1167,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
self?.beganDragging?()
|
||||
}
|
||||
|
||||
self.endedInteractiveDragging = { [weak self] in
|
||||
self.endedInteractiveDragging = { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1177,7 +1177,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.didEndScrolling = { [weak self] in
|
||||
self.didEndScrolling = { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
@ -311,9 +311,6 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
stickersEnabled = false
|
||||
}
|
||||
}
|
||||
// if let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, let _ = peer.botInfo {
|
||||
// accessoryItems.append(.commands)
|
||||
// } else
|
||||
if chatPresentationInterfaceState.hasBots {
|
||||
accessoryItems.append(.commands)
|
||||
}
|
||||
|
@ -23,11 +23,11 @@ func inputNodeForChatPresentationIntefaceState(_ chatPresentationInterfaceState:
|
||||
let inputNode = ChatMediaInputNode(context: context, peerId: peerId, chatLocation: chatPresentationInterfaceState.chatLocation, controllerInteraction: controllerInteraction, chatWallpaper: chatPresentationInterfaceState.chatWallpaper, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, fontSize: chatPresentationInterfaceState.fontSize, gifPaneIsActiveUpdated: { [weak interfaceInteraction] value in
|
||||
if let interfaceInteraction = interfaceInteraction {
|
||||
interfaceInteraction.updateInputModeAndDismissedButtonKeyboardMessageId { state in
|
||||
if case let .media(_, expanded) = state.inputMode {
|
||||
if case let .media(_, expanded, focused) = state.inputMode {
|
||||
if value {
|
||||
return (.media(mode: .gif, expanded: expanded), nil)
|
||||
return (.media(mode: .gif, expanded: expanded, focused: focused), nil)
|
||||
} else {
|
||||
return (.media(mode: .other, expanded: expanded), nil)
|
||||
return (.media(mode: .other, expanded: expanded, focused: focused), nil)
|
||||
}
|
||||
} else {
|
||||
return (state.inputMode, nil)
|
||||
|
@ -8,6 +8,7 @@ import MergeLists
|
||||
|
||||
enum ChatMediaInputGridEntryStableId: Equatable, Hashable {
|
||||
case search
|
||||
case trendingList
|
||||
case peerSpecificSetup
|
||||
case sticker(ItemCollectionId, ItemCollectionItemIndex.Id)
|
||||
case trending(ItemCollectionId)
|
||||
@ -15,6 +16,7 @@ enum ChatMediaInputGridEntryStableId: Equatable, Hashable {
|
||||
|
||||
enum ChatMediaInputGridEntryIndex: Equatable, Comparable {
|
||||
case search
|
||||
case trendingList
|
||||
case peerSpecificSetup(dismissed: Bool)
|
||||
case collectionIndex(ItemCollectionViewEntryIndex)
|
||||
case trending(ItemCollectionId, Int)
|
||||
@ -23,6 +25,8 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable {
|
||||
switch self {
|
||||
case .search:
|
||||
return .search
|
||||
case .trendingList:
|
||||
return .trendingList
|
||||
case .peerSpecificSetup:
|
||||
return .peerSpecificSetup
|
||||
case let .collectionIndex(index):
|
||||
@ -40,9 +44,16 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable {
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
case .trendingList:
|
||||
switch rhs {
|
||||
case .search, .trendingList:
|
||||
return false
|
||||
case .peerSpecificSetup, .collectionIndex, .trending:
|
||||
return true
|
||||
}
|
||||
case let .peerSpecificSetup(lhsDismissed):
|
||||
switch rhs {
|
||||
case .search, .peerSpecificSetup:
|
||||
case .search, .trendingList, .peerSpecificSetup:
|
||||
return false
|
||||
case let .collectionIndex(index):
|
||||
if lhsDismissed {
|
||||
@ -59,7 +70,7 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable {
|
||||
}
|
||||
case let .collectionIndex(lhsIndex):
|
||||
switch rhs {
|
||||
case .search:
|
||||
case .search, .trendingList:
|
||||
return false
|
||||
case let .peerSpecificSetup(dismissed):
|
||||
if dismissed {
|
||||
@ -74,7 +85,7 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable {
|
||||
}
|
||||
case let .trending(_, lhsIndex):
|
||||
switch rhs {
|
||||
case .search, .peerSpecificSetup, .collectionIndex:
|
||||
case .search, .trendingList, .peerSpecificSetup, .collectionIndex:
|
||||
return false
|
||||
case let .trending(_, rhsIndex):
|
||||
return lhsIndex < rhsIndex
|
||||
@ -85,6 +96,7 @@ enum ChatMediaInputGridEntryIndex: Equatable, Comparable {
|
||||
|
||||
enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable {
|
||||
case search(theme: PresentationTheme, strings: PresentationStrings)
|
||||
case trendingList(theme: PresentationTheme, strings: PresentationStrings, packs: [FeaturedStickerPackItem])
|
||||
case peerSpecificSetup(theme: PresentationTheme, strings: PresentationStrings, dismissed: Bool)
|
||||
case sticker(index: ItemCollectionViewEntryIndex, stickerItem: StickerPackItem, stickerPackInfo: StickerPackCollectionInfo?, canManagePeerSpecificPack: Bool?, maybeManageable: Bool, theme: PresentationTheme)
|
||||
case trending(TrendingPanePackEntry)
|
||||
@ -93,6 +105,8 @@ enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable {
|
||||
switch self {
|
||||
case .search:
|
||||
return .search
|
||||
case .trendingList:
|
||||
return .trendingList
|
||||
case let .peerSpecificSetup(_, _, dismissed):
|
||||
return .peerSpecificSetup(dismissed: dismissed)
|
||||
case let .sticker(index, _, _, _, _, _):
|
||||
@ -120,6 +134,26 @@ enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .trendingList(lhsTheme, lhsStrings, lhsPacks):
|
||||
if case let .trendingList(rhsTheme, rhsStrings, rhsPacks) = rhs {
|
||||
if lhsTheme !== rhsTheme {
|
||||
return false
|
||||
}
|
||||
if lhsStrings !== rhsStrings {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhsPacks.count {
|
||||
if lhsPacks[i].unread != rhsPacks[i].unread {
|
||||
return false
|
||||
}
|
||||
if lhsPacks[i].info != rhsPacks[i].info {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerSpecificSetup(lhsTheme, lhsStrings, lhsDismissed):
|
||||
if case let .peerSpecificSetup(rhsTheme, rhsStrings, rhsDismissed) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDismissed == rhsDismissed {
|
||||
return true
|
||||
@ -169,6 +203,10 @@ enum ChatMediaInputGridEntry: Equatable, Comparable, Identifiable {
|
||||
return PaneSearchBarPlaceholderItem(theme: theme, strings: strings, type: .stickers, activate: {
|
||||
inputNodeInteraction.toggleSearch(true, .sticker, "")
|
||||
})
|
||||
case let .trendingList(theme, strings, packs):
|
||||
return StickerPaneTrendingListGridItem(account: account, theme: theme, strings: strings, trendingPacks: packs, inputNodeInteraction: inputNodeInteraction, dismiss: {
|
||||
inputNodeInteraction.dismissTrendingPacks(packs.map { $0.info.id })
|
||||
})
|
||||
case let .peerSpecificSetup(theme, strings, dismissed):
|
||||
return StickerPanePeerSpecificSetupGridItem(theme: theme, strings: strings, setup: {
|
||||
inputNodeInteraction.openPeerSpecificSettings()
|
||||
|
@ -222,16 +222,18 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
|
||||
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0)))
|
||||
|
||||
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize)
|
||||
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||
expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001)
|
||||
|
||||
let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate
|
||||
alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0)
|
||||
|
||||
self.currentExpanded = expanded
|
||||
|
||||
expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.imageNode.position.x - highlightSize.width / 2.0, y: self.imageNode.position.y - highlightSize.height / 2.0), size: highlightSize))
|
||||
|
@ -7,6 +7,7 @@ import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import TelegramNotices
|
||||
import MergeLists
|
||||
import AccountContext
|
||||
import StickerPackPreviewUI
|
||||
@ -67,7 +68,7 @@ func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemColle
|
||||
switch toEntries[i] {
|
||||
case .search, .peerSpecificSetup, .trending:
|
||||
break
|
||||
case .sticker:
|
||||
case .trendingList, .sticker:
|
||||
scrollToItem = GridNodeScrollToItem(index: i, position: .top(0.0), transition: .immediate, directionHint: .down, adjustForSection: true, adjustForTopInset: true)
|
||||
}
|
||||
}
|
||||
@ -140,7 +141,7 @@ func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemColle
|
||||
var firstIndexInSectionOffset = 0
|
||||
if !toEntries.isEmpty {
|
||||
switch toEntries[0].index {
|
||||
case .search, .peerSpecificSetup, .trending:
|
||||
case .search, .trendingList, .peerSpecificSetup, .trending:
|
||||
break
|
||||
case let .collectionIndex(index):
|
||||
firstIndexInSectionOffset = Int(index.itemIndex.index)
|
||||
@ -152,14 +153,11 @@ func preparedChatMediaInputGridEntryTransition(account: Account, view: ItemColle
|
||||
return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated)
|
||||
}
|
||||
|
||||
func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasUnreadTrending: Bool?, theme: PresentationTheme, hasGifs: Bool = true, hasSettings: Bool = true, expanded: Bool = false) -> [ChatMediaInputPanelEntry] {
|
||||
func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, theme: PresentationTheme, hasGifs: Bool = true, hasSettings: Bool = true, expanded: Bool = false) -> [ChatMediaInputPanelEntry] {
|
||||
var entries: [ChatMediaInputPanelEntry] = []
|
||||
if hasGifs {
|
||||
entries.append(.recentGifs(theme, expanded))
|
||||
}
|
||||
if let hasUnreadTrending = hasUnreadTrending {
|
||||
entries.append(.trending(hasUnreadTrending, theme, expanded))
|
||||
}
|
||||
if let savedStickers = savedStickers, !savedStickers.items.isEmpty {
|
||||
entries.append(.savedStickers(theme, expanded))
|
||||
}
|
||||
@ -221,7 +219,7 @@ func chatMediaInputPanelGifModeEntries(theme: PresentationTheme, reactions: [Str
|
||||
return entries
|
||||
}
|
||||
|
||||
func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, hasSearch: Bool = true, hasAccessories: Bool = true, strings: PresentationStrings, theme: PresentationTheme) -> [ChatMediaInputGridEntry] {
|
||||
func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, trendingPacks: [FeaturedStickerPackItem], dismissedTrendingStickerPacks: [ItemCollectionId.Id]? = nil, hasSearch: Bool = true, hasAccessories: Bool = true, strings: PresentationStrings, theme: PresentationTheme) -> [ChatMediaInputGridEntry] {
|
||||
var entries: [ChatMediaInputGridEntry] = []
|
||||
|
||||
if hasSearch && view.lower == nil {
|
||||
@ -249,6 +247,14 @@ func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: Ordered
|
||||
}
|
||||
}
|
||||
|
||||
var trendingIsDismissed = false
|
||||
if let dismissedTrendingStickerPacks = dismissedTrendingStickerPacks, trendingPacks.map({ $0.info.id.id }) == dismissedTrendingStickerPacks {
|
||||
trendingIsDismissed = true
|
||||
}
|
||||
if !trendingIsDismissed {
|
||||
entries.append(.trendingList(theme: theme, strings: strings, packs: trendingPacks))
|
||||
}
|
||||
|
||||
if let recentStickers = recentStickers, !recentStickers.items.isEmpty {
|
||||
let packInfo = StickerPackCollectionInfo(id: ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0), flags: [], accessHash: 0, title: strings.Stickers_FrequentlyUsed.uppercased(), shortName: "", thumbnail: nil, immediateThumbnailData: nil, hash: 0, count: 0)
|
||||
var addedCount = 0
|
||||
@ -346,6 +352,8 @@ final class ChatMediaInputNodeInteraction {
|
||||
let navigateBackToStickers: () -> Void
|
||||
let setGifMode: (ChatMediaInputGifMode) -> Void
|
||||
let openSettings: () -> Void
|
||||
let openTrending: (ItemCollectionId?) -> Void
|
||||
let dismissTrendingPacks: ([ItemCollectionId]) -> Void
|
||||
let toggleSearch: (Bool, ChatMediaInputSearchMode?, String) -> Void
|
||||
let openPeerSpecificSettings: () -> Void
|
||||
let dismissPeerSpecificSettings: () -> Void
|
||||
@ -360,11 +368,13 @@ final class ChatMediaInputNodeInteraction {
|
||||
var displayStickerPlaceholder = true
|
||||
var displayStickerPackManageControls = true
|
||||
|
||||
init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, navigateBackToStickers: @escaping () -> Void, setGifMode: @escaping (ChatMediaInputGifMode) -> Void, openSettings: @escaping () -> Void, toggleSearch: @escaping (Bool, ChatMediaInputSearchMode?, String) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void, clearRecentlyUsedStickers: @escaping () -> Void) {
|
||||
init(navigateToCollectionId: @escaping (ItemCollectionId) -> Void, navigateBackToStickers: @escaping () -> Void, setGifMode: @escaping (ChatMediaInputGifMode) -> Void, openSettings: @escaping () -> Void, openTrending: @escaping (ItemCollectionId?) -> Void, dismissTrendingPacks: @escaping ([ItemCollectionId]) -> Void, toggleSearch: @escaping (Bool, ChatMediaInputSearchMode?, String) -> Void, openPeerSpecificSettings: @escaping () -> Void, dismissPeerSpecificSettings: @escaping () -> Void, clearRecentlyUsedStickers: @escaping () -> Void) {
|
||||
self.navigateToCollectionId = navigateToCollectionId
|
||||
self.navigateBackToStickers = navigateBackToStickers
|
||||
self.setGifMode = setGifMode
|
||||
self.openSettings = openSettings
|
||||
self.openTrending = openTrending
|
||||
self.dismissTrendingPacks = dismissTrendingPacks
|
||||
self.toggleSearch = toggleSearch
|
||||
self.openPeerSpecificSettings = openPeerSpecificSettings
|
||||
self.dismissPeerSpecificSettings = dismissPeerSpecificSettings
|
||||
@ -451,14 +461,15 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
private var currentView: ItemCollectionsView?
|
||||
private let dismissedPeerSpecificStickerPack = Promise<Bool>()
|
||||
|
||||
private var panelCollapseScrollToIndex: Int?
|
||||
private let panelExpandedPromise = ValuePromise<Bool>(false)
|
||||
private var panelExpanded: Bool = false {
|
||||
private var panelFocusScrollToIndex: Int?
|
||||
private var panelFocusInitialPosition: CGPoint?
|
||||
private let panelIsFocusedPromise = ValuePromise<Bool>(false)
|
||||
private var panelIsFocused: Bool = false {
|
||||
didSet {
|
||||
self.panelExpandedPromise.set(self.panelExpanded)
|
||||
self.panelIsFocusedPromise.set(self.panelIsFocused)
|
||||
}
|
||||
}
|
||||
private var panelCollapseTimer: SwiftSignalKit.Timer?
|
||||
private var panelFocusTimer: SwiftSignalKit.Timer?
|
||||
|
||||
var requestDisableStickerAnimations: ((Bool) -> Void)?
|
||||
|
||||
@ -494,17 +505,15 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
self.themeAndStringsPromise = Promise((theme, strings))
|
||||
|
||||
self.collectionListPanel = ASDisplayNode()
|
||||
self.collectionListPanel.clipsToBounds = true
|
||||
|
||||
self.collectionListSeparator = ASDisplayNode()
|
||||
self.collectionListSeparator.isLayerBacked = true
|
||||
self.collectionListSeparator.backgroundColor = theme.chat.inputMediaPanel.panelSeparatorColor
|
||||
|
||||
self.collectionListContainer = CollectionListContainerNode()
|
||||
self.collectionListContainer.clipsToBounds = true
|
||||
|
||||
self.listView = ListView()
|
||||
// self.listView.clipsToBounds = false
|
||||
self.listView.useSingleDimensionTouchPoint = true
|
||||
self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
||||
self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false
|
||||
self.listView.accessibilityPageScrolledString = { row, count in
|
||||
@ -512,7 +521,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
self.gifListView = ListView()
|
||||
// self.gifListView.clipsToBounds = false
|
||||
self.gifListView.useSingleDimensionTouchPoint = true
|
||||
self.gifListView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
||||
self.gifListView.scroller.panGestureRecognizer.cancelsTouchesInView = false
|
||||
self.gifListView.accessibilityPageScrolledString = { row, count in
|
||||
@ -550,6 +559,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
} else if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue {
|
||||
strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen(
|
||||
context: strongSelf.context,
|
||||
highlightedPackId: nil,
|
||||
sendSticker: {
|
||||
fileReference, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
@ -609,6 +619,23 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
controller.navigationPresentation = .modal
|
||||
strongSelf.controllerInteraction.navigationController()?.pushViewController(controller)
|
||||
}
|
||||
}, openTrending: { [weak self] packId in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen(
|
||||
context: strongSelf.context,
|
||||
highlightedPackId: packId,
|
||||
sendSticker: {
|
||||
fileReference, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.controllerInteraction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
}, dismissTrendingPacks: { packIds in
|
||||
let _ = ApplicationSpecificNotice.setDismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager, values: packIds.map { $0.id }).start()
|
||||
}, toggleSearch: { [weak self] value, searchMode, query in
|
||||
if let strongSelf = self {
|
||||
if let searchMode = searchMode, value {
|
||||
@ -636,8 +663,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction.updateInputMode { current in
|
||||
switch current {
|
||||
case let .media(mode, _):
|
||||
return .media(mode: mode, expanded: .search(searchMode))
|
||||
case let .media(mode, _, focused):
|
||||
return .media(mode: mode, expanded: .search(searchMode), focused: focused)
|
||||
default:
|
||||
return current
|
||||
}
|
||||
@ -648,8 +675,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
} else {
|
||||
strongSelf.controllerInteraction.updateInputMode { current in
|
||||
switch current {
|
||||
case let .media(mode, _):
|
||||
return .media(mode: mode, expanded: nil)
|
||||
case let .media(mode, _, focused):
|
||||
return .media(mode: mode, expanded: nil, focused: focused)
|
||||
default:
|
||||
return current
|
||||
}
|
||||
@ -860,8 +887,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
|
||||
let previousView = Atomic<ItemCollectionsView?>(value: nil)
|
||||
let transitionQueue = Queue()
|
||||
let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions, self.panelExpandedPromise.get())
|
||||
|> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions, panelExpanded -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
|
||||
let transitions = combineLatest(queue: transitionQueue, itemCollectionsView, peerSpecificPack, context.account.viewTracker.featuredStickerPacks(), self.themeAndStringsPromise.get(), reactions, self.panelIsFocusedPromise.get(), ApplicationSpecificNotice.dismissedTrendingStickerPacks(accountManager: context.sharedContext.accountManager))
|
||||
|> map { viewAndUpdate, peerSpecificPack, trendingPacks, themeAndStrings, reactions, panelExpanded, dismissedTrendingStickerPacks -> (ItemCollectionsView, ChatMediaInputPanelTransition, ChatMediaInputPanelTransition, Bool, ChatMediaInputGridTransition, Bool) in
|
||||
let (view, viewUpdate) = viewAndUpdate
|
||||
let previous = previousView.swap(view)
|
||||
var update = viewUpdate
|
||||
@ -884,21 +911,10 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
for info in view.collectionInfos {
|
||||
installedPacks.insert(info.0)
|
||||
}
|
||||
|
||||
var hasUnreadTrending: Bool?
|
||||
for pack in trendingPacks {
|
||||
if hasUnreadTrending == nil {
|
||||
hasUnreadTrending = false
|
||||
}
|
||||
if pack.unread {
|
||||
hasUnreadTrending = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, hasUnreadTrending: hasUnreadTrending, theme: theme, expanded: panelExpanded)
|
||||
|
||||
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, theme: theme, expanded: panelExpanded)
|
||||
let gifPaneEntries = chatMediaInputPanelGifModeEntries(theme: theme, reactions: reactions, expanded: panelExpanded)
|
||||
var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, strings: strings, theme: theme)
|
||||
var gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, trendingPacks: trendingPacks, dismissedTrendingStickerPacks: dismissedTrendingStickerPacks, strings: strings, theme: theme)
|
||||
|
||||
if view.higher == nil {
|
||||
var hasTopSeparator = true
|
||||
@ -943,7 +959,9 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
if let topVisibleSection = visibleItems.topSectionVisible as? ChatMediaInputStickerGridSection {
|
||||
topVisibleCollectionId = topVisibleSection.collectionId
|
||||
} else if let topVisible = visibleItems.topVisible {
|
||||
if let item = topVisible.1 as? ChatMediaInputStickerGridItem {
|
||||
if let _ = topVisible.1 as? StickerPaneTrendingListGridItem {
|
||||
topVisibleCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.recentStickers.rawValue, id: 0)
|
||||
} else if let item = topVisible.1 as? ChatMediaInputStickerGridItem {
|
||||
topVisibleCollectionId = item.index.collectionId
|
||||
} else if let _ = topVisible.1 as? StickerPanePeerSpecificSetupGridItem {
|
||||
topVisibleCollectionId = ItemCollectionId(namespace: ChatMediaInputPanelAuxiliaryNamespace.peerSpecific.rawValue, id: 0)
|
||||
@ -998,32 +1016,79 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
self.listView.beganInteractiveDragging = { [weak self] position in
|
||||
if let strongSelf = self, false {
|
||||
if !strongSelf.panelExpanded, let index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) {
|
||||
strongSelf.panelCollapseScrollToIndex = index
|
||||
if let strongSelf = self {
|
||||
strongSelf.panelFocusTimer?.invalidate()
|
||||
var position = position
|
||||
var index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y))
|
||||
if index == nil {
|
||||
position.y += 10.0
|
||||
index = strongSelf.listView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y))
|
||||
}
|
||||
if let index = index {
|
||||
strongSelf.panelFocusScrollToIndex = index
|
||||
strongSelf.panelFocusInitialPosition = position
|
||||
}
|
||||
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in
|
||||
if case let .media(mode, expanded, _) = inputMode {
|
||||
return (inputTextState, .media(mode: mode, expanded: expanded, focused: true))
|
||||
} else {
|
||||
return (inputTextState, inputMode)
|
||||
}
|
||||
}
|
||||
strongSelf.updateIsExpanded(true)
|
||||
}
|
||||
}
|
||||
|
||||
self.listView.didEndScrolling = { [weak self] in
|
||||
if let strongSelf = self, false {
|
||||
strongSelf.setupCollapseTimer()
|
||||
self.listView.endedInteractiveDragging = { [weak self] position in
|
||||
if let strongSelf = self {
|
||||
strongSelf.panelFocusInitialPosition = position
|
||||
}
|
||||
}
|
||||
|
||||
self.listView.didEndScrolling = { [weak self] decelerated in
|
||||
if let strongSelf = self {
|
||||
if decelerated {
|
||||
strongSelf.panelFocusScrollToIndex = nil
|
||||
strongSelf.panelFocusInitialPosition = nil
|
||||
}
|
||||
strongSelf.setupCollapseTimer(timeout: decelerated ? 0.5 : 1.5)
|
||||
}
|
||||
}
|
||||
|
||||
self.gifListView.beganInteractiveDragging = { [weak self] position in
|
||||
if let strongSelf = self, false {
|
||||
if !strongSelf.panelExpanded, let index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y)) {
|
||||
strongSelf.panelCollapseScrollToIndex = index
|
||||
if let strongSelf = self {
|
||||
strongSelf.panelFocusTimer?.invalidate()
|
||||
var position = position
|
||||
var index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y))
|
||||
if index == nil {
|
||||
position.y += 10.0
|
||||
index = strongSelf.gifListView.itemIndexAtPoint(CGPoint(x: 0.0, y: position.y))
|
||||
}
|
||||
if let index = index {
|
||||
strongSelf.panelFocusScrollToIndex = index
|
||||
}
|
||||
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in
|
||||
if case let .media(mode, expanded, _) = inputMode {
|
||||
return (inputTextState, .media(mode: mode, expanded: expanded, focused: true))
|
||||
} else {
|
||||
return (inputTextState, inputMode)
|
||||
}
|
||||
}
|
||||
strongSelf.updateIsExpanded(true)
|
||||
}
|
||||
}
|
||||
|
||||
self.gifListView.didEndScrolling = { [weak self] in
|
||||
if let strongSelf = self, false {
|
||||
strongSelf.setupCollapseTimer()
|
||||
self.gifListView.endedInteractiveDragging = { [weak self] position in
|
||||
if let strongSelf = self {
|
||||
strongSelf.panelFocusInitialPosition = position
|
||||
}
|
||||
}
|
||||
|
||||
self.gifListView.didEndScrolling = { [weak self] decelerated in
|
||||
if let strongSelf = self {
|
||||
if decelerated {
|
||||
strongSelf.panelFocusScrollToIndex = nil
|
||||
strongSelf.panelFocusInitialPosition = nil
|
||||
}
|
||||
strongSelf.setupCollapseTimer(timeout: decelerated ? 0.5 : 1.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1031,23 +1096,31 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
self.searchContainerNodeLoadedDisposable.dispose()
|
||||
self.panelCollapseTimer?.invalidate()
|
||||
self.panelFocusTimer?.invalidate()
|
||||
}
|
||||
|
||||
private func updateIsExpanded(_ isExpanded: Bool) {
|
||||
self.panelCollapseTimer?.invalidate()
|
||||
|
||||
self.panelExpanded = isExpanded
|
||||
guard self.panelIsFocused != isExpanded else {
|
||||
return
|
||||
}
|
||||
|
||||
self.panelIsFocused = isExpanded
|
||||
self.updatePaneClippingContainer(size: self.paneClippingContainer.bounds.size, offset: self.currentCollectionListPanelOffset(), transition: .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
|
||||
private func setupCollapseTimer() {
|
||||
self.panelCollapseTimer?.invalidate()
|
||||
private func setupCollapseTimer(timeout: Double) {
|
||||
self.panelFocusTimer?.invalidate()
|
||||
|
||||
let timer = SwiftSignalKit.Timer(timeout: 1.5, repeat: false, completion: { [weak self] in
|
||||
self?.updateIsExpanded(false)
|
||||
let timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: { [weak self] in
|
||||
self?.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in
|
||||
if case let .media(mode, expanded, _) = inputMode {
|
||||
return (inputTextState, .media(mode: mode, expanded: expanded, focused: false))
|
||||
} else {
|
||||
return (inputTextState, inputMode)
|
||||
}
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
self.panelCollapseTimer = timer
|
||||
self.panelFocusTimer = timer
|
||||
timer.start()
|
||||
}
|
||||
|
||||
@ -1502,10 +1575,19 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
itemNode.updateIsHighlighted()
|
||||
if itemNode.currentCollectionId == collectionId {
|
||||
if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||
self.panelCollapseScrollToIndex = targetIndex
|
||||
self.updateIsExpanded(false)
|
||||
if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||
self.panelFocusScrollToIndex = targetIndex
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in
|
||||
if case let .media(mode, expanded, _) = inputMode {
|
||||
return (inputTextState, .media(mode: mode, expanded: expanded, focused: false))
|
||||
} else {
|
||||
return (inputTextState, inputMode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.panelFocusScrollToIndex = nil
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.listView.ensureItemNodeVisible(itemNode)
|
||||
}
|
||||
ensuredNodeVisible = true
|
||||
@ -1513,10 +1595,19 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
} else if let itemNode = itemNode as? ChatMediaInputMetaSectionItemNode {
|
||||
itemNode.updateIsHighlighted()
|
||||
if itemNode.currentCollectionId == collectionId {
|
||||
if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||
self.panelCollapseScrollToIndex = targetIndex
|
||||
self.updateIsExpanded(false)
|
||||
if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||
self.panelFocusScrollToIndex = targetIndex
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in
|
||||
if case let .media(mode, expanded, _) = inputMode {
|
||||
return (inputTextState, .media(mode: mode, expanded: expanded, focused: false))
|
||||
} else {
|
||||
return (inputTextState, inputMode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.panelFocusScrollToIndex = nil
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.listView.ensureItemNodeVisible(itemNode)
|
||||
}
|
||||
ensuredNodeVisible = true
|
||||
@ -1524,10 +1615,19 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
} else if let itemNode = itemNode as? ChatMediaInputRecentGifsItemNode {
|
||||
itemNode.updateIsHighlighted()
|
||||
if itemNode.currentCollectionId == collectionId {
|
||||
if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||
self.panelCollapseScrollToIndex = targetIndex
|
||||
self.updateIsExpanded(false)
|
||||
if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||
self.panelFocusScrollToIndex = targetIndex
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in
|
||||
if case let .media(mode, expanded, _) = inputMode {
|
||||
return (inputTextState, .media(mode: mode, expanded: expanded, focused: false))
|
||||
} else {
|
||||
return (inputTextState, inputMode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.panelFocusScrollToIndex = nil
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.listView.ensureItemNodeVisible(itemNode)
|
||||
}
|
||||
ensuredNodeVisible = true
|
||||
@ -1535,10 +1635,19 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
} else if let itemNode = itemNode as? ChatMediaInputTrendingItemNode {
|
||||
itemNode.updateIsHighlighted()
|
||||
if itemNode.currentCollectionId == collectionId {
|
||||
if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||
self.panelCollapseScrollToIndex = targetIndex
|
||||
self.updateIsExpanded(false)
|
||||
if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||
self.panelFocusScrollToIndex = targetIndex
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in
|
||||
if case let .media(mode, expanded, _) = inputMode {
|
||||
return (inputTextState, .media(mode: mode, expanded: expanded, focused: false))
|
||||
} else {
|
||||
return (inputTextState, inputMode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.panelFocusScrollToIndex = nil
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.listView.ensureItemNodeVisible(itemNode)
|
||||
}
|
||||
ensuredNodeVisible = true
|
||||
@ -1546,10 +1655,19 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
} else if let itemNode = itemNode as? ChatMediaInputPeerSpecificItemNode {
|
||||
itemNode.updateIsHighlighted()
|
||||
if itemNode.currentCollectionId == collectionId {
|
||||
if self.panelExpanded, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||
self.panelCollapseScrollToIndex = targetIndex
|
||||
self.updateIsExpanded(false)
|
||||
if self.panelIsFocused, let targetIndex = self.listView.indexOf(itemNode: itemNode) {
|
||||
self.panelFocusScrollToIndex = targetIndex
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in
|
||||
if case let .media(mode, expanded, _) = inputMode {
|
||||
return (inputTextState, .media(mode: mode, expanded: expanded, focused: false))
|
||||
} else {
|
||||
return (inputTextState, inputMode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.panelFocusScrollToIndex = nil
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.listView.ensureItemNodeVisible(itemNode)
|
||||
}
|
||||
ensuredNodeVisible = true
|
||||
@ -1562,10 +1680,19 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
let firstVisibleIndex = currentView.collectionInfos.firstIndex(where: { id, _, _ in return id == firstVisibleCollectionId })
|
||||
if let targetIndex = targetIndex, let firstVisibleIndex = firstVisibleIndex {
|
||||
let toRight = targetIndex > firstVisibleIndex
|
||||
if self.panelExpanded {
|
||||
self.panelCollapseScrollToIndex = targetIndex
|
||||
self.updateIsExpanded(false)
|
||||
if self.panelIsFocused {
|
||||
self.panelFocusScrollToIndex = targetIndex
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.interfaceInteraction?.updateTextInputStateAndMode { inputTextState, inputMode in
|
||||
if case let .media(mode, expanded, _) = inputMode {
|
||||
return (inputTextState, .media(mode: mode, expanded: expanded, focused: false))
|
||||
} else {
|
||||
return (inputTextState, inputMode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.panelFocusScrollToIndex = nil
|
||||
self.panelFocusInitialPosition = nil
|
||||
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [], scrollToItem: ListViewScrollToItem(index: targetIndex, position: toRight ? .bottom(0.0) : .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: toRight ? .Down : .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil)
|
||||
}
|
||||
}
|
||||
@ -1643,7 +1770,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, standardInputHeight: CGFloat, inputHeight: CGFloat, maximumHeight: CGFloat, inputPanelHeight: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState, deviceMetrics: DeviceMetrics, isVisible: Bool) -> (CGFloat, CGFloat) {
|
||||
var searchMode: ChatMediaInputSearchMode?
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = self.validLayout, case let .media(_, maybeExpanded) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded {
|
||||
if let (_, _, _, _, _, _, _, _, interfaceState, _, _) = self.validLayout, case let .media(_, maybeExpanded, _) = interfaceState.inputMode, let expanded = maybeExpanded, case let .search(mode) = expanded {
|
||||
searchMode = mode
|
||||
}
|
||||
|
||||
@ -1659,8 +1786,12 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
let separatorHeight = UIScreenPixel
|
||||
let panelHeight: CGFloat
|
||||
|
||||
var isFocused = false
|
||||
var isExpanded: Bool = false
|
||||
if case let .media(_, maybeExpanded) = interfaceState.inputMode, let expanded = maybeExpanded {
|
||||
if case let .media(_, _, focused) = interfaceState.inputMode {
|
||||
isFocused = focused
|
||||
}
|
||||
if case let .media(_, maybeExpanded, _) = interfaceState.inputMode, let expanded = maybeExpanded {
|
||||
isExpanded = true
|
||||
switch expanded {
|
||||
case .content:
|
||||
@ -1677,6 +1808,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
panelHeight = standardInputHeight
|
||||
}
|
||||
|
||||
self.updateIsExpanded(isFocused)
|
||||
|
||||
if displaySearch {
|
||||
if let searchContainerNode = self.searchContainerNode {
|
||||
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: -inputPanelHeight), size: CGSize(width: width, height: panelHeight + inputPanelHeight))
|
||||
@ -1721,10 +1854,10 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
transition.updateFrame(node: self.collectionListPanel, frame: CGRect(origin: CGPoint(x: 0.0, y: collectionListPanelOffset), size: CGSize(width: width, height: 41.0)))
|
||||
transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 41.0 + collectionListPanelOffset), size: CGSize(width: width, height: separatorHeight)))
|
||||
|
||||
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 20.0, height: width)
|
||||
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 40.0, height: width)
|
||||
transition.updatePosition(node: self.listView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0))
|
||||
|
||||
self.gifListView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 20.0, height: width)
|
||||
self.gifListView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0 + 31.0 + 40.0, height: width)
|
||||
transition.updatePosition(node: self.gifListView, position: CGPoint(x: width / 2.0, y: (41.0 - collectionListPanelOffset) / 2.0))
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
@ -1891,15 +2024,24 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
var scrollToItem: ListViewScrollToItem?
|
||||
if let targetIndex = self.panelCollapseScrollToIndex {
|
||||
if let targetIndex = self.panelFocusScrollToIndex {
|
||||
var position: ListViewScrollPosition
|
||||
if self.panelExpanded {
|
||||
position = .center(.top)
|
||||
if self.panelIsFocused {
|
||||
if let initialPosition = self.panelFocusInitialPosition {
|
||||
position = .top(96.0 + (initialPosition.y - self.listView.frame.height / 2.0) * 0.5)
|
||||
} else {
|
||||
position = .top(96.0)
|
||||
}
|
||||
} else {
|
||||
position = .top(self.listView.frame.height / 2.0 + 96.0)
|
||||
if let initialPosition = self.panelFocusInitialPosition {
|
||||
position = .top(self.listView.frame.height / 2.0 + 96.0 + (initialPosition.y - self.listView.frame.height / 2.0))
|
||||
} else {
|
||||
position = .top(self.listView.frame.height / 2.0 + 96.0)
|
||||
}
|
||||
self.panelFocusScrollToIndex = nil
|
||||
self.panelFocusInitialPosition = nil
|
||||
}
|
||||
scrollToItem = ListViewScrollToItem(index: targetIndex, position: position, animated: true, curve: .Default(duration: nil), directionHint: .Down)
|
||||
self.panelCollapseScrollToIndex = nil
|
||||
scrollToItem = ListViewScrollToItem(index: targetIndex, position: position, animated: true, curve: .Spring(duration: 0.4), directionHint: .Down, displayLink: true)
|
||||
}
|
||||
|
||||
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: scrollToItem, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
@ -1993,7 +2135,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
|
||||
private var isExpanded: Bool {
|
||||
var isExpanded: Bool = false
|
||||
if let validLayout = self.validLayout, case let .media(_, maybeExpanded) = validLayout.8.inputMode, maybeExpanded != nil {
|
||||
if let validLayout = self.validLayout, case let .media(_, maybeExpanded, _) = validLayout.8.inputMode, maybeExpanded != nil {
|
||||
isExpanded = true
|
||||
}
|
||||
return isExpanded
|
||||
@ -2021,7 +2163,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
var collectionListPanelOffset = self.currentCollectionListPanelOffset()
|
||||
if self.panelExpanded {
|
||||
if self.panelIsFocused {
|
||||
collectionListPanelOffset = 0.0
|
||||
}
|
||||
|
||||
@ -2037,14 +2179,12 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
|
||||
private func updatePaneClippingContainer(size: CGSize, offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
var offset = offset
|
||||
var additionalOffset: CGFloat = 0.0
|
||||
if self.panelExpanded {
|
||||
if self.panelIsFocused {
|
||||
offset = 0.0
|
||||
additionalOffset = 31.0
|
||||
}
|
||||
transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0 + additionalOffset), size: self.collectionListSeparator.bounds.size))
|
||||
transition.updateFrame(node: self.paneClippingContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0 + additionalOffset), size: size))
|
||||
transition.updateSublayerTransformOffset(layer: self.paneClippingContainer.layer, offset: CGPoint(x: 0.0, y: -offset - 41.0 - additionalOffset))
|
||||
transition.updateFrame(node: self.collectionListSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0), size: self.collectionListSeparator.bounds.size))
|
||||
transition.updateFrame(node: self.paneClippingContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: offset + 41.0), size: size))
|
||||
transition.updateSublayerTransformOffset(layer: self.paneClippingContainer.layer, offset: CGPoint(x: 0.0, y: -offset - 41.0))
|
||||
}
|
||||
|
||||
private func fixPaneScroll(pane: ChatMediaInputPane, state: ChatMediaInputPaneScrollState) {
|
||||
@ -2059,7 +2199,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
var collectionListPanelOffset = self.currentCollectionListPanelOffset()
|
||||
if self.panelExpanded {
|
||||
if self.panelIsFocused {
|
||||
collectionListPanelOffset = 0.0
|
||||
}
|
||||
|
||||
@ -2073,6 +2213,15 @@ final class ChatMediaInputNode: ChatInputNode {
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.panelIsFocused {
|
||||
let convertedPoint = CGPoint(x: max(0.0, point.y), y: point.x)
|
||||
if let result = self.listView.hitTest(convertedPoint, with: event) {
|
||||
return result
|
||||
}
|
||||
if let result = self.gifListView.hitTest(convertedPoint, with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
if let searchContainerNode = self.searchContainerNode {
|
||||
if let result = searchContainerNode.hitTest(point.offsetBy(dx: -searchContainerNode.frame.minX, dy: -searchContainerNode.frame.minY), with: event) {
|
||||
return result
|
||||
|
@ -133,16 +133,18 @@ final class ChatMediaInputPeerSpecificItemNode: ListViewItemNode {
|
||||
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0)))
|
||||
|
||||
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize)
|
||||
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.avatarNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||
expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001)
|
||||
|
||||
let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate
|
||||
alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0)
|
||||
|
||||
self.currentExpanded = expanded
|
||||
|
||||
self.avatarNode.bounds = CGRect(origin: CGPoint(), size: imageSize)
|
||||
|
@ -124,16 +124,18 @@ final class ChatMediaInputRecentGifsItemNode: ListViewItemNode {
|
||||
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0)))
|
||||
|
||||
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize)
|
||||
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||
expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001)
|
||||
|
||||
let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate
|
||||
alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0)
|
||||
|
||||
self.currentExpanded = expanded
|
||||
|
||||
expandTransition.updateFrame(node: self.highlightNode, frame: expanded ? titleFrame.insetBy(dx: -7.0, dy: -2.0) : CGRect(origin: CGPoint(x: self.imageNode.position.x - highlightSize.width / 2.0, y: self.imageNode.position.y - highlightSize.height / 2.0), size: highlightSize))
|
||||
|
@ -91,6 +91,7 @@ final class ChatMediaInputSettingsItemNode: ListViewItemNode {
|
||||
self.containerNode.addSubnode(self.scalingNode)
|
||||
|
||||
self.scalingNode.addSubnode(self.buttonNode)
|
||||
self.scalingNode.addSubnode(self.titleNode)
|
||||
self.scalingNode.addSubnode(self.imageNode)
|
||||
}
|
||||
|
||||
@ -114,18 +115,19 @@ final class ChatMediaInputSettingsItemNode: ListViewItemNode {
|
||||
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0)))
|
||||
|
||||
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize)
|
||||
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||
expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001)
|
||||
|
||||
let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate
|
||||
alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0)
|
||||
|
||||
self.currentExpanded = expanded
|
||||
|
||||
}
|
||||
|
||||
func updateAppearanceTransition(transition: ContainedViewLayoutTransition) {
|
||||
|
@ -185,7 +185,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
var resourceReference: MediaResourceReference?
|
||||
if let thumbnail = info.thumbnail {
|
||||
if info.flags.contains(.isAnimated) {
|
||||
thumbnailItem = .animated(thumbnail.resource)
|
||||
thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions)
|
||||
resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)
|
||||
} else {
|
||||
thumbnailItem = .still(thumbnail)
|
||||
@ -193,7 +193,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
}
|
||||
} else if let item = item {
|
||||
if item.file.isAnimatedSticker {
|
||||
thumbnailItem = .animated(item.file.resource)
|
||||
thumbnailItem = .animated(item.file.resource, item.file.dimensions ?? PixelDimensions(width: 100, height: 100))
|
||||
resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource)
|
||||
} else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource {
|
||||
thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil))
|
||||
@ -210,15 +210,16 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
|
||||
if self.currentThumbnailItem != thumbnailItem {
|
||||
self.currentThumbnailItem = thumbnailItem
|
||||
let thumbnailDimensions = PixelDimensions(width: 512, height: 512)
|
||||
if let thumbnailItem = thumbnailItem {
|
||||
switch thumbnailItem {
|
||||
case let .still(representation):
|
||||
imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize)
|
||||
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingImageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
imageApply()
|
||||
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true))
|
||||
case let .animated(resource):
|
||||
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
case let .animated(resource, _):
|
||||
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: boundingImageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
imageApply()
|
||||
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: resource, animated: true, nilIfEmpty: true))
|
||||
|
||||
@ -236,7 +237,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
} else {
|
||||
self.scalingNode.addSubnode(animatedStickerNode)
|
||||
}
|
||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 80, height: 80, mode: .cached)
|
||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 128, height: 128, mode: .cached)
|
||||
}
|
||||
animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers
|
||||
}
|
||||
@ -247,7 +248,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
let imageSize = boundingImageSize
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), data: info.immediateThumbnailData, size: imageSize, imageSize: CGSize(width: 100.0, height: 100.0))
|
||||
placeholderNode.update(backgroundColor: nil, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), data: info.immediateThumbnailData, size: imageSize, imageSize: thumbnailDimensions.cgSize)
|
||||
}
|
||||
|
||||
self.updateIsHighlighted()
|
||||
@ -259,16 +260,18 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0)))
|
||||
|
||||
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize)
|
||||
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||
expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001)
|
||||
|
||||
let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate
|
||||
alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0)
|
||||
|
||||
self.currentExpanded = expanded
|
||||
|
||||
self.imageNode.bounds = CGRect(origin: CGPoint(), size: imageSize)
|
||||
|
@ -143,14 +143,17 @@ final class ChatMediaInputTrendingItemNode: ListViewItemNode {
|
||||
let expandScale: CGFloat = expanded ? 1.0 : boundingImageScale
|
||||
let expandTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: 0.3, curve: .spring) : .immediate
|
||||
expandTransition.updateTransformScale(node: self.scalingNode, scale: expandScale)
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -2.0 : 3.0)))
|
||||
expandTransition.updatePosition(node: self.scalingNode, position: CGPoint(x: boundsSize.width / 2.0, y: boundsSize.height / 2.0 + (expanded ? -53.0 : -7.0)))
|
||||
|
||||
expandTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0)
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: expandedBoundingSize.width - 8.0, height: expandedBoundingSize.height))
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 2.0), size: titleSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((expandedBoundingSize.width - titleSize.width) / 2.0), y: expandedBoundingSize.height - titleSize.height + 6.0), size: titleSize)
|
||||
let displayTitleFrame = expanded ? titleFrame : CGRect(origin: CGPoint(x: titleFrame.minX, y: self.imageNode.position.y - titleFrame.size.height), size: titleFrame.size)
|
||||
expandTransition.updateFrameAsPositionAndBounds(node: self.titleNode, frame: displayTitleFrame)
|
||||
expandTransition.updateTransformScale(node: self.titleNode, scale: expanded ? 1.0 : 0.001)
|
||||
|
||||
let alphaTransition: ContainedViewLayoutTransition = self.currentExpanded != expanded ? .animated(duration: expanded ? 0.15 : 0.1, curve: .linear) : .immediate
|
||||
alphaTransition.updateAlpha(node: self.titleNode, alpha: expanded ? 1.0 : 0.0, delay: expanded ? 0.05 : 0.0)
|
||||
|
||||
self.currentExpanded = expanded
|
||||
|
||||
|
@ -73,7 +73,7 @@ enum ChatMediaInputExpanded: Equatable {
|
||||
enum ChatInputMode: Equatable {
|
||||
case none
|
||||
case text
|
||||
case media(mode: ChatMediaInputMode, expanded: ChatMediaInputExpanded?)
|
||||
case media(mode: ChatMediaInputMode, expanded: ChatMediaInputExpanded?, focused: Bool)
|
||||
case inputButtons
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,7 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode {
|
||||
|
||||
transition.updateFrame(node: self.expandMediaInputButton, frame: CGRect(origin: CGPoint(), size: size))
|
||||
var expanded = false
|
||||
if case let .media(_, maybeExpanded) = interfaceState.inputMode, maybeExpanded != nil {
|
||||
if case let .media(_, maybeExpanded, _) = interfaceState.inputMode, maybeExpanded != nil {
|
||||
expanded = true
|
||||
}
|
||||
transition.updateSublayerTransformScale(node: self.expandMediaInputButton, scale: CGPoint(x: 1.0, y: expanded ? 1.0 : -1.0))
|
||||
|
@ -230,6 +230,7 @@ enum ChatTextInputPanelPasteData {
|
||||
}
|
||||
|
||||
class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
let clippingNode: ASDisplayNode
|
||||
var textPlaceholderNode: ImmediateTextNode
|
||||
var contextPlaceholderNode: TextNode?
|
||||
var slowmodePlaceholderNode: ChatTextInputSlowmodePlaceholderNode?
|
||||
@ -434,6 +435,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
init(presentationInterfaceState: ChatPresentationInterfaceState, presentController: @escaping (ViewController) -> Void) {
|
||||
self.presentationInterfaceState = presentationInterfaceState
|
||||
|
||||
self.clippingNode = ASDisplayNode()
|
||||
self.clippingNode.clipsToBounds = true
|
||||
|
||||
self.textInputContainerBackgroundNode = ASImageNode()
|
||||
self.textInputContainerBackgroundNode.isUserInteractionEnabled = false
|
||||
self.textInputContainerBackgroundNode.displaysAsynchronously = false
|
||||
@ -476,6 +480,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.clippingNode)
|
||||
|
||||
self.menuButton.addTarget(self, action: #selector(self.menuButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.menuButton.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
@ -565,24 +571,24 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
self.searchLayoutClearButton.addTarget(self, action: #selector(self.searchLayoutClearButtonPressed), for: .touchUpInside)
|
||||
self.searchLayoutClearButton.alpha = 0.0
|
||||
|
||||
self.addSubnode(self.textInputContainer)
|
||||
self.addSubnode(self.textInputBackgroundNode)
|
||||
self.clippingNode.addSubnode(self.textInputContainer)
|
||||
self.clippingNode.addSubnode(self.textInputBackgroundNode)
|
||||
|
||||
self.addSubnode(self.textPlaceholderNode)
|
||||
self.clippingNode.addSubnode(self.textPlaceholderNode)
|
||||
|
||||
self.menuButton.addSubnode(self.menuButtonBackgroundNode)
|
||||
self.menuButton.addSubnode(self.menuButtonClippingNode)
|
||||
self.menuButtonClippingNode.addSubnode(self.menuButtonTextNode)
|
||||
self.menuButton.addSubnode(self.menuButtonIconNode)
|
||||
|
||||
self.addSubnode(self.menuButton)
|
||||
self.addSubnode(self.attachmentButton)
|
||||
self.addSubnode(self.attachmentButtonDisabledNode)
|
||||
self.clippingNode.addSubnode(self.menuButton)
|
||||
self.clippingNode.addSubnode(self.attachmentButton)
|
||||
self.clippingNode.addSubnode(self.attachmentButtonDisabledNode)
|
||||
|
||||
self.addSubnode(self.actionButtons)
|
||||
self.addSubnode(self.counterTextNode)
|
||||
self.clippingNode.addSubnode(self.actionButtons)
|
||||
self.clippingNode.addSubnode(self.counterTextNode)
|
||||
|
||||
self.view.addSubview(self.searchLayoutClearButton)
|
||||
self.clippingNode.view.addSubview(self.searchLayoutClearButton)
|
||||
|
||||
self.textInputBackgroundNode.clipsToBounds = true
|
||||
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
|
||||
@ -1521,7 +1527,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
button.updateLayout(size: buttonSize)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: nextButtonTopRight.x - buttonSize.width, y: nextButtonTopRight.y + floor((minimalInputHeight - buttonSize.height) / 2.0)), size: buttonSize)
|
||||
if button.supernode == nil {
|
||||
self.addSubnode(button)
|
||||
self.clippingNode.addSubnode(button)
|
||||
button.frame = buttonFrame.offsetBy(dx: -additionalOffset, dy: 0.0)
|
||||
transition.updateFrame(layer: button.layer, frame: buttonFrame)
|
||||
if animatedTransition {
|
||||
@ -1645,6 +1651,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
prevPreviewInputPanelNode.sendButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
var clippingDelta: CGFloat = 0.0
|
||||
if case let .media(_, _, focused) = interfaceState.inputMode, focused {
|
||||
clippingDelta = -panelHeight
|
||||
}
|
||||
transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)))
|
||||
transition.updateSublayerTransformOffset(layer: self.clippingNode.layer, offset: CGPoint(x: 0.0, y: clippingDelta))
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
@ -2248,11 +2261,11 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
||||
|
||||
@objc func expandButtonPressed() {
|
||||
self.interfaceInteraction?.updateInputModeAndDismissedButtonKeyboardMessageId({ state in
|
||||
if case let .media(mode, expanded) = state.inputMode {
|
||||
if case let .media(mode, expanded, focused) = state.inputMode {
|
||||
if let _ = expanded {
|
||||
return (.media(mode: mode, expanded: nil), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId)
|
||||
return (.media(mode: mode, expanded: nil, focused: focused), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId)
|
||||
} else {
|
||||
return (.media(mode: mode, expanded: .content), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId)
|
||||
return (.media(mode: mode, expanded: .content, focused: focused), state.interfaceState.messageActionsState.closedButtonKeyboardMessageId)
|
||||
}
|
||||
} else {
|
||||
return (state.inputMode, state.interfaceState.messageActionsState.closedButtonKeyboardMessageId)
|
||||
|
@ -224,6 +224,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue {
|
||||
strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen(
|
||||
context: strongSelf.context,
|
||||
highlightedPackId: nil,
|
||||
sendSticker: {
|
||||
fileReference, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
@ -263,6 +264,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
}, navigateBackToStickers: {
|
||||
}, setGifMode: { _ in
|
||||
}, openSettings: {
|
||||
}, openTrending: { _ in
|
||||
}, dismissTrendingPacks: { _ in
|
||||
}, toggleSearch: { [weak self] value, searchMode, query in
|
||||
if let strongSelf = self {
|
||||
if let searchMode = searchMode, value {
|
||||
@ -287,8 +290,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction.updateInputMode { current in
|
||||
switch current {
|
||||
case let .media(mode, _):
|
||||
return .media(mode: mode, expanded: .search(searchMode))
|
||||
case let .media(mode, _, focused):
|
||||
return .media(mode: mode, expanded: .search(searchMode), focused: focused)
|
||||
default:
|
||||
return current
|
||||
}
|
||||
@ -299,8 +302,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
} else {
|
||||
strongSelf.controllerInteraction.updateInputMode { current in
|
||||
switch current {
|
||||
case let .media(mode, _):
|
||||
return .media(mode: mode, expanded: nil)
|
||||
case let .media(mode, _, focused):
|
||||
return .media(mode: mode, expanded: nil, focused: focused)
|
||||
default:
|
||||
return current
|
||||
}
|
||||
@ -336,6 +339,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.trending.rawValue {
|
||||
strongSelf.controllerInteraction.navigationController()?.pushViewController(FeaturedStickersScreen(
|
||||
context: strongSelf.context,
|
||||
highlightedPackId: nil,
|
||||
sendSticker: {
|
||||
fileReference, sourceNode, sourceRect in
|
||||
if let strongSelf = self {
|
||||
@ -375,6 +379,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
}, navigateBackToStickers: {
|
||||
}, setGifMode: { _ in
|
||||
}, openSettings: {
|
||||
}, openTrending: { _ in
|
||||
}, dismissTrendingPacks: { _ in
|
||||
}, toggleSearch: { [weak self] value, searchMode, query in
|
||||
if let strongSelf = self {
|
||||
if let searchMode = searchMode, value {
|
||||
@ -399,8 +405,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction.updateInputMode { current in
|
||||
switch current {
|
||||
case let .media(mode, _):
|
||||
return .media(mode: mode, expanded: .search(searchMode))
|
||||
case let .media(mode, _, focused):
|
||||
return .media(mode: mode, expanded: .search(searchMode), focused: focused)
|
||||
default:
|
||||
return current
|
||||
}
|
||||
@ -411,8 +417,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
} else {
|
||||
strongSelf.controllerInteraction.updateInputMode { current in
|
||||
switch current {
|
||||
case let .media(mode, _):
|
||||
return .media(mode: mode, expanded: nil)
|
||||
case let .media(mode, _, focused):
|
||||
return .media(mode: mode, expanded: nil, focused: focused)
|
||||
default:
|
||||
return current
|
||||
}
|
||||
@ -582,20 +588,9 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
for info in view.collectionInfos {
|
||||
installedPacks.insert(info.0)
|
||||
}
|
||||
|
||||
var hasUnreadTrending: Bool?
|
||||
for pack in trendingPacks {
|
||||
if hasUnreadTrending == nil {
|
||||
hasUnreadTrending = false
|
||||
}
|
||||
if pack.unread {
|
||||
hasUnreadTrending = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasUnreadTrending: nil, theme: theme, hasGifs: false, hasSettings: false)
|
||||
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasSearch: false, hasAccessories: false, strings: strings, theme: theme)
|
||||
|
||||
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, theme: theme, hasGifs: false, hasSettings: false)
|
||||
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: trendingPacks, hasSearch: false, hasAccessories: false, strings: strings, theme: theme)
|
||||
|
||||
let (previousPanelEntries, previousGridEntries) = previousStickerEntries.swap((panelEntries, gridEntries))
|
||||
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: stickersInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: stickersInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
||||
@ -629,8 +624,8 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
|
||||
installedPacks.insert(info.0)
|
||||
}
|
||||
|
||||
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasUnreadTrending: nil, theme: theme, hasGifs: false, hasSettings: false)
|
||||
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, hasSearch: false, hasAccessories: false, strings: strings, theme: theme)
|
||||
let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, theme: theme, hasGifs: false, hasSettings: false)
|
||||
let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: nil, recentStickers: nil, peerSpecificPack: nil, canInstallPeerSpecificPack: .none, trendingPacks: [], hasSearch: false, hasAccessories: false, strings: strings, theme: theme)
|
||||
|
||||
let (previousPanelEntries, previousGridEntries) = previousMaskEntries.swap((panelEntries, gridEntries))
|
||||
return (view, preparedChatMediaInputPanelEntryTransition(context: context, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: masksInputNodeInteraction, scrollToItem: nil), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: context.account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: masksInputNodeInteraction, trendingInteraction: trendingInteraction), previousGridEntries.isEmpty)
|
||||
|
@ -95,7 +95,7 @@ private final class FeaturedPackEntry: Identifiable, Comparable {
|
||||
|
||||
func item(account: Account, interaction: FeaturedInteraction, isOther: Bool) -> GridItem {
|
||||
let info = self.info
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: true, info: self.info, topItems: self.topItems, topSeparator: self.topSeparator, regularInsets: self.regularInsets, installed: self.installed, unread: self.unread, open: {
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: true, fillsRow: false, info: self.info, topItems: self.topItems, topSeparator: self.topSeparator, regularInsets: self.regularInsets, installed: self.installed, unread: self.unread, open: {
|
||||
interaction.openPack(info)
|
||||
}, install: {
|
||||
interaction.installPack(info, !self.installed)
|
||||
@ -153,16 +153,17 @@ private struct FeaturedTransition {
|
||||
let insertions: [GridNodeInsertItem]
|
||||
let updates: [GridNodeUpdateItem]
|
||||
let initial: Bool
|
||||
let scrollToItem: GridNodeScrollToItem?
|
||||
}
|
||||
|
||||
private func preparedTransition(from fromEntries: [FeaturedEntry], to toEntries: [FeaturedEntry], account: Account, interaction: FeaturedInteraction, initial: Bool) -> FeaturedTransition {
|
||||
private func preparedTransition(from fromEntries: [FeaturedEntry], to toEntries: [FeaturedEntry], account: Account, interaction: FeaturedInteraction, initial: Bool, scrollToItem: GridNodeScrollToItem?) -> FeaturedTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices
|
||||
let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction), previousIndex: $0.2) }
|
||||
let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction)) }
|
||||
|
||||
return FeaturedTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial)
|
||||
return FeaturedTransition(deletions: deletions, insertions: insertions, updates: updates, initial: initial, scrollToItem: scrollToItem)
|
||||
}
|
||||
|
||||
private func featuredScreenEntries(featuredEntries: [FeaturedStickerPackItem], installedPacks: Set<ItemCollectionId>, theme: PresentationTheme, strings: PresentationStrings, fixedUnread: Set<ItemCollectionId>, additionalPacks: [FeaturedStickerPackItem]) -> [FeaturedEntry] {
|
||||
@ -280,6 +281,10 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|
||||
},
|
||||
openSettings: {
|
||||
},
|
||||
openTrending: { _ in
|
||||
},
|
||||
dismissTrendingPacks: { _ in
|
||||
},
|
||||
toggleSearch: { _, _, _ in
|
||||
},
|
||||
openPeerSpecificSettings: {
|
||||
@ -360,6 +365,8 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|
||||
return (items, fixedUnread)
|
||||
}
|
||||
|
||||
let highlightedPackId = controller.highlightedPackId
|
||||
|
||||
self.disposable = (combineLatest(queue: .mainQueue(),
|
||||
mappedFeatured,
|
||||
self.additionalPacks.get(),
|
||||
@ -378,7 +385,20 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|
||||
let entries = featuredScreenEntries(featuredEntries: featuredEntries.0, installedPacks: installedPacks, theme: presentationData.theme, strings: presentationData.strings, fixedUnread: featuredEntries.1, additionalPacks: additionalPacks)
|
||||
let previous = previousEntries.swap(entries)
|
||||
|
||||
return preparedTransition(from: previous ?? [], to: entries, account: context.account, interaction: interaction, initial: previous == nil)
|
||||
var scrollToItem: GridNodeScrollToItem?
|
||||
let initial = previous == nil
|
||||
if initial, let highlightedPackId = highlightedPackId {
|
||||
var index = 0
|
||||
for entry in entries {
|
||||
if case let .pack(packEntry, _) = entry, packEntry.info.id == highlightedPackId {
|
||||
scrollToItem = GridNodeScrollToItem(index: index, position: .center(0.0), transition: .immediate, directionHint: .down, adjustForSection: false)
|
||||
break
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
return preparedTransition(from: previous ?? [], to: entries, account: context.account, interaction: interaction, initial: initial, scrollToItem: scrollToItem)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] transition in
|
||||
guard let strongSelf = self else {
|
||||
@ -682,7 +702,15 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|
||||
self.enqueuedTransitions.remove(at: 0)
|
||||
|
||||
let itemTransition: ContainedViewLayoutTransition = .immediate
|
||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: nil, updateLayout: nil, itemTransition: itemTransition, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, synchronousLoads: transition.initial), completion: { _ in })
|
||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: transition.scrollToItem, updateLayout: nil, itemTransition: itemTransition, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, synchronousLoads: transition.initial), completion: { [weak self] _ in
|
||||
if let strongSelf = self, transition.initial {
|
||||
strongSelf.gridNode.forEachItemNode({ itemNode in
|
||||
if let itemNode = itemNode as? StickerPaneSearchGlobalItemNode, itemNode.item?.info.id == strongSelf.controller?.highlightedPackId {
|
||||
itemNode.highlight()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -711,6 +739,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
|
||||
|
||||
final class FeaturedStickersScreen: ViewController {
|
||||
private let context: AccountContext
|
||||
fileprivate let highlightedPackId: ItemCollectionId?
|
||||
private let sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?
|
||||
|
||||
private var controllerNode: FeaturedStickersScreenNode {
|
||||
@ -727,8 +756,9 @@ final class FeaturedStickersScreen: ViewController {
|
||||
|
||||
fileprivate var searchNavigationNode: SearchNavigationContentNode?
|
||||
|
||||
public init(context: AccountContext, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? = nil) {
|
||||
public init(context: AccountContext, highlightedPackId: ItemCollectionId?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? = nil) {
|
||||
self.context = context
|
||||
self.highlightedPackId = highlightedPackId
|
||||
self.sendSticker = sendSticker
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -956,7 +986,7 @@ private enum FeaturedSearchEntry: Identifiable, Comparable {
|
||||
interaction.sendSticker(.standalone(media: stickerItem.file), node, rect)
|
||||
})
|
||||
case let .global(_, info, topItems, installed, topSeparator):
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
|
||||
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: true, fillsRow: true, info: info, topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
|
||||
interaction.open(info)
|
||||
}, install: {
|
||||
interaction.install(info, topItems, !installed)
|
||||
|
@ -202,7 +202,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}
|
||||
}
|
||||
|
||||
self.historyNode.endedInteractiveDragging = { [weak self] in
|
||||
self.historyNode.endedInteractiveDragging = { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -615,7 +615,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
|
||||
}
|
||||
}
|
||||
|
||||
self.historyNode.endedInteractiveDragging = { [weak self] in
|
||||
self.historyNode.endedInteractiveDragging = { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
@ -3156,7 +3156,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
break
|
||||
}
|
||||
}, sendFile: nil,
|
||||
sendSticker: { f, sourceNode, sourceRect in
|
||||
sendSticker: { _, _, _ in
|
||||
return false
|
||||
}, requestMessageActionUrlAuth: nil,
|
||||
joinVoiceChat: { peerId, invite, call in
|
||||
|
@ -78,6 +78,7 @@ final class StickerPaneSearchGlobalItem: GridItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let listAppearance: Bool
|
||||
let fillsRow: Bool
|
||||
let info: StickerPackCollectionInfo
|
||||
let topItems: [StickerPackItem]
|
||||
let topSeparator: Bool
|
||||
@ -102,14 +103,15 @@ final class StickerPaneSearchGlobalItem: GridItem {
|
||||
}
|
||||
}
|
||||
|
||||
return (128.0 + additionalHeight, !self.listAppearance)
|
||||
return (128.0 + additionalHeight, self.fillsRow)
|
||||
}
|
||||
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, info: StickerPackCollectionInfo, topItems: [StickerPackItem], topSeparator: Bool, regularInsets: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext, sectionTitle: String? = nil) {
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, fillsRow: Bool = true, info: StickerPackCollectionInfo, topItems: [StickerPackItem], topSeparator: Bool, regularInsets: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool, itemContext: StickerPaneSearchGlobalItemContext, sectionTitle: String? = nil) {
|
||||
self.account = account
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.listAppearance = listAppearance
|
||||
self.fillsRow = fillsRow
|
||||
self.info = info
|
||||
self.topItems = topItems
|
||||
self.topSeparator = topSeparator
|
||||
@ -155,8 +157,9 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
|
||||
private let uninstallButtonNode: HighlightTrackingButtonNode
|
||||
private var itemNodes: [TrendingTopItemNode]
|
||||
private let topSeparatorNode: ASDisplayNode
|
||||
private var highlightNode: ASDisplayNode?
|
||||
|
||||
private var item: StickerPaneSearchGlobalItem?
|
||||
var item: StickerPaneSearchGlobalItem?
|
||||
private var appliedItem: StickerPaneSearchGlobalItem?
|
||||
private let preloadDisposable = MetaDisposable()
|
||||
private let preloadedStickerPackThumbnailDisposable = MetaDisposable()
|
||||
@ -330,6 +333,27 @@ class StickerPaneSearchGlobalItemNode: GridItemNode {
|
||||
self.canPlayMedia = item.itemContext.canPlayMedia
|
||||
}
|
||||
|
||||
func highlight() {
|
||||
guard self.highlightNode == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
let highlightNode = ASDisplayNode()
|
||||
highlightNode.frame = self.bounds
|
||||
if let theme = self.item?.theme {
|
||||
highlightNode.backgroundColor = theme.list.itemCheckColors.fillColor.withAlphaComponent(0.08)
|
||||
}
|
||||
self.highlightNode = highlightNode
|
||||
self.insertSubnode(highlightNode, at: 0)
|
||||
|
||||
Queue.mainQueue().after(1.5) {
|
||||
self.highlightNode = nil
|
||||
highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { [weak highlightNode] _ in
|
||||
highlightNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
|
@ -0,0 +1,505 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import StickerResources
|
||||
import ItemListStickerPackItem
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import ShimmerEffect
|
||||
import MergeLists
|
||||
|
||||
private let boundingSize = CGSize(width: 41.0, height: 41.0)
|
||||
private let boundingImageSize = CGSize(width: 28.0, height: 28.0)
|
||||
|
||||
private struct Transition {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
}
|
||||
|
||||
private enum EntryStableId: Hashable {
|
||||
case stickerPack(Int64)
|
||||
}
|
||||
|
||||
private enum Entry: Comparable, Identifiable {
|
||||
case stickerPack(index: Int, info: StickerPackCollectionInfo, topItem: StickerPackItem?, unread: Bool, theme: PresentationTheme)
|
||||
|
||||
var stableId: EntryStableId {
|
||||
switch self {
|
||||
case let .stickerPack(_, info, _, _, _):
|
||||
return .stickerPack(info.id.id)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: Entry, rhs: Entry) -> Bool {
|
||||
switch lhs {
|
||||
case let .stickerPack(index, info, topItem, lhsUnread, lhsTheme):
|
||||
if case let .stickerPack(rhsIndex, rhsInfo, rhsTopItem, rhsUnread, rhsTheme) = rhs, index == rhsIndex, info == rhsInfo, topItem == rhsTopItem, lhsUnread == rhsUnread, lhsTheme === rhsTheme {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: Entry, rhs: Entry) -> Bool {
|
||||
switch lhs {
|
||||
case let .stickerPack(lhsIndex, lhsInfo, _, _, _):
|
||||
switch rhs {
|
||||
case let .stickerPack(rhsIndex, rhsInfo, _, _, _):
|
||||
if lhsIndex == rhsIndex {
|
||||
return lhsInfo.id.id < rhsInfo.id.id
|
||||
} else {
|
||||
return lhsIndex <= rhsIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func item(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .stickerPack(index, info, topItem, unread, theme):
|
||||
return FeaturedPackItem(account: account, inputNodeInteraction: inputNodeInteraction, collectionId: info.id, collectionInfo: info, stickerPackItem: topItem, unread: unread, index: index, theme: theme, selected: {
|
||||
inputNodeInteraction.openTrending(info.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func preparedEntryTransition(account: Account, from fromEntries: [Entry], to toEntries: [Entry], inputNodeInteraction: ChatMediaInputNodeInteraction) -> Transition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, inputNodeInteraction: inputNodeInteraction), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, inputNodeInteraction: inputNodeInteraction), directionHint: nil) }
|
||||
|
||||
return Transition(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
|
||||
private func panelEntries(featuredPacks: [FeaturedStickerPackItem], theme: PresentationTheme) -> [Entry] {
|
||||
var entries: [Entry] = []
|
||||
var index = 0
|
||||
for pack in featuredPacks {
|
||||
entries.append(.stickerPack(index: index, info: pack.info, topItem: pack.topItems.first, unread: pack.unread, theme: theme))
|
||||
index += 1
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
private final class FeaturedPackItem: ListViewItem {
|
||||
let account: Account
|
||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||
let collectionId: ItemCollectionId
|
||||
let collectionInfo: StickerPackCollectionInfo
|
||||
let stickerPackItem: StickerPackItem?
|
||||
let unread: Bool
|
||||
let selectedItem: () -> Void
|
||||
let index: Int
|
||||
let theme: PresentationTheme
|
||||
|
||||
var selectable: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
init(account: Account, inputNodeInteraction: ChatMediaInputNodeInteraction, collectionId: ItemCollectionId, collectionInfo: StickerPackCollectionInfo, stickerPackItem: StickerPackItem?, unread: Bool, index: Int, theme: PresentationTheme, selected: @escaping () -> Void) {
|
||||
self.account = account
|
||||
self.inputNodeInteraction = inputNodeInteraction
|
||||
self.collectionId = collectionId
|
||||
self.collectionInfo = collectionInfo
|
||||
self.stickerPackItem = stickerPackItem
|
||||
self.unread = unread
|
||||
self.index = index
|
||||
self.theme = theme
|
||||
self.selectedItem = selected
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = FeaturedPackItemNode()
|
||||
node.contentSize = boundingSize
|
||||
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||
node.inputNodeInteraction = self.inputNodeInteraction
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in
|
||||
node.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, unread: self.unread, theme: self.theme)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
completion(ListViewItemNodeLayout(contentSize: boundingSize, insets: ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)), { _ in
|
||||
(node() as? FeaturedPackItemNode)?.updateStickerPackItem(account: self.account, info: self.collectionInfo, item: self.stickerPackItem, collectionId: self.collectionId, unread: self.unread, theme: self.theme)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
self.selectedItem()
|
||||
}
|
||||
}
|
||||
|
||||
private final class FeaturedPackItemNode: ListViewItemNode {
|
||||
private let containerNode: ASDisplayNode
|
||||
private let imageNode: TransformImageNode
|
||||
private var animatedStickerNode: AnimatedStickerNode?
|
||||
private var placeholderNode: StickerShimmerEffectNode?
|
||||
private let unreadNode: ASImageNode
|
||||
|
||||
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||
var currentCollectionId: ItemCollectionId?
|
||||
private var currentThumbnailItem: StickerPackThumbnailItem?
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
private let stickerFetchedDisposable = MetaDisposable()
|
||||
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
self.visibilityStatus = self.visibility != .none
|
||||
}
|
||||
}
|
||||
|
||||
var visibilityStatus: Bool = false {
|
||||
didSet {
|
||||
if self.visibilityStatus != oldValue {
|
||||
let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false
|
||||
self.animatedStickerNode?.visibility = self.visibilityStatus && loopAnimatedStickers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.containerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||
|
||||
self.placeholderNode = StickerShimmerEffectNode()
|
||||
|
||||
self.unreadNode = ASImageNode()
|
||||
self.unreadNode.isLayerBacked = true
|
||||
self.unreadNode.displayWithoutProcessing = true
|
||||
self.unreadNode.displaysAsynchronously = false
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.containerNode.addSubnode(self.imageNode)
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.containerNode.addSubnode(placeholderNode)
|
||||
}
|
||||
self.containerNode.addSubnode(self.unreadNode)
|
||||
|
||||
var firstTime = true
|
||||
self.imageNode.imageUpdated = { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
strongSelf.removePlaceholder(animated: !firstTime)
|
||||
if firstTime {
|
||||
strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
firstTime = false
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stickerFetchedDisposable.dispose()
|
||||
}
|
||||
|
||||
private func removePlaceholder(animated: Bool) {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.placeholderNode = nil
|
||||
if !animated {
|
||||
placeholderNode.removeFromSupernode()
|
||||
} else {
|
||||
placeholderNode.alpha = 0.0
|
||||
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
|
||||
placeholderNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, unread: Bool, theme: PresentationTheme) {
|
||||
self.currentCollectionId = collectionId
|
||||
|
||||
if self.theme !== theme {
|
||||
self.theme = theme
|
||||
}
|
||||
|
||||
var thumbnailItem: StickerPackThumbnailItem?
|
||||
var resourceReference: MediaResourceReference?
|
||||
if let thumbnail = info.thumbnail {
|
||||
if info.flags.contains(.isAnimated) {
|
||||
thumbnailItem = .animated(thumbnail.resource, thumbnail.dimensions)
|
||||
resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)
|
||||
} else {
|
||||
thumbnailItem = .still(thumbnail)
|
||||
resourceReference = MediaResourceReference.stickerPackThumbnail(stickerPack: .id(id: info.id.id, accessHash: info.accessHash), resource: thumbnail.resource)
|
||||
}
|
||||
} else if let item = item {
|
||||
if item.file.isAnimatedSticker {
|
||||
thumbnailItem = .animated(item.file.resource, item.file.dimensions ?? PixelDimensions(width: 100, height: 100))
|
||||
resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: item.file.resource)
|
||||
} else if let dimensions = item.file.dimensions, let resource = chatMessageStickerResource(file: item.file, small: true) as? TelegramMediaResource {
|
||||
thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil))
|
||||
resourceReference = MediaResourceReference.media(media: .standalone(media: item.file), resource: resource)
|
||||
}
|
||||
}
|
||||
|
||||
var imageSize = boundingImageSize
|
||||
|
||||
if self.currentThumbnailItem != thumbnailItem {
|
||||
self.currentThumbnailItem = thumbnailItem
|
||||
let thumbnailDimensions = PixelDimensions(width: 512, height: 512)
|
||||
if let thumbnailItem = thumbnailItem {
|
||||
switch thumbnailItem {
|
||||
case let .still(representation):
|
||||
imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize)
|
||||
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
imageApply()
|
||||
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true))
|
||||
case let .animated(resource, _):
|
||||
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
imageApply()
|
||||
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: resource, animated: true, nilIfEmpty: true))
|
||||
|
||||
let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false
|
||||
self.imageNode.isHidden = loopAnimatedStickers
|
||||
|
||||
let animatedStickerNode: AnimatedStickerNode
|
||||
if let current = self.animatedStickerNode {
|
||||
animatedStickerNode = current
|
||||
} else {
|
||||
animatedStickerNode = AnimatedStickerNode()
|
||||
self.animatedStickerNode = animatedStickerNode
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
self.containerNode.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode)
|
||||
} else {
|
||||
self.containerNode.addSubnode(animatedStickerNode)
|
||||
}
|
||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 80, height: 80, mode: .direct(cachePathPrefix: nil))
|
||||
}
|
||||
animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers
|
||||
}
|
||||
if let resourceReference = resourceReference {
|
||||
self.stickerFetchedDisposable.set(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: resourceReference).start())
|
||||
}
|
||||
}
|
||||
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
let imageSize = boundingImageSize
|
||||
placeholderNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor.withAlphaComponent(1.0), foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.15), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), data: info.immediateThumbnailData, size: imageSize, imageSize: thumbnailDimensions.cgSize)
|
||||
}
|
||||
}
|
||||
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: boundingSize)
|
||||
|
||||
self.imageNode.bounds = CGRect(origin: CGPoint(), size: imageSize)
|
||||
self.imageNode.position = CGPoint(x: boundingSize.height / 2.0, y: boundingSize.width / 2.0)
|
||||
if let animatedStickerNode = self.animatedStickerNode {
|
||||
animatedStickerNode.frame = self.imageNode.frame
|
||||
animatedStickerNode.updateLayout(size: self.imageNode.frame.size)
|
||||
}
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
placeholderNode.bounds = CGRect(origin: CGPoint(), size: boundingImageSize)
|
||||
placeholderNode.position = self.imageNode.position
|
||||
}
|
||||
|
||||
let unreadImage = PresentationResourcesItemList.stickerUnreadDotImage(theme)
|
||||
if unread {
|
||||
self.unreadNode.isHidden = false
|
||||
} else {
|
||||
self.unreadNode.isHidden = true
|
||||
}
|
||||
if let image = unreadImage {
|
||||
self.unreadNode.image = image
|
||||
self.unreadNode.frame = CGRect(origin: CGPoint(x: 35.0, y: 4.0), size: image.size)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
if let placeholderNode = self.placeholderNode {
|
||||
placeholderNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class StickerPaneTrendingListGridItem: GridItem {
|
||||
let account: Account
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let trendingPacks: [FeaturedStickerPackItem]
|
||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||
let dismiss: (() -> Void)?
|
||||
|
||||
let section: GridSection? = nil
|
||||
let fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)?
|
||||
|
||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, trendingPacks: [FeaturedStickerPackItem], inputNodeInteraction: ChatMediaInputNodeInteraction, dismiss: (() -> Void)?) {
|
||||
self.account = account
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.trendingPacks = trendingPacks
|
||||
self.inputNodeInteraction = inputNodeInteraction
|
||||
self.dismiss = dismiss
|
||||
self.fillsRowWithDynamicHeight = { _ in
|
||||
return 70.0
|
||||
}
|
||||
}
|
||||
|
||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||
let node = StickerPaneTrendingListGridItemNode()
|
||||
node.setup(item: self)
|
||||
return node
|
||||
}
|
||||
|
||||
func update(node: GridItemNode) {
|
||||
guard let node = node as? StickerPaneTrendingListGridItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
node.setup(item: self)
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.medium(12.0)
|
||||
|
||||
class StickerPaneTrendingListGridItemNode: GridItemNode {
|
||||
private let titleNode: TextNode
|
||||
private let dismissButtonNode: HighlightTrackingButtonNode
|
||||
|
||||
private let listView: ListView
|
||||
|
||||
private var item: StickerPaneTrendingListGridItem?
|
||||
private var appliedItem: StickerPaneTrendingListGridItem?
|
||||
|
||||
override var isVisibleInGrid: Bool {
|
||||
didSet {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
private var currentEntries: [Entry] = []
|
||||
|
||||
override init() {
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.dismissButtonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.listView = ListView()
|
||||
self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
||||
self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.listView)
|
||||
self.addSubnode(self.dismissButtonNode)
|
||||
|
||||
self.dismissButtonNode.addTarget(self, action: #selector(self.dismissPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
private func enqueuePanelTransition(_ transition: Transition, firstTime: Bool) {
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
if firstTime {
|
||||
options.insert(.Synchronous)
|
||||
options.insert(.LowLatency)
|
||||
} else {
|
||||
options.insert(.AnimateInsertion)
|
||||
}
|
||||
|
||||
self.listView.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, scrollToItem: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
}
|
||||
|
||||
func setup(item: StickerPaneTrendingListGridItem) {
|
||||
self.item = item
|
||||
|
||||
let entries = panelEntries(featuredPacks: item.trendingPacks, theme: item.theme)
|
||||
let transition = preparedEntryTransition(account: item.account, from: self.currentEntries, to: entries, inputNodeInteraction: item.inputNodeInteraction)
|
||||
self.enqueuePanelTransition(transition, firstTime: self.currentEntries.isEmpty)
|
||||
self.currentEntries = entries
|
||||
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
let params = ListViewItemLayoutParams(width: self.bounds.size.width, leftInset: 0.0, rightInset: 0.0, availableHeight: self.bounds.size.height)
|
||||
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
|
||||
let currentItem = self.appliedItem
|
||||
self.appliedItem = item
|
||||
|
||||
let width = self.bounds.size.width
|
||||
|
||||
self.listView.bounds = CGRect(x: 0.0, y: 0.0, width: 41.0, height: width)
|
||||
self.listView.position = CGPoint(x: width / 2.0, y: 26.0 + 41.0 / 2.0)
|
||||
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: .immediate)
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: CGSize(width: 41.0, height: self.bounds.size.width), insets: UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0), duration: duration, curve: curve)
|
||||
|
||||
self.listView.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
if currentItem?.theme !== item.theme {
|
||||
self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme), for: [])
|
||||
}
|
||||
|
||||
let leftInset: CGFloat = 12.0
|
||||
let rightInset: CGFloat = 16.0
|
||||
let topOffset: CGFloat = 9.0
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.StickerPacksSettings_FeaturedPacks.uppercased(), font: titleFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
self.item = item
|
||||
|
||||
let _ = titleApply()
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset), size: titleLayout.size)
|
||||
let dismissButtonSize = CGSize(width: 12.0, height: 12.0)
|
||||
self.dismissButtonNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightInset - dismissButtonSize.width, y: topOffset - 1.0), size: dismissButtonSize)
|
||||
self.dismissButtonNode.isHidden = item.dismiss == nil
|
||||
self.titleNode.frame = titleFrame
|
||||
}
|
||||
|
||||
@objc private func dismissPressed() {
|
||||
if let item = self.item {
|
||||
item.dismiss?()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user