Various improvements

This commit is contained in:
Ilya Laktyushin 2020-06-19 00:33:54 +03:00
parent 03a84fda99
commit df70d5d718
87 changed files with 7865 additions and 6292 deletions

View File

@ -5545,6 +5545,68 @@ Any member of this group will be able to see messages in the channel.";
"PhotoEditor.BlurToolPortrait" = "Portrait";
"PhotoEditor.SelectCoverFrame" = "Choose a cover for your profile video";
"Conversation.PeerNearbyTitle" = "%@ is %@\naway from you";
"Conversation.PeerNearbyTitle" = "%@ is %@ away";
"Conversation.PeerNearbyText" = "Send a message or tap on the greeting below to show that you are ready to chat.";
"Conversation.PeerNearbyDistance" = "%@ is %@ away from you";
"Conversation.PeerNearbyDistance" = "%@ is %@ away";
"ProfilePhoto.MainPhoto" = "Main Photo";
"ProfilePhoto.SetMain" = "Set as Main Photo";
"Stats.GroupOverview" = "OVERVIEW";
"Stats.GroupMembers" = "Members";
"Stats.GroupMessages" = "Messages";
"Stats.GroupViewers" = "Viewing Members";
"Stats.GroupPosters" = "Posting Members";
"Stats.GroupGrowthTitle" = "GROWTH";
"Stats.GroupMembersTitle" = "GROUP MEMBERS";
"Stats.GroupNewMembersBySourceTitle" = "NEW MEMBERS BY SOURCE";
"Stats.GroupLanguagesTitle" = "MEMBERS' PRIMARY LANGUAGE";
"Stats.GroupMessagesTitle" = "MESSAGES";
"Stats.GroupActionsTitle" = "ACTIONS";
"Stats.GroupTopHoursTitle" = "TOP HOURS";
"Stats.GroupTopPostersTitle" = "TOP MEMBERS";
"Stats.GroupTopAdminsTitle" = "TOP ADMINS";
"Stats.GroupTopInvitersTitle" = "TOP INVITERS";
"Stats.GroupTopPosterMessages_0" = "%@ messages";
"Stats.GroupTopPosterMessages_1" = "%@ message";
"Stats.GroupTopPosterMessages_2" = "%@ messages";
"Stats.GroupTopPosterMessages_3_10" = "%@ messages";
"Stats.GroupTopPosterMessages_many" = "%@ messages";
"Stats.GroupTopPosterMessages_any" = "%@ messages";
"Stats.GroupTopPosterChars_0" = "%@ symbols per message";
"Stats.GroupTopPosterChars_1" = "%@ symbol per message";
"Stats.GroupTopPosterChars_2" = "%@ symbols per message";
"Stats.GroupTopPosterChars_3_10" = "%@ symbols per message";
"Stats.GroupTopPosterChars_many" = "%@ symbols per message";
"Stats.GroupTopPosterChars_any" = "%@ symbols per message";
"Stats.GroupTopAdminDeletions_0" = "%@ deletions";
"Stats.GroupTopAdminDeletions_1" = "%@ deletion";
"Stats.GroupTopAdminDeletions_2" = "%@ deletions";
"Stats.GroupTopAdminDeletions_3_10" = "%@ deletions";
"Stats.GroupTopAdminDeletions_many" = "%@ deletions";
"Stats.GroupTopAdminDeletions_any" = "%@ deletions";
"Stats.GroupTopAdminKicks_0" = "%@ kicks";
"Stats.GroupTopAdminKicks_1" = "%@ kick";
"Stats.GroupTopAdminKicks_2" = "%@ kicks";
"Stats.GroupTopAdminKicks_3_10" = "%@ kicks";
"Stats.GroupTopAdminKicks_many" = "%@ kicks";
"Stats.GroupTopAdminKicks_any" = "%@ kicks";
"Stats.GroupTopAdminBans_0" = "%@ bans";
"Stats.GroupTopAdminBans_1" = "%@ ban";
"Stats.GroupTopAdminBans_2" = "%@ bans";
"Stats.GroupTopAdminBans_3_10" = "%@ bans";
"Stats.GroupTopAdminBans_many" = "%@ bans";
"Stats.GroupTopAdminBans_any" = "%@ bans";
"Stats.GroupTopInviterInvites_0" = "%@ invitations";
"Stats.GroupTopInviterInvites_1" = "%@ invitation";
"Stats.GroupTopInviterInvites_2" = "%@ invitations";
"Stats.GroupTopInviterInvites_3_10" = "%@ invitations";
"Stats.GroupTopInviterInvites_many" = "%@ invitations";
"Stats.GroupTopInviterInvites_any" = "%@ invitations";

View File

@ -15,7 +15,7 @@ public protocol GridItem {
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode
func update(node: GridItemNode)
var aspectRatio: CGFloat { get }
var fillsRowWithHeight: CGFloat? { get }
var fillsRowWithHeight: (CGFloat, Bool)? { get }
var fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)? { get }
}
@ -24,7 +24,7 @@ public extension GridItem {
return 1.0
}
var fillsRowWithHeight: CGFloat? {
var fillsRowWithHeight: (CGFloat, Bool)? {
return nil
}

View File

@ -523,9 +523,11 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
}
previousSection = section
if let height = item.fillsRowWithHeight {
nextItemOrigin.x = 0.0
itemSize.width = gridLayout.size.width
if let (height, fillWidth) = item.fillsRowWithHeight {
if fillWidth {
nextItemOrigin.x = 0.0
itemSize.width = gridLayout.size.width
}
itemSize.height = height
} else if let fillsRowWithDynamicHeight = item.fillsRowWithDynamicHeight {
let height = fillsRowWithDynamicHeight(gridLayout.size.width)

View File

@ -11,9 +11,8 @@ public enum PointerStyle {
}
@available(iOSApplicationExtension 13.4, iOS 13.4, *)
private final class PointerInteractionImpl: NSObject {
//UIPointerInteractionDelegate {
// weak var pointerInteraction: UIPointerInteraction?
private final class PointerInteractionImpl: NSObject, UIPointerInteractionDelegate {
weak var pointerInteraction: UIPointerInteraction?
private let style: PointerStyle
@ -29,63 +28,63 @@ private final class PointerInteractionImpl: NSObject {
}
deinit {
// if let pointerInteraction = self.pointerInteraction {
// pointerInteraction.view?.removeInteraction(pointerInteraction)
// }
if let pointerInteraction = self.pointerInteraction {
pointerInteraction.view?.removeInteraction(pointerInteraction)
}
}
func setup(view: UIView) {
// let pointerInteraction = UIPointerInteraction(delegate: self)
// view.addInteraction(pointerInteraction)
// self.pointerInteraction = pointerInteraction
let pointerInteraction = UIPointerInteraction(delegate: self)
view.addInteraction(pointerInteraction)
self.pointerInteraction = pointerInteraction
}
// func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
// var pointerStyle: UIPointerStyle? = nil
// if let interactionView = interaction.view {
// let targetedPreview = UITargetedPreview(view: interactionView)
// switch self.style {
// case .default:
// let horizontalPadding: CGFloat = 10.0
// let verticalPadding: CGFloat = 4.0
// let minHeight: CGFloat = 40.0
// let size: CGSize = CGSize(width: targetedPreview.size.width + horizontalPadding * 2.0, height: max(minHeight, targetedPreview.size.height + verticalPadding * 2.0))
// pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .roundedRect(CGRect(origin: CGPoint(x: targetedPreview.view.center.x - size.width / 2.0, y: targetedPreview.view.center.y - size.height / 2.0), size: size), radius: UIPointerShape.defaultCornerRadius))
// case let .rectangle(size):
// pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .roundedRect(CGRect(origin: CGPoint(x: targetedPreview.view.center.x - size.width / 2.0, y: targetedPreview.view.center.y - size.height / 2.0), size: size), radius: UIPointerShape.defaultCornerRadius))
// case .circle:
// let maxSide = max(targetedPreview.size.width, targetedPreview.size.height)
// pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .path(UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: CGSize(width: maxSide, height: maxSide)))))
// case .caret:
// pointerStyle = UIPointerStyle(shape: .verticalBeam(length: 24.0), constrainedAxes: .vertical)
// case .lift:
// pointerStyle = UIPointerStyle(effect: .lift(targetedPreview))
// case .hover:
// pointerStyle = UIPointerStyle(effect: .hover(targetedPreview, preferredTintMode: .none, prefersShadow: false, prefersScaledContent: false))
// }
// }
// return pointerStyle
// }
//
// func pointerInteraction(_ interaction: UIPointerInteraction, willEnter region: UIPointerRegion, animator: UIPointerInteractionAnimating) {
// guard let _ = interaction.view else {
// return
// }
//
// animator.addAnimations {
// self.willEnter()
// }
// }
//
// func pointerInteraction(_ interaction: UIPointerInteraction, willExit region: UIPointerRegion, animator: UIPointerInteractionAnimating) {
// guard let _ = interaction.view else {
// return
// }
//
// animator.addAnimations {
// self.willExit()
// }
// }
func pointerInteraction(_ interaction: UIPointerInteraction, styleFor region: UIPointerRegion) -> UIPointerStyle? {
var pointerStyle: UIPointerStyle? = nil
if let interactionView = interaction.view {
let targetedPreview = UITargetedPreview(view: interactionView)
switch self.style {
case .default:
let horizontalPadding: CGFloat = 10.0
let verticalPadding: CGFloat = 4.0
let minHeight: CGFloat = 40.0
let size: CGSize = CGSize(width: targetedPreview.size.width + horizontalPadding * 2.0, height: max(minHeight, targetedPreview.size.height + verticalPadding * 2.0))
pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .roundedRect(CGRect(origin: CGPoint(x: targetedPreview.view.center.x - size.width / 2.0, y: targetedPreview.view.center.y - size.height / 2.0), size: size), radius: UIPointerShape.defaultCornerRadius))
case let .rectangle(size):
pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .roundedRect(CGRect(origin: CGPoint(x: targetedPreview.view.center.x - size.width / 2.0, y: targetedPreview.view.center.y - size.height / 2.0), size: size), radius: UIPointerShape.defaultCornerRadius))
case .circle:
let maxSide = max(targetedPreview.size.width, targetedPreview.size.height)
pointerStyle = UIPointerStyle(effect: .highlight(targetedPreview), shape: .path(UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: CGSize(width: maxSide, height: maxSide)))))
case .caret:
pointerStyle = UIPointerStyle(shape: .verticalBeam(length: 24.0), constrainedAxes: .vertical)
case .lift:
pointerStyle = UIPointerStyle(effect: .lift(targetedPreview))
case .hover:
pointerStyle = UIPointerStyle(effect: .hover(targetedPreview, preferredTintMode: .none, prefersShadow: false, prefersScaledContent: false))
}
}
return pointerStyle
}
func pointerInteraction(_ interaction: UIPointerInteraction, willEnter region: UIPointerRegion, animator: UIPointerInteractionAnimating) {
guard let _ = interaction.view else {
return
}
animator.addAnimations {
self.willEnter()
}
}
func pointerInteraction(_ interaction: UIPointerInteraction, willExit region: UIPointerRegion, animator: UIPointerInteractionAnimating) {
guard let _ = interaction.view else {
return
}
animator.addAnimations {
self.willExit()
}
}
}
public final class PointerInteraction {

View File

@ -479,20 +479,12 @@ public class Window1 {
} else {
screenHeight = strongSelf.windowLayout.size.height
}
/*if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 41.0 {
if abs(portraitLayoutSize.height - portraitScreenSize.height) > 41.0 || abs(portraitLayoutSize.width - portraitScreenSize.width) > 41.0 {
screenHeight = strongSelf.windowLayout.size.height
} else {
screenHeight = UIScreen.main.bounds.height
}
} else if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 39.0 {
} else {
if keyboardFrame.minX > 0.0 {
screenHeight = UIScreen.main.bounds.height
} else {
screenHeight = strongSelf.windowLayout.size.height
}*/
} else {
screenHeight = UIScreen.main.bounds.width
screenHeight = UIScreen.main.bounds.width
}
}
var keyboardHeight: CGFloat

View File

@ -1072,7 +1072,7 @@ public class GalleryController: ViewController, StandalonePresentableController
if let presentationArguments = self.presentationArguments as? GalleryControllerPresentationArguments, let transitionArguments = presentationArguments.transitionArguments(message.id, media) {
nodeAnimatesItself = true
if presentationArguments.animated {
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: {})
}
self._hiddenMedia.set(.single((message.id, media)))

View File

@ -81,7 +81,7 @@ open class GalleryItemNode: ASDisplayNode {
open func visibilityUpdated(isVisible: Bool) {
}
open func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
open func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
}
open func animateOut(to node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {

View File

@ -332,6 +332,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
for i in 0 ..< items.count {
insertItems.append(GalleryPagerInsertItem(index: i, item: items[i], previousIndex: previousIndexById[items[i].id]))
}
self.transaction(GalleryPagerTransaction(deleteItems: deleteItems, insertItems: insertItems, updateItems: updateItems, focusOnItem: centralItemIndex))
}
@ -394,7 +395,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
self.centralItemIndex = focusOnItem
}
self.updateItemNodes(transition: .immediate)
self.updateItemNodes(transition: .immediate, notify: transaction.focusOnItem != nil)
//print("visible indices after update \(self.itemNodes.map { $0.index })")
}
@ -474,7 +475,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
self.itemNodes.remove(at: internalIndex)
}
private func updateItemNodes(transition: ContainedViewLayoutTransition, forceOffsetReset: Bool = false, forceLoad: Bool = false) {
private func updateItemNodes(transition: ContainedViewLayoutTransition, forceOffsetReset: Bool = false, notify: Bool = false, forceLoad: Bool = false) {
if self.items.isEmpty || self.containerLayout == nil {
return
}
@ -496,7 +497,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
resetOffsetToCentralItem = true
}
var notifyCentralItemUpdated = forceOffsetReset
var notifyCentralItemUpdated = forceOffsetReset || notify
if let centralItemIndex = self.centralItemIndex, let centralItemNode = self.visibleItemNode(at: centralItemIndex) {
if centralItemIndex != 0 {

View File

@ -242,7 +242,7 @@ final class ChatAnimationGalleryItemNode: ZoomableContentGalleryItemNode {
}))
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.containerNode.view)
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.containerNode.view.superview)

View File

@ -311,7 +311,7 @@ class ChatDocumentGalleryItemNode: ZoomableContentGalleryItemNode, WKNavigationD
return self._title.get()
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.webView)
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.webView.superview)

View File

@ -246,7 +246,7 @@ class ChatExternalFileGalleryItemNode: GalleryItemNode {
return self._title.get()
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.containerNode.view)
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.containerNode.view.superview)

View File

@ -424,7 +424,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
}))
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
let contentNode = self.tilingNode ?? self.imageNode
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: contentNode.view)

View File

@ -882,7 +882,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
guard let videoNode = self.videoNode else {
return
}

View File

@ -359,7 +359,7 @@ public final class SecretMediaPreviewController: ViewController {
centralItemNode.activateAsInitial()
if presentationArguments.animated {
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: {})
}
self._hiddenMedia.set(.single((message.id, media)))

View File

@ -168,7 +168,7 @@ final class InstantImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.footerContentNode.setShareMedia(fileReference.abstract)
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.imageNode.view)
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.imageNode.view.superview)
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)

View File

@ -407,7 +407,7 @@ public class InstantPageGalleryController: ViewController, StandalonePresentable
if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) {
nodeAnimatesItself = true
centralItemNode.activateAsInitial()
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: {})
self._hiddenMedia.set(.single(self.entries[centralItemNode.index]))
}

View File

@ -9,6 +9,7 @@
@class TGSuggestionContext;
@class TGViewController;
@class TGAttachmentCameraView;
@class TGVideoEditAdjustments;
@protocol TGModernGalleryTransitionHostScrollView;
@protocol TGPhotoPaintStickersContext;
@ -44,6 +45,7 @@
@property (nonatomic, copy) void (^cameraPressed)(TGAttachmentCameraView *cameraView);
@property (nonatomic, copy) void (^sendPressed)(TGMediaAsset *currentItem, bool asFiles, bool silentPosting, int32_t scheduleTime);
@property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image);
@property (nonatomic, copy) void (^avatarVideoCompletionBlock)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments);
@property (nonatomic, copy) void (^editorOpened)(void);
@property (nonatomic, copy) void (^editorClosed)(void);

View File

@ -8,6 +8,7 @@
@class TGMediaAssetsPickerController;
@class TGViewController;
@class TGVideoEditAdjustments;
@protocol TGPhotoPaintStickersContext;
@ -74,6 +75,7 @@ typedef enum
@property (nonatomic, copy) NSDictionary *(^descriptionGenerator)(id, NSString *, NSArray *, NSString *);
@property (nonatomic, copy) void (^avatarCompletionBlock)(UIImage *image);
@property (nonatomic, copy) void (^completionBlock)(NSArray *signals, bool silentPosting, int32_t scheduleTime);
@property (nonatomic, copy) void (^avatarVideoCompletionBlock)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments);
@property (nonatomic, copy) void (^singleCompletionBlock)(id<TGMediaEditableItem> item, TGMediaEditingContext *editingContext);
@property (nonatomic, copy) void (^dismissalBlock)(void);
@property (nonatomic, copy) void (^selectionBlock)(TGMediaAsset *asset, UIImage *);
@ -91,6 +93,7 @@ typedef enum
- (NSArray *)resultSignalsWithCurrentItem:(TGMediaAsset *)currentItem descriptionGenerator:(id (^)(id, NSString *, NSArray *, NSString *))descriptionGenerator;
- (void)completeWithAvatarImage:(UIImage *)image;
- (void)completeWithAvatarVideo:(NSURL *)url adjustments:(TGVideoEditAdjustments *)adjustments image:(UIImage *)image;
- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem silentPosting:(bool)silentPosting scheduleTime:(int32_t)scheduleTime;
- (void)dismiss;

View File

@ -4,6 +4,7 @@
@class TGViewController;
@class TGMenuSheetController;
@class TGMediaAssetsController;
@class TGVideoEditAdjustments;
@protocol TGPhotoPaintStickersContext;
@ -12,6 +13,7 @@ typedef void (^TGMediaAvatarPresentImpl)(id<LegacyComponentsContext>, void (^)(U
@interface TGMediaAvatarMenuMixin : NSObject
@property (nonatomic, copy) void (^didFinishWithImage)(UIImage *image);
@property (nonatomic, copy) void (^didFinishWithVideo)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments);
@property (nonatomic, copy) void (^didFinishWithDelete)(void);
@property (nonatomic, copy) void (^didFinishWithView)(void);
@property (nonatomic, copy) void (^didDismiss)(void);

View File

@ -50,6 +50,7 @@ typedef enum {
@property (nonatomic, copy) void (^willFinishEditing)(id<TGMediaEditAdjustments> adjustments, id temporaryRep, bool hasChanges);
@property (nonatomic, copy) void (^didFinishRenderingFullSizeImage)(UIImage *fullSizeImage);
@property (nonatomic, copy) void (^didFinishEditing)(id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges);
@property (nonatomic, copy) void (^didFinishEditingVideo)(NSURL *url, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges);
@property (nonatomic, assign) bool skipInitialTransition;
@property (nonatomic, assign) bool dontHideStatusBar;

View File

@ -24,9 +24,6 @@
@property (nonatomic, copy) UIView *(^beginTransitionOut)(CGRect *referenceFrame, UIView **parentView);
@property (nonatomic, copy) void(^finishedTransitionOut)(void);
@property (nonatomic, copy) void (^beginItemTransitionIn)(void);
@property (nonatomic, copy) void (^beginItemTransitionOut)(void);
@property (nonatomic, copy) void (^valuesChanged)(void);
@property (nonatomic, copy) void (^tabsChanged)(void);
@ -66,6 +63,9 @@
- (bool)isDismissAllowed;
- (UIInterfaceOrientation)effectiveOrientation;
- (UIInterfaceOrientation)effectiveOrientation:(UIInterfaceOrientation)orientation;
- (void)_updateTabs;
- (TGPhotoEditorTab)activeTab;
- (TGPhotoEditorTab)highlightedTabs;

View File

@ -906,7 +906,23 @@ const NSUInteger TGAttachmentDisplayedAssetLimit = 500;
[strongController dismissAnimated:true];
};
controller.didFinishEditingVideo = ^(NSURL *url, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) {
if (!hasChanges)
return;
__strong TGAttachmentCarouselItemView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
__strong TGPhotoEditorController *strongController = weakController;
if (strongController == nil)
return;
if (strongSelf.avatarVideoCompletionBlock != nil)
strongSelf.avatarVideoCompletionBlock(resultImage, url, adjustments);
[strongController dismissAnimated:true];
};
controller.requestThumbnailImage = ^(id<TGMediaEditableItem> editableItem)
{
return [editableItem thumbnailImageSignal];

View File

@ -566,6 +566,12 @@
self.avatarCompletionBlock(image);
}
- (void)completeWithAvatarVideo:(NSURL *)url adjustments:(TGVideoEditAdjustments *)adjustments image:(UIImage *)image
{
if (self.avatarVideoCompletionBlock != nil)
self.avatarVideoCompletionBlock(image, url, adjustments);
}
- (void)completeWithCurrentItem:(TGMediaAsset *)currentItem silentPosting:(bool)silentPosting scheduleTime:(int32_t)scheduleTime
{
if (self.completionBlock != nil)

View File

@ -417,7 +417,16 @@
[(TGMediaAssetsController *)strongSelf.navigationController completeWithAvatarImage:resultImage];
};
controller.didFinishEditingVideo = ^(NSURL *url, id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage, bool hasChanges) {
if (!hasChanges)
return;
__strong TGMediaAssetsPickerController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[(TGMediaAssetsController *)strongSelf.navigationController completeWithAvatarVideo:url adjustments:adjustments image:resultImage];
};
controller.requestThumbnailImage = ^(id<TGMediaEditableItem> editableItem)
{
return [editableItem thumbnailImageSignal];

View File

@ -127,9 +127,23 @@
[strongController dismissAnimated:false];
};
carouselItem.avatarVideoCompletionBlock = ^(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments) {
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
if (strongSelf == nil)
return;
__strong TGMenuSheetController *strongController = weakController;
if (strongController == nil)
return;
if (strongSelf.didFinishWithVideo != nil)
strongSelf.didFinishWithVideo(image, url, adjustments);
[strongController dismissAnimated:false];
};
[itemViews addObject:carouselItem];
TGMenuSheetButtonItemView *galleryItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"Common.ChoosePhoto") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
TGMenuSheetButtonItemView *galleryItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"AttachmentMenu.PhotoOrVideo") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
{
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
if (strongSelf == nil)
@ -464,6 +478,18 @@
if (strongController != nil && strongController.dismissalBlock != nil)
strongController.dismissalBlock();
};
controller.avatarVideoCompletionBlock = ^(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments) {
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (strongSelf.didFinishWithVideo != nil)
strongSelf.didFinishWithVideo(image, url, adjustments);
__strong TGMediaAssetsController *strongController = weakController;
if (strongController != nil && strongController.dismissalBlock != nil)
strongController.dismissalBlock();
};
if (strongSelf.requestSearchController != nil) {
controller.requestSearchController = ^
{

View File

@ -84,7 +84,6 @@
NSTimer *_tooltipTimer;
TGMenuContainerView *_tooltipContainerView;
TGTooltipContainerView *_groupingTooltipContainerView;
SMetaDisposable *_tooltipDismissDisposable;
void (^_closePressed)();
@ -236,9 +235,7 @@
if (selectableItem != nil)
[strongSelf->_checkButton setNumber:[strongSelf->_selectionContext indexOfItem:selectableItem]];
bool groupingButtonVisible = [strongSelf updateGroupingButtonVisibility];
if (!strongSelf->_groupButton.hidden && groupingButtonVisible && [strongSelf shouldDisplayGroupingTooltip] && strongSelf->_selectionContext.grouping)
[strongSelf setupGroupingTooltip:[strongSelf->_groupButton.superview convertRect:strongSelf->_groupButton.frame toView:strongSelf]];
[strongSelf updateGroupingButtonVisibility];
}];
if (_selectionContext.allowGrouping)
@ -510,8 +507,7 @@
[_currentItemView setSafeAreaInset:[self localSafeAreaInset]];
CGFloat screenSide = MAX(TGScreenSize().width, TGScreenSize().height);
UIEdgeInsets screenEdges = UIEdgeInsetsMake((screenSide - self.frame.size.height) / 2, (screenSide - self.frame.size.width) / 2, (screenSide + self.frame.size.height) / 2, (screenSide + self.frame.size.width) / 2);
UIEdgeInsets screenEdges = [self screenEdges];
__weak TGMediaPickerGalleryInterfaceView *weakSelf = self;
@ -714,7 +710,6 @@
[_photoCounterButton setSelected:!_photoCounterButton.selected animated:true];
[_selectedPhotosView setHidden:!_photoCounterButton.selected animated:true];
[_groupButton setHidden:!_photoCounterButton.selected animated:true];
//[_cameraButton setHidden:!_photoCounterButton.selected animated:true];
void (^changeBlock)(void) = ^
{
@ -725,14 +720,7 @@
else
[UIView animateWithDuration:0.3 delay:0.0 options:7 << 16 animations:changeBlock completion:nil];
bool groupingButtonVisible = [self updateGroupingButtonVisibility];
if (selected && _groupButton != nil && groupingButtonVisible && _selectionContext.grouping && [self shouldDisplayGroupingTooltip])
{
TGDispatchAfter(0.5, dispatch_get_main_queue(), ^
{
[self setupGroupingTooltip:[_groupButton.superview convertRect:_groupButton.frame toView:self]];
});
}
[self updateGroupingButtonVisibility];
}
- (void)updateEditorButtonsForItem:(id<TGModernGalleryItem>)item animated:(bool)animated
@ -973,35 +961,6 @@
#pragma mark - Grouping Tooltip
- (bool)shouldDisplayGroupingTooltip
{
return ![[[NSUserDefaults standardUserDefaults] objectForKey:@"TG_displayedGroupTooltip_v0"] boolValue];
}
- (void)setupGroupingTooltip:(CGRect)rect
{
if (_tooltipContainerView != nil)
return;
rect = CGRectOffset(rect, 0.0f, 3.0f);
_tooltipTimer = [TGTimerTarget scheduledMainThreadTimerWithTarget:self action:@selector(tooltipTimerTick) interval:3.0 repeat:false];
_tooltipContainerView = [[TGMenuContainerView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height)];
[self addSubview:_tooltipContainerView];
NSMutableArray *actions = [[NSMutableArray alloc] init];
[actions addObject:[[NSDictionary alloc] initWithObjectsAndKeys:TGLocalized(@"MediaPicker.TapToUngroupDescription"), @"title", nil]];
[_tooltipContainerView.menuView setButtonsAndActions:actions watcherHandle:_actionHandle];
[_tooltipContainerView.menuView sizeToFit];
_tooltipContainerView.menuView.buttonHighlightDisabled = true;
[_tooltipContainerView showMenuFromRect:rect animated:false];
[[NSUserDefaults standardUserDefaults] setObject:@true forKey:@"TG_displayedGroupTooltip_v0"];
}
- (void)actionStageActionRequested:(NSString *)action options:(id)__unused options
{
if ([action isEqualToString:@"menuAction"])
@ -1013,41 +972,6 @@
}
}
- (void)showGroupingTooltip:(bool)grouped duration:(NSTimeInterval)duration
{
NSString *tooltipText = TGLocalized(grouped ? @"MediaPicker.GroupDescription" : @"MediaPicker.UngroupDescription");
if (_groupingTooltipContainerView.isShowingTooltip && _groupingTooltipContainerView.tooltipView.sourceView == _groupButton)
{
[_groupingTooltipContainerView.tooltipView setText:tooltipText animated:true];
}
else
{
[_tooltipContainerView removeFromSuperview];
[_groupingTooltipContainerView removeFromSuperview];
_groupingTooltipContainerView = [[TGTooltipContainerView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height)];
[self addSubview:_groupingTooltipContainerView];
[_groupingTooltipContainerView.tooltipView setText:tooltipText animated:false];
_groupingTooltipContainerView.tooltipView.sourceView = _groupButton;
CGRect recordButtonFrame = [_groupButton.superview convertRect:_groupButton.frame toView:_groupingTooltipContainerView];
recordButtonFrame.origin.y += 3.0f;
[_groupingTooltipContainerView showTooltipFromRect:recordButtonFrame animated:false];
}
if (_tooltipDismissDisposable == nil)
_tooltipDismissDisposable = [[SMetaDisposable alloc] init];
__weak TGTooltipContainerView *weakContainerView = _groupingTooltipContainerView;
[_tooltipDismissDisposable setDisposable:[[[SSignal complete] delay:duration onQueue:[SQueue mainQueue]] startWithNext:nil completed:^{
__strong TGTooltipContainerView *strongContainerView = weakContainerView;
if (strongContainerView != nil)
[strongContainerView hideTooltip];
}]];
}
#pragma mark -
- (void)updateSelectionInterface:(NSUInteger)selectedCount counterVisible:(bool)counterVisible animated:(bool)animated
@ -1286,8 +1210,6 @@
- (void)toggleGrouping
{
[_selectionContext toggleGrouping];
[self showGroupingTooltip:_selectionContext.grouping duration:2.5];
}
- (CGRect)itemFooterViewFrameForSize:(CGSize)size

View File

@ -411,9 +411,6 @@
CGRect iconViewFrame = CGRectMake(12, 188 + _safeAreaInset.top, 40, 40);
[_tooltipContainerView showMenuFromRect:iconViewFrame animated:false];
}
if (self.item.selectionContext != nil)
[self.item.selectionContext setItem:self.item.selectableMediaItem selected:true];
}
}

View File

@ -1470,9 +1470,6 @@
[_tooltipContainerView showMenuFromRect:iconViewFrame animated:false];
}
if (self.item.selectionContext != nil)
[self.item.selectionContext setItem:self.item.selectableMediaItem selected:true];
if (!self.isPlaying)
[self play];
}

View File

@ -254,7 +254,11 @@ const CGFloat TGPhotoAvatarCropButtonsWrapperSize = 61.0f;
- (void)_finishedTransitionInWithView:(UIView *)transitionView
{
[transitionView removeFromSuperview];
if ([transitionView isKindOfClass:[TGPhotoEditorPreviewView class]]) {
} else {
[transitionView removeFromSuperview];
}
_buttonsWrapperView.alpha = 1.0f;
[_cropView transitionInFinishedFromCamera:(self.fromCamera && self.initialAppearance)];
@ -313,7 +317,6 @@ const CGFloat TGPhotoAvatarCropButtonsWrapperSize = 61.0f;
return;
}
UIImage *croppedImage = [_cropView croppedImageWithMaxSize:TGPhotoEditorScreenImageMaxSize()];
[photoEditor setImage:croppedImage forCropRect:_cropView.cropRect cropRotation:0.0f cropOrientation:_cropView.cropOrientation cropMirrored:_cropView.cropMirrored fullSize:false];
@ -335,9 +338,7 @@ const CGFloat TGPhotoAvatarCropButtonsWrapperSize = 61.0f;
});
}
UIInterfaceOrientation orientation = [[LegacyComponentsGlobals provider] applicationStatusBarOrientation];
if ([self inFormSheet] || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
orientation = UIInterfaceOrientationPortrait;
UIInterfaceOrientation orientation = self.effectiveOrientation;
bool hasOnScreenNavigation = false;
if (iosMajorVersion() >= 11)
@ -466,15 +467,12 @@ const CGFloat TGPhotoAvatarCropButtonsWrapperSize = 61.0f;
- (CGRect)_targetFrameForTransitionInFromFrame:(CGRect)fromFrame
{
CGSize referenceSize = [self referenceViewSize];
UIInterfaceOrientation orientation = self.interfaceOrientation;
UIInterfaceOrientation orientation = self.effectiveOrientation;
bool hasOnScreenNavigation = false;
if (iosMajorVersion() >= 11)
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || self.context.safeAreaInset.bottom > FLT_EPSILON;
if ([self inFormSheet] || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
orientation = UIInterfaceOrientationPortrait;
CGRect containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:CGRectMake(0, 0, referenceSize.width, referenceSize.height) toolbarLandscapeSize:self.toolbarLandscapeSize orientation:orientation panelSize:0.0f hasOnScreenNavigation:hasOnScreenNavigation];
CGRect targetFrame = CGRectZero;
@ -548,10 +546,11 @@ const CGFloat TGPhotoAvatarCropButtonsWrapperSize = 61.0f;
{
if ([self inFormSheet] || [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
{
orientation = UIInterfaceOrientationPortrait;
_resetButton.hidden = true;
}
orientation = [self effectiveOrientation:orientation];
CGSize referenceSize = [self referenceViewSize];
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)

View File

@ -11,4 +11,6 @@
- (void)setScrubberPosition:(NSTimeInterval)position reset:(bool)reset;
- (void)setScrubberPlaying:(bool)value;
- (NSTimeInterval)coverPosition;
@end

View File

@ -62,6 +62,11 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
return self;
}
- (void)dealloc
{
[_thumbnailsDisposable dispose];
}
- (void)loadView
{
[super loadView];
@ -69,31 +74,33 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
[self.view addSubview:_previewView];
_wrapperView = [[TGPhotoEditorSparseView alloc] initWithFrame:CGRectZero];
[self.view addSubview:_wrapperView];
_portraitToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero];
_portraitToolsWrapperView.alpha = 0.0f;
[_wrapperView addSubview:_portraitToolsWrapperView];
_portraitWrapperBackgroundView = [[UIView alloc] initWithFrame:_portraitToolsWrapperView.bounds];
_portraitWrapperBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_portraitWrapperBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor];
_portraitWrapperBackgroundView.userInteractionEnabled = false;
[_portraitToolsWrapperView addSubview:_portraitWrapperBackgroundView];
if (self.item.isVideo) {
_wrapperView = [[TGPhotoEditorSparseView alloc] initWithFrame:CGRectZero];
[self.view addSubview:_wrapperView];
_portraitToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero];
_portraitToolsWrapperView.alpha = 0.0f;
[_wrapperView addSubview:_portraitToolsWrapperView];
_portraitWrapperBackgroundView = [[UIView alloc] initWithFrame:_portraitToolsWrapperView.bounds];
_portraitWrapperBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_portraitWrapperBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor];
_portraitWrapperBackgroundView.userInteractionEnabled = false;
[_portraitToolsWrapperView addSubview:_portraitWrapperBackgroundView];
_landscapeToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero];
_landscapeToolsWrapperView.alpha = 0.0f;
[_wrapperView addSubview:_landscapeToolsWrapperView];
_landscapeWrapperBackgroundView = [[UIView alloc] initWithFrame:_landscapeToolsWrapperView.bounds];
_landscapeWrapperBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_landscapeWrapperBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor];
_landscapeWrapperBackgroundView.userInteractionEnabled = false;
[_landscapeToolsWrapperView addSubview:_landscapeWrapperBackgroundView];
_videoAreaView = [[UIView alloc] init];
[self.view insertSubview:_videoAreaView belowSubview:_wrapperView];
_landscapeToolsWrapperView = [[UIView alloc] initWithFrame:CGRectZero];
_landscapeToolsWrapperView.alpha = 0.0f;
[_wrapperView addSubview:_landscapeToolsWrapperView];
_landscapeWrapperBackgroundView = [[UIView alloc] initWithFrame:_landscapeToolsWrapperView.bounds];
_landscapeWrapperBackgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_landscapeWrapperBackgroundView.backgroundColor = [TGPhotoEditorInterfaceAssets toolbarTransparentBackgroundColor];
_landscapeWrapperBackgroundView.userInteractionEnabled = false;
[_landscapeToolsWrapperView addSubview:_landscapeWrapperBackgroundView];
_videoAreaView = [[UIView alloc] init];
[self.view insertSubview:_videoAreaView belowSubview:_wrapperView];
}
_flashView = [[UIView alloc] init];
_flashView.alpha = 0.0;
@ -121,11 +128,6 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
[_portraitToolsWrapperView addSubview:_coverLabel];
}
- (void)dealloc
{
[_thumbnailsDisposable dispose];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
@ -316,7 +318,11 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
{
_appeared = true;
[transitionView removeFromSuperview];
if ([transitionView isKindOfClass:[TGPhotoEditorPreviewView class]]) {
[self.view insertSubview:transitionView atIndex:0];
} else {
[transitionView removeFromSuperview];
}
TGPhotoEditorPreviewView *previewView = _previewView;
previewView.hidden = false;
@ -572,6 +578,7 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
if (!_dismissing)
[self updateToolViews];
dispatch_async(dispatch_get_main_queue(), ^{
[_scrubberView reloadThumbnails];
});
@ -869,4 +876,8 @@ const CGFloat TGPhotoAvatarPreviewLandscapePanelSize = TGPhotoAvatarPreviewPanel
[_scrubberView setIsPlaying:value];
}
- (NSTimeInterval)coverPosition {
return _scrubberView.dotValue;
}
@end

View File

@ -345,6 +345,10 @@
CGFloat keyboardHeight = (keyboardFrame.size.height <= FLT_EPSILON || keyboardFrame.size.width <= FLT_EPSILON) ? 0.0f : (parentView.frame.size.height - keyboardFrame.origin.y);
keyboardHeight = MAX(keyboardHeight, 0.0f);
if (CGRectGetMaxY(keyboardFrame) < [UIScreen mainScreen].bounds.size.height || keyboardHeight < 20.0f) {
keyboardHeight = 0.0f;
}
_keyboardHeight = keyboardHeight;
if (!UIInterfaceOrientationIsPortrait([[LegacyComponentsGlobals provider] applicationStatusBarOrientation]) && !TGIsPad())

View File

@ -130,7 +130,7 @@
_screenImage = screenImage;
_queue = [[SQueue alloc] init];
_photoEditor = [[PGPhotoEditor alloc] initWithOriginalSize:_item.originalSize adjustments:adjustments forVideo:(intent == TGPhotoEditorControllerVideoIntent || intent == TGPhotoEditorControllerAvatarIntent) enableStickers:(intent & TGPhotoEditorControllerSignupAvatarIntent) == 0];
_photoEditor = [[PGPhotoEditor alloc] initWithOriginalSize:_item.originalSize adjustments:adjustments forVideo:item.isVideo enableStickers:(intent & TGPhotoEditorControllerSignupAvatarIntent) == 0];
if ([self presentedForAvatarCreation])
{
CGFloat shortSide = MIN(_item.originalSize.width, _item.originalSize.height);
@ -151,7 +151,7 @@
- (void)dealloc
{
[_positionTimer invalidate];
[self stopVideoPlayback:true];
[_actionHandle reset];
[_faceDetectorDisposable dispose];
}
@ -291,15 +291,11 @@
if (_intent & TGPhotoEditorControllerWebIntent)
[self updateDoneButtonEnabled:false animated:false];
UIInterfaceOrientation orientation = self.interfaceOrientation;
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
orientation = UIInterfaceOrientationPortrait;
bool hasOnScreenNavigation = false;
if (iosMajorVersion() >= 11)
hasOnScreenNavigation = (self.viewLoaded && self.view.safeAreaInsets.bottom > FLT_EPSILON) || _context.safeAreaInset.bottom > FLT_EPSILON;
CGRect containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:TGPhotoEditorToolbarSize orientation:orientation panelSize:TGPhotoEditorPanelSize hasOnScreenNavigation:hasOnScreenNavigation];
CGRect containerFrame = [TGPhotoEditorTabController photoContainerFrameForParentViewFrame:self.view.frame toolbarLandscapeSize:TGPhotoEditorToolbarSize orientation:self.effectiveOrientation panelSize:TGPhotoEditorPanelSize hasOnScreenNavigation:hasOnScreenNavigation];
CGSize fittedSize = TGScaleToSize(_photoEditor.rotatedCropSize, containerFrame.size);
_previewView = [[TGPhotoEditorPreviewView alloc] initWithFrame:CGRectMake(0, 0, fittedSize.width, fittedSize.height)];
@ -671,7 +667,7 @@
#pragma mark -
- (void)createEditedImageWithEditorValues:(PGPhotoEditorValues *)editorValues createThumbnail:(bool)createThumbnail saveOnly:(bool)saveOnly completion:(void (^)(UIImage *))completion
- (void)createEditedImageWithEditorValues:(id<TGMediaEditAdjustments>)editorValues createThumbnail:(bool)createThumbnail saveOnly:(bool)saveOnly completion:(void (^)(UIImage *))completion
{
if (!saveOnly)
{
@ -1006,7 +1002,7 @@
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (isInitialAppearance && strongSelf.finishedTransitionIn != nil)
strongSelf.finishedTransitionIn();
@ -1050,7 +1046,7 @@
*referenceFrame = transitionReferenceFrame;
*noTransitionView = transitionNoTransitionView;
*parentView = transitionParentView;
if (strongSelf != nil)
{
UIView *backgroundView = nil;
@ -1153,10 +1149,7 @@
}
else
{
TGPhotoCropController *cropController = [[TGPhotoCropController alloc] initWithContext:_context photoEditor:_photoEditor
previewView:_previewView
metadata:self.metadata
forVideo:(_intent == TGPhotoEditorControllerVideoIntent)];
TGPhotoCropController *cropController = [[TGPhotoCropController alloc] initWithContext:_context photoEditor:_photoEditor previewView:_previewView metadata:self.metadata forVideo:(_intent == TGPhotoEditorControllerVideoIntent)];
if (snapshotView != nil)
[cropController setSnapshotView:snapshotView];
else if (snapshotImage != nil)
@ -1267,7 +1260,7 @@
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
if (isInitialAppearance && strongSelf.finishedTransitionIn != nil)
strongSelf.finishedTransitionIn();
@ -1322,7 +1315,7 @@
*referenceFrame = transitionReferenceFrame;
*parentView = transitionParentView;
*noTransitionView = transitionNoTransitionView;
return transitionReferenceView;
};
previewController.finishedTransitionIn = ^
@ -1379,53 +1372,14 @@
[self addChildViewController:_currentTabController];
[_containerView addSubview:_currentTabController.view];
if ([currentController isKindOfClass:[TGPhotoAvatarCropController class]])
if (currentController != nil)
[_currentTabController viewWillAppear:true];
if (currentController != nil)
[_currentTabController viewDidAppear:true];
_currentTabController.view.frame = _containerView.bounds;
_currentTabController.beginItemTransitionIn = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
UIInterfaceOrientation orientation = strongSelf.interfaceOrientation;
if ([strongSelf inFormSheet])
orientation = UIInterfaceOrientationPortrait;
if (UIInterfaceOrientationIsPortrait(orientation))
{
[strongSelf->_portraitToolbarView transitionOutAnimated:true];
[strongSelf->_landscapeToolbarView transitionOutAnimated:false];
}
else
{
[strongSelf->_portraitToolbarView transitionOutAnimated:false];
[strongSelf->_landscapeToolbarView transitionOutAnimated:true];
}
};
_currentTabController.beginItemTransitionOut = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
if (strongSelf == nil)
return;
UIInterfaceOrientation orientation = strongSelf.interfaceOrientation;
if ([strongSelf inFormSheet])
orientation = UIInterfaceOrientationPortrait;
if (UIInterfaceOrientationIsPortrait(orientation))
{
[strongSelf->_portraitToolbarView transitionInAnimated:true];
[strongSelf->_landscapeToolbarView transitionInAnimated:false];
}
else
{
[strongSelf->_portraitToolbarView transitionInAnimated:false];
[strongSelf->_landscapeToolbarView transitionInAnimated:true];
}
};
_currentTabController.valuesChanged = ^
{
__strong TGPhotoEditorController *strongSelf = weakSelf;
@ -1516,6 +1470,9 @@
if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) {
[self presentEditorTab:TGPhotoEditorCropTab];
return;
} else if (![_currentTabController isKindOfClass:[TGPhotoAvatarCropController class]] && _intent == TGPhotoEditorControllerAvatarIntent) {
[self presentEditorTab:TGPhotoEditorPreviewTab];
return;
}
if (![_currentTabController isDismissAllowed])
@ -1591,7 +1548,7 @@
if (strongSelf == nil)
return CGRectZero;
if (UIInterfaceOrientationIsPortrait(self.interfaceOrientation))
if (UIInterfaceOrientationIsPortrait(strongSelf.effectiveOrientation))
return [strongSelf.view convertRect:strongSelf->_portraitToolbarView.cancelButtonFrame fromView:strongSelf->_portraitToolbarView];
else
return [strongSelf.view convertRect:strongSelf->_landscapeToolbarView.cancelButtonFrame fromView:strongSelf->_landscapeToolbarView];
@ -1608,6 +1565,8 @@
{
if ([_currentTabController isKindOfClass:[TGPhotoAvatarCropController class]]) {
[self presentEditorTab:TGPhotoEditorPreviewTab];
} else if (_intent == TGPhotoEditorControllerAvatarIntent && ![_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]]) {
[self presentEditorTab:TGPhotoEditorPreviewTab];
} else {
[self applyEditor];
}
@ -1624,6 +1583,7 @@
TGPaintingData *paintingData = _photoEditor.paintingData;
bool saving = true;
NSTimeInterval coverPosition = 0.0;
if ([_currentTabController isKindOfClass:[TGPhotoPaintController class]])
{
TGPhotoPaintController *paintController = (TGPhotoPaintController *)_currentTabController;
@ -1640,17 +1600,87 @@
saving = false;
[[NSUserDefaults standardUserDefaults] setObject:@(qualityController.preset) forKey:@"TG_preferredVideoPreset_v0"];
} else if ([_currentTabController isKindOfClass:[TGPhotoAvatarPreviewController class]])
{
TGPhotoAvatarPreviewController *previewController = (TGPhotoAvatarPreviewController *)_currentTabController;
coverPosition = previewController.coverPosition;
}
if (_intent != TGPhotoEditorControllerVideoIntent)
TGVideoEditAdjustments *adjustments = [_photoEditor exportAdjustmentsWithPaintingData:paintingData];
if (_intent == TGPhotoEditorControllerAvatarIntent && _item.isVideo) {
[[SQueue concurrentDefaultQueue] dispatch:^
{
id<TGMediaEditableItem> item = _item;
SSignal *assetSignal = [SSignal complete];
if ([item isKindOfClass:[TGMediaAsset class]])
assetSignal = [TGMediaAssetImageSignals avAssetForVideoAsset:(TGMediaAsset *)item];
else if ([item isKindOfClass:[TGCameraCapturedVideo class]])
assetSignal = ((TGCameraCapturedVideo *)item).avAsset;
[assetSignal startWithNext:^(AVURLAsset *asset)
{
CGSize videoDimensions = CGSizeZero;
if ([item isKindOfClass:[TGMediaAsset class]])
videoDimensions = ((TGMediaAsset *)item).dimensions;
else if ([asset isKindOfClass:[AVURLAsset class]])
videoDimensions = ((AVURLAsset *)asset).originalSize;
AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
generator.appliesPreferredTrackTransform = true;
generator.maximumSize = TGFitSize(videoDimensions, CGSizeMake(1280.0f, 1280.0f));
generator.requestedTimeToleranceAfter = kCMTimeZero;
generator.requestedTimeToleranceBefore = kCMTimeZero;
CGImageRef imageRef = [generator copyCGImageAtTime:CMTimeMakeWithSeconds(coverPosition, NSEC_PER_SEC) actualTime:nil error:NULL];
UIImage *image = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
UIImage *paintingImage = adjustments.paintingData.stillImage;
if (paintingImage == nil) {
paintingImage = adjustments.paintingData.image;
}
UIImage *fullImage = nil;
UIImage *thumbnailImage = nil;
if (adjustments.toolsApplied) {
image = [PGPhotoEditor resultImageForImage:image adjustments:adjustments];
CGSize fillSize = TGScaleToFillSize(videoDimensions, image.size);
UIGraphicsBeginImageContextWithOptions(fillSize, true, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationMedium);
[image drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
[paintingImage drawInRect:CGRectMake(0, 0, fillSize.width, fillSize.height)];
fullImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
} else {
fullImage = TGPhotoEditorVideoCrop(image, paintingImage, adjustments.cropOrientation, adjustments.cropRotation, adjustments.cropRect, adjustments.cropMirrored, CGSizeMake(640, 640), item.originalSize, true, false);
}
TGDispatchOnMainThread(^{
if (self.didFinishEditingVideo != nil)
self.didFinishEditingVideo(asset.URL, [adjustments editAdjustmentsWithPreset:TGMediaVideoConversionPresetAnimation maxDuration:0.0], fullImage, nil, true);
[self transitionOutSaving:true completion:^
{
[self dismiss];
}];
});
}];
}];
return;
}
else if (_intent != TGPhotoEditorControllerVideoIntent)
{
TGProgressWindow *progressWindow = [[TGProgressWindow alloc] init];
progressWindow.windowLevel = self.view.window.windowLevel + 0.001f;
[progressWindow performSelector:@selector(showAnimated) withObject:nil afterDelay:0.5];
bool forAvatar = [self presentedForAvatarCreation];
PGPhotoEditorValues *editorValues = [_photoEditor exportAdjustmentsWithPaintingData:paintingData];
[self createEditedImageWithEditorValues:editorValues createThumbnail:!forAvatar saveOnly:false completion:^(__unused UIImage *image)
[self createEditedImageWithEditorValues:adjustments createThumbnail:!forAvatar saveOnly:false completion:^(__unused UIImage *image)
{
[NSObject cancelPreviousPerformRequestsWithTarget:progressWindow selector:@selector(showAnimated) object:nil];
[progressWindow dismiss:true];
@ -1666,7 +1696,6 @@
}
else
{
TGVideoEditAdjustments *adjustments = [_photoEditor exportAdjustmentsWithPaintingData:paintingData];
bool hasChanges = !(_initialAdjustments == nil && [adjustments isDefaultValuesForAvatar:false] && adjustments.cropOrientation == UIImageOrientationUp);
if (adjustments.paintingData != nil || adjustments.hasPainting != _initialAdjustments.hasPainting || adjustments.toolsApplied)
@ -1912,11 +1941,20 @@
return [_context fullscreenBounds].size;
}
- (void)updateLayout:(UIInterfaceOrientation)orientation
{
- (UIInterfaceOrientation)effectiveOrientation {
return [self effectiveOrientation:self.interfaceOrientation];
}
- (UIInterfaceOrientation)effectiveOrientation:(UIInterfaceOrientation)orientation {
bool isPad = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
if ([self inFormSheet] || isPad)
orientation = UIInterfaceOrientationPortrait;
return orientation;
}
- (void)updateLayout:(UIInterfaceOrientation)orientation
{
orientation = [self effectiveOrientation:orientation];
CGSize referenceSize = [self referenceViewSize];
@ -1960,7 +1998,7 @@
}
CGFloat portraitToolbarViewBottomEdge = screenSide;
if (isPad)
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
portraitToolbarViewBottomEdge = screenEdges.bottom;
_portraitToolbarView.frame = CGRectMake(screenEdges.left, portraitToolbarViewBottomEdge - TGPhotoEditorToolbarSize - safeAreaInset.bottom, referenceSize.width, TGPhotoEditorToolbarSize + safeAreaInset.bottom);
}

View File

@ -82,6 +82,17 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
}
}
- (UIInterfaceOrientation)effectiveOrientation {
return [self effectiveOrientation:self.interfaceOrientation];
}
- (UIInterfaceOrientation)effectiveOrientation:(UIInterfaceOrientation)orientation {
bool isPad = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad;
if ([self inFormSheet] || isPad)
orientation = UIInterfaceOrientationPortrait;
return orientation;
}
- (void)transitionInWithDuration:(CGFloat)__unused duration
{
@ -89,6 +100,8 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
- (void)prepareTransitionInWithReferenceView:(UIView *)referenceView referenceFrame:(CGRect)referenceFrame parentView:(UIView *)parentView noTransitionView:(bool)noTransitionView
{
_dismissing = false;
CGRect targetFrame = [self _targetFrameForTransitionInFromFrame:referenceFrame];
if (_CGRectEqualToRectWithEpsilon(targetFrame, referenceFrame, FLT_EPSILON))
@ -128,6 +141,9 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
else
{
_transitionView = [referenceView snapshotViewAfterScreenUpdates:false];
if (_transitionView == nil) {
_transitionView = referenceView;
}
transitionViewSuperview = parentView;
}
@ -324,10 +340,7 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
- (CGRect)_targetFrameForTransitionInFromFrame:(CGRect)fromFrame
{
CGSize referenceSize = [self referenceViewSize];
UIInterfaceOrientation orientation = self.interfaceOrientation;
if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
orientation = UIInterfaceOrientationPortrait;
UIInterfaceOrientation orientation = self.effectiveOrientation;
bool hasOnScreenNavigation = false;
if (iosMajorVersion() >= 11)
@ -345,7 +358,11 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
- (void)_finishedTransitionInWithView:(UIView *)transitionView
{
[transitionView removeFromSuperview];
if ([transitionView isKindOfClass:[TGPhotoEditorPreviewView class]]) {
[self.view insertSubview:transitionView atIndex:0];
} else {
[transitionView removeFromSuperview];
}
}
- (bool)inFormSheet

View File

@ -1851,8 +1851,12 @@ const CGFloat TGPhotoPaintStickerKeyboardSize = 260.0f;
{
_appeared = true;
[transitionView removeFromSuperview];
if ([transitionView isKindOfClass:[TGPhotoEditorPreviewView class]]) {
[_containerView insertSubview:transitionView belowSubview:_paintingWrapperView];
} else {
[transitionView removeFromSuperview];
}
[self setupCanvas];
TGPhotoEditorPreviewView *previewView = _previewView;

View File

@ -421,7 +421,11 @@ const NSTimeInterval TGPhotoQualityPreviewDuration = 15.0f;
{
_appeared = true;
[transitionView removeFromSuperview];
if ([transitionView isKindOfClass:[TGPhotoEditorPreviewView class]]) {
[self.view insertSubview:transitionView atIndex:0];
} else {
[transitionView removeFromSuperview];
}
TGPhotoEditorPreviewView *previewView = _previewView;
previewView.hidden = false;

View File

@ -461,7 +461,11 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
{
_appeared = true;
[transitionView removeFromSuperview];
if ([transitionView isKindOfClass:[TGPhotoEditorPreviewView class]]) {
[self.view insertSubview:transitionView atIndex:0];
} else {
[transitionView removeFromSuperview];
}
TGPhotoEditorPreviewView *previewView = _previewView;
previewView.hidden = false;

View File

@ -269,9 +269,14 @@ const NSTimeInterval TGVideoEditMaximumGifDuration = 30.5;
return (_paintingData != nil);
}
- (bool)cropAppliedForAvatar:(bool)__unused forAvatar
- (bool)cropAppliedForAvatar:(bool)forAvatar
{
CGRect defaultCropRect = CGRectMake(0, 0, _originalSize.width, _originalSize.height);
if (forAvatar)
{
CGFloat shortSide = MIN(_originalSize.width, _originalSize.height);
defaultCropRect = CGRectMake((_originalSize.width - shortSide) / 2, (_originalSize.height - shortSide) / 2, shortSide, shortSide);
}
if (_CGRectEqualToRectWithEpsilon(self.cropRect, CGRectZero, [self _cropRectEpsilon]))
return false;

View File

@ -252,7 +252,11 @@ typedef enum
self.view.backgroundColor = [UIColor clearColor];
CGRect wrapperFrame = TGIsPad() ? CGRectMake(0.0f, 0.0f, self.view.frame.size.width, CGRectGetMaxY(_controlsFrame)): CGRectMake(0.0f, 0.0f, self.view.frame.size.width, CGRectGetMinY(_controlsFrame));
CGFloat bottomOffset = self.view.frame.size.height - CGRectGetMaxY(_controlsFrame);
if (bottomOffset > 44.0) {
bottomOffset = 0.0f;
}
CGRect wrapperFrame = TGIsPad() ? CGRectMake(0.0f, 0.0f, self.view.frame.size.width, CGRectGetMaxY(_controlsFrame) + bottomOffset): CGRectMake(0.0f, 0.0f, self.view.frame.size.width, CGRectGetMinY(_controlsFrame));
_wrapperView = [[UIView alloc] initWithFrame:wrapperFrame];
_wrapperView.clipsToBounds = true;
@ -340,7 +344,7 @@ typedef enum
[_circleWrapperView addSubview:_ringView];
CGRect controlsFrame = _controlsFrame;
controlsFrame.size.width = _wrapperView.frame.size.width;
// controlsFrame.size.width = _wrapperView.frame.size.width;
_controlsView = [[TGVideoMessageControls alloc] initWithFrame:controlsFrame assets:_assets slowmodeTimestamp:_slowmodeTimestamp slowmodeView:_slowmodeView];
_controlsView.pallete = self.pallete;

View File

@ -844,6 +844,9 @@ static id<LegacyComponentsContext> _defaultContext = nil;
CGRect keyboardFrame = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardHeight = MIN(keyboardFrame.size.height, keyboardFrame.size.width);
if (CGRectGetMaxY(keyboardFrame) < [UIScreen mainScreen].bounds.size.height) {
keyboardHeight = 0.0f;
}
double duration = ([[[notification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]);
if ([self isViewLoaded] && !_viewControllerHasEverAppeared && ([self findFirstResponder:self.view] == nil && ![self willCaptureInputShortly]))

View File

@ -6,8 +6,35 @@ import SyncCore
import LegacyComponents
import SwiftSignalKit
final class LegacyLiveUploadInterfaceResult: NSObject {
let id: Int64
public class VideoConversionWatcher: TGMediaVideoFileWatcher {
private let update: (String, Int) -> Void
private var path: String?
public init(update: @escaping (String, Int) -> Void) {
self.update = update
super.init()
}
override public func setup(withFileURL fileURL: URL!) {
self.path = fileURL?.path
super.setup(withFileURL: fileURL)
}
override public func fileUpdated(_ completed: Bool) -> Any! {
if let path = self.path {
var value = stat()
if stat(path, &value) == 0 {
self.update(path, Int(value.st_size))
}
}
return super.fileUpdated(completed)
}
}
public final class LegacyLiveUploadInterfaceResult: NSObject {
public let id: Int64
init(id: Int64) {
self.id = id
@ -16,7 +43,7 @@ final class LegacyLiveUploadInterfaceResult: NSObject {
}
}
final class LegacyLiveUploadInterface: VideoConversionWatcher, TGLiveUploadInterface {
public final class LegacyLiveUploadInterface: VideoConversionWatcher, TGLiveUploadInterface {
private let account: Account
private let id: Int64
private var path: String?
@ -25,7 +52,7 @@ final class LegacyLiveUploadInterface: VideoConversionWatcher, TGLiveUploadInter
private let data = Promise<MediaResourceData>()
private let dataValue = Atomic<MediaResourceData?>(value: nil)
init(account: Account) {
public init(account: Account) {
self.account = account
self.id = arc4random64()
@ -60,7 +87,7 @@ final class LegacyLiveUploadInterface: VideoConversionWatcher, TGLiveUploadInter
deinit {
}
override func fileUpdated(_ completed: Bool) -> Any! {
override public func fileUpdated(_ completed: Bool) -> Any! {
let _ = super.fileUpdated(completed)
print("**fileUpdated \(completed)")
if completed {

View File

@ -238,7 +238,7 @@ class SecureIdDocumentGalleryController: ViewController, StandalonePresentableCo
if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) {
nodeAnimatesItself = true
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: {})
self._hiddenMedia.set(.single(self.entries[centralItemNode.index]))
}

View File

@ -138,7 +138,7 @@ final class SecureIdDocumentGalleryItemNode: ZoomableContentGalleryItemNode {
self.contextAndMedia = (context, secureIdContext, resource)
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.imageNode.view)
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.imageNode.view.superview)
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)

View File

@ -18,7 +18,7 @@ public enum AvatarGalleryEntryId: Hashable {
public enum AvatarGalleryEntry: Equatable {
case topImage([ImageRepresentationWithReference], GalleryItemIndexData?)
case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], Peer?, Int32, GalleryItemIndexData?, MessageId?)
case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], [TelegramMediaImage.VideoRepresentation], Peer?, Int32, GalleryItemIndexData?, MessageId?)
public var id: AvatarGalleryEntryId {
switch self {
@ -33,16 +33,25 @@ public enum AvatarGalleryEntry: Equatable {
switch self {
case let .topImage(representations, _):
return representations
case let .image(_, _, representations, _, _, _, _):
case let .image(_, _, representations, _, _, _, _, _):
return representations
}
}
public var videoRepresentations: [TelegramMediaImage.VideoRepresentation] {
switch self {
case let .topImage(representations, _):
return []
case let .image(_, _, _, videoRepresentations, _, _, _, _):
return videoRepresentations
}
}
public var indexData: GalleryItemIndexData? {
switch self {
case let .topImage(_, indexData):
return indexData
case let .image(_, _, _, _, _, indexData, _):
case let .image(_, _, _, _, _, _, indexData, _):
return indexData
}
}
@ -55,8 +64,8 @@ public enum AvatarGalleryEntry: Equatable {
} else {
return false
}
case let .image(lhsId, lhsImageReference, lhsRepresentations, lhsPeer, lhsDate, lhsIndexData, lhsMessageId):
if case let .image(rhsId, rhsImageReference, rhsRepresentations, rhsPeer, rhsDate, rhsIndexData, rhsMessageId) = rhs, lhsId == rhsId, lhsImageReference == rhsImageReference, lhsRepresentations == rhsRepresentations, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate, lhsIndexData == rhsIndexData, lhsMessageId == rhsMessageId {
case let .image(lhsId, lhsImageReference, lhsRepresentations, lhsVideoRepresentations, lhsPeer, lhsDate, lhsIndexData, lhsMessageId):
if case let .image(rhsId, rhsImageReference, rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsDate, rhsIndexData, rhsMessageId) = rhs, lhsId == rhsId, lhsImageReference == rhsImageReference, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate, lhsIndexData == rhsIndexData, lhsMessageId == rhsMessageId {
return true
} else {
return false
@ -98,9 +107,9 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<
for photo in photos {
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
if result.isEmpty, let first = initialEntries.first {
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, peer, photo.date, indexData, photo.messageId))
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, first.videoRepresentations, peer, photo.date, indexData, photo.messageId))
} else {
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId))
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.image.videoRepresentations, peer, photo.date, indexData, photo.messageId))
}
index += 1
}
@ -124,10 +133,10 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer, firstEntry
var index: Int32 = 0
for photo in photos {
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
if result.isEmpty, let first = initialEntries.first {
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, peer, photo.date, indexData, photo.messageId))
if result.isEmpty, let first = initialEntries.first, photo.image.videoRepresentations.isEmpty {
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, first.videoRepresentations, peer, photo.date, indexData, photo.messageId))
} else {
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId))
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), photo.image.videoRepresentations, peer, photo.date, indexData, photo.messageId))
}
index += 1
}
@ -149,6 +158,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
private var presentationData: PresentationData
private let _ready = Promise<Bool>()
private let animatedIn = ValuePromise<Bool>(true)
override public var ready: Promise<Bool> {
return self._ready
}
@ -210,9 +220,9 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
let syncResult = Atomic<(Bool, (() -> Void)?)>(value: (false, nil))
self.disposable.set(entriesSignal.start(next: { [weak self] entries in
self.disposable.set(combineLatest(entriesSignal, self.animatedIn.get()).start(next: { [weak self] entries, animatedIn in
let f: () -> Void = {
if let strongSelf = self {
if let strongSelf = self, animatedIn {
strongSelf.entries = entries
if strongSelf.centralEntryIndex == nil {
strongSelf.centralEntryIndex = 0
@ -235,7 +245,10 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
}
strongSelf.galleryNode.pager.replaceItems(strongSelf.entries.map({ entry in PeerAvatarImageGalleryItem(context: context, peer: peer, presentationData: presentationData, entry: entry, sourceHasRoundCorners: sourceHasRoundCorners, delete: canDelete ? {
self?.deleteEntry(entry)
} : nil) }), centralItemIndex: 0, keepFirst: true)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
})
}), centralItemIndex: 0, keepFirst: false)
let ready = strongSelf.galleryNode.pager.ready() |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(Void())) |> afterNext { [weak strongSelf] _ in
strongSelf?.didSetReady = true
@ -387,7 +400,9 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
let presentationData = self.presentationData
self.galleryNode.pager.replaceItems(self.entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceHasRoundCorners: self.sourceHasRoundCorners, delete: canDelete ? { [weak self] in
self?.deleteEntry(entry)
} : nil) }), centralItemIndex: self.centralEntryIndex)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
}) }), centralItemIndex: self.centralEntryIndex)
self.galleryNode.pager.centralItemIndexUpdated = { [weak self] index in
if let strongSelf = self {
@ -432,7 +447,10 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
if let transitionArguments = presentationArguments.transitionArguments(self.entries[centralItemNode.index]) {
nodeAnimatesItself = true
if presentationArguments.animated {
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
self.animatedIn.set(false)
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: {
self.animatedIn.set(true)
})
}
self._hiddenMedia.set(.single(self.entries[centralItemNode.index]))
@ -474,6 +492,93 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
}
}
private func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryEntry] {
var updatedEntries: [AvatarGalleryEntry] = []
let count: Int32 = Int32(entries.count)
var index: Int32 = 0
for entry in entries {
let indexData = GalleryItemIndexData(position: index, totalCount: count)
if case let .topImage(representations, _) = entry {
updatedEntries.append(.topImage(representations, indexData))
} else if case let .image(id, reference, representations, videoRepresentations, peer, date, _, messageId) = entry {
updatedEntries.append(.image(id, reference, representations, videoRepresentations, peer, date, indexData, messageId))
}
index += 1
}
return updatedEntries
}
private func setMainEntry(_ rawEntry: AvatarGalleryEntry) {
var entry = rawEntry
if case .topImage = entry, !self.entries.isEmpty {
entry = self.entries[0]
}
switch entry {
case .topImage:
if self.peer.id == self.context.account.peerId {
} else {
}
case let .image(_, reference, _, _, _, _, _, messageId):
if self.peer.id == self.context.account.peerId {
if let reference = reference {
let _ = updatePeerPhotoExisting(network: self.context.account.network, reference: reference).start()
}
if let index = self.entries.firstIndex(of: entry) {
var entries = self.entries
let previousFirstEntry = entries.first
entries.remove(at: index)
entries.remove(at: 0)
entries.insert(entry, at: 0)
if let previousFirstEntry = previousFirstEntry {
entries.insert(previousFirstEntry, at: index)
}
let canDelete: Bool
if self.peer.id == self.context.account.peerId {
canDelete = true
} else if let group = self.peer as? TelegramGroup {
switch group.role {
case .creator, .admin:
canDelete = true
case .member:
canDelete = false
}
} else if let channel = self.peer as? TelegramChannel {
canDelete = channel.hasPermission(.changeInfo)
} else {
canDelete = false
}
entries = self.normalizeEntries(entries)
self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: peer, presentationData: presentationData, entry: entry, sourceHasRoundCorners: self.sourceHasRoundCorners, delete: canDelete ? { [weak self] in
self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
}) }), centralItemIndex: 0)
self.entries = entries
}
} else {
// if let messageId = messageId {
// let _ = deleteMessagesInteractively(account: self.context.account, messageIds: [messageId], type: .forEveryone).start()
// }
//
// if entry == self.entries.first {
// let _ = updatePeerPhoto(postbox: self.context.account.postbox, network: self.context.account.network, stateManager: self.context.account.stateManager, accountPeerId: self.context.account.peerId, peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start()
// self.dismiss(forceAway: true)
// } else {
// if let index = self.entries.firstIndex(of: entry) {
// self.entries.remove(at: index)
// self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1))
// }
// }
}
}
}
private func deleteEntry(_ rawEntry: AvatarGalleryEntry) {
var entry = rawEntry
if case .topImage = entry, !self.entries.isEmpty {
@ -494,7 +599,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
}
}
}
case let .image(_, reference, _, _, _, _, messageId):
case let .image(_, reference, _, _, _, _, _, messageId):
if self.peer.id == self.context.account.peerId {
if let reference = reference {
let _ = removeAccountPhoto(network: self.context.account.network, reference: reference).start()

View File

@ -49,11 +49,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
var share: ((GalleryControllerInteraction) -> Void)?
var setMain: (() -> Void)? {
didSet {
self.setMainButton.isHidden = self.setMain == nil
}
}
var setMain: (() -> Void)?
init(context: AccountContext, presentationData: PresentationData) {
self.context = context
@ -81,13 +77,13 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
self.setMainButton = HighlightableButtonNode()
self.setMainButton.isHidden = true
self.setMainButton.setAttributedTitle(NSAttributedString(string: "Set as Main Photo", font: Font.regular(17.0), textColor: .white), for: .normal)
self.setMainButton.setAttributedTitle(NSAttributedString(string: self.strings.ProfilePhoto_SetMain, font: Font.regular(17.0), textColor: .white), for: .normal)
self.mainNode = ASTextNode()
self.mainNode.maximumNumberOfLines = 1
self.mainNode.isUserInteractionEnabled = false
self.mainNode.displaysAsynchronously = false
self.mainNode.attributedText = NSAttributedString(string: "Main Photo", font: Font.regular(17.0), textColor: UIColor(rgb: 0x808080))
self.mainNode.attributedText = NSAttributedString(string: self.strings.ProfilePhoto_MainPhoto, font: Font.regular(17.0), textColor: UIColor(rgb: 0x808080))
super.init()
@ -103,15 +99,12 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside])
self.setMainButton.addTarget(self, action: #selector(self.setMainButtonPressed), forControlEvents: .touchUpInside)
}
deinit {
}
func setEntry(_ entry: AvatarGalleryEntry, content: AvatarGalleryItemFooterContent) {
var nameText: String?
var dateText: String?
switch entry {
case let .image(_, _, _, peer, date, _, _):
case let .image(_, _, _, _, peer, date, _, _):
nameText = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""
dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date)
default:
@ -222,6 +215,6 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
}
@objc private func setMainButtonPressed() {
self.setMain?()
}
}

View File

@ -12,6 +12,7 @@ import RadialStatusNode
import ShareController
import PhotoResources
import GalleryUI
import TelegramUniversalVideoContent
private struct PeerAvatarImageGalleryThumbnailItem: GalleryThumbnailItem {
let account: Account
@ -52,14 +53,16 @@ class PeerAvatarImageGalleryItem: GalleryItem {
let entry: AvatarGalleryEntry
let sourceHasRoundCorners: Bool
let delete: (() -> Void)?
let setMain: (() -> Void)?
init(context: AccountContext, peer: Peer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceHasRoundCorners: Bool, delete: (() -> Void)?) {
init(context: AccountContext, peer: Peer, presentationData: PresentationData, entry: AvatarGalleryEntry, sourceHasRoundCorners: Bool, delete: (() -> Void)?, setMain: (() -> Void)?) {
self.context = context
self.peer = peer
self.presentationData = presentationData
self.entry = entry
self.sourceHasRoundCorners = sourceHasRoundCorners
self.delete = delete
self.setMain = setMain
}
func node() -> GalleryItemNode {
@ -71,6 +74,7 @@ class PeerAvatarImageGalleryItem: GalleryItem {
node.setEntry(self.entry)
node.footerContentNode.delete = self.delete
node.footerContentNode.setMain = self.setMain
return node
}
@ -83,6 +87,7 @@ class PeerAvatarImageGalleryItem: GalleryItem {
node.setEntry(self.entry)
node.footerContentNode.delete = self.delete
node.footerContentNode.setMain = self.setMain
}
}
@ -91,7 +96,7 @@ class PeerAvatarImageGalleryItem: GalleryItem {
switch self.entry {
case let .topImage(representations, _):
content = representations
case let .image(_, _, representations, _, _, _, _):
case let .image(_, _, representations, _, _, _, _, _):
content = representations
}
@ -107,6 +112,9 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
private var entry: AvatarGalleryEntry?
private let imageNode: TransformImageNode
private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
fileprivate let _ready = Promise<Void>()
fileprivate let _title = Promise<String>()
private let statusNodeContainer: HighlightableButtonNode
@ -176,13 +184,11 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
var footerContent: AvatarGalleryItemFooterContent
if self.peer.id == self.context.account.peerId {
footerContent = .own(true)
footerContent = .own((entry.indexData?.position ?? 0) == 0)
} else {
footerContent = .info
}
self.peer.largeProfileImage
self.footerContentNode.setEntry(entry, content: footerContent)
if let largestSize = largestImageRepresentation(entry.representations.map({ $0.representation })) {
@ -192,7 +198,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
switch entry {
case let .topImage(topRepresentations, _):
representations = topRepresentations
case let .image(_, _, imageRepresentations, _, _, _, _):
case let .image(_, _, imageRepresentations, _, _, _, _, _):
representations = imageRepresentations
}
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations), dispatchOnDisplayLink: false)
@ -247,7 +253,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: self.imageNode.view)
let transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: self.imageNode.view.superview)
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)
@ -300,7 +306,9 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
let scale = CGSize(width: transformedCopyViewFinalFrame.size.width / transformedSelfFrame.size.width, height: transformedCopyViewFinalFrame.size.height / transformedSelfFrame.size.height)
copyView.layer.animate(from: NSValue(caTransform3D: CATransform3DIdentity), to: NSValue(caTransform3D: CATransform3DMakeScale(scale.width, scale.height, 1.0)), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25, removeOnCompletion: false)
self.imageNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.imageNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
self.imageNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.imageNode.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
completion()
})
self.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.07)
transformedFrame.origin = CGPoint()
@ -427,7 +435,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
switch entry {
case let .topImage(topRepresentations, _):
representations = topRepresentations
case let .image(_, _, imageRepresentations, _, _, _, _):
case let .image(_, _, imageRepresentations, _, _, _, _, _):
representations = imageRepresentations
}

View File

@ -849,43 +849,6 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
pushControllerImpl?(controller)
}
}, openStats: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var urlSignal = channelStatsUrl(postbox: context.account.postbox, network: context.account.network, peerId: peerId, params: "", darkTheme: presentationData.theme.rootController.keyboardColor.keyboardAppearance == .dark)
var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?()
}))
presentControllerImpl?(controller, nil)
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.05, queue: Queue.mainQueue())
let progressDisposable = progressSignal.start()
urlSignal = urlSignal
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
cancelImpl = {
statsUrlDisposable.set(nil)
}
statsUrlDisposable.set((urlSignal
|> deliverOnMainQueue).start(next: { url in
pushControllerImpl?(ChannelStatsController(context: context, url: url, peerId: peerId))
}, error: { _ in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}))
}, openAdmins: {
pushControllerImpl?(channelAdminsController(context: context, peerId: peerId))
}, openMembers: {

View File

@ -1,74 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramCore
import SyncCore
import SwiftSignalKit
import Postbox
import TelegramPresentationData
import ProgressNavigationButtonNode
import AccountContext
final class ChannelStatsController: ViewController {
private var controllerNode: ChannelStatsControllerNode {
return self.displayNode as! ChannelStatsControllerNode
}
private let context: AccountContext
private let url: String
private let peerId: PeerId
private var presentationData: PresentationData
init(context: AccountContext, url: String, peerId: PeerId) {
self.context = context
self.url = url
self.peerId = peerId
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.navigationItem.title = self.presentationData.strings.ChannelInfo_Stats
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func closePressed() {
self.dismiss()
}
override func loadDisplayNode() {
self.displayNode = ChannelStatsControllerNode(context: self.context, presentationData: self.presentationData, peerId: self.peerId, url: self.url, present: { [weak self] c, a in
self?.present(c, in: .window(.root), with: a)
}, updateActivity: { [weak self] value in
guard let strongSelf = self else {
return
}
if value {
strongSelf.navigationItem.rightBarButtonItem = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: strongSelf.presentationData.theme.rootController.navigationBar.controlColor))
} else {
strongSelf.navigationItem.rightBarButtonItem = nil
}
})
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
}
override var presentationController: UIPresentationController? {
get {
return nil
} set(value) {
}
}
}

View File

@ -1,127 +0,0 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import WebKit
import TelegramCore
import SyncCore
import Postbox
import SwiftSignalKit
import TelegramPresentationData
import AccountContext
final class ChannelStatsControllerNode: ViewControllerTracingNode, WKNavigationDelegate {
private var webView: WKWebView?
private let context: AccountContext
private let peerId: PeerId
var presentationData: PresentationData
private let present: (ViewController, Any?) -> Void
private let updateActivity: (Bool) -> Void
private let refreshDisposable = MetaDisposable()
init(context: AccountContext, presentationData: PresentationData, peerId: PeerId, url: String, present: @escaping (ViewController, Any?) -> Void, updateActivity: @escaping (Bool) -> Void) {
self.context = context
self.presentationData = presentationData
self.peerId = peerId
self.present = present
self.updateActivity = updateActivity
super.init()
self.backgroundColor = .white
let configuration = WKWebViewConfiguration()
let userController = WKUserContentController()
configuration.userContentController = userController
let webView = WKWebView(frame: CGRect(), configuration: configuration)
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
webView.allowsLinkPreview = false
}
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
webView.scrollView.contentInsetAdjustmentBehavior = .never
}
webView.navigationDelegate = self
webView.interactiveTransitionGestureRecognizerTest = { point -> Bool in
return point.x > 30.0
}
self.view.addSubview(webView)
self.webView = webView
if let parsedUrl = URL(string: url) {
webView.load(URLRequest(url: parsedUrl))
}
}
deinit {
self.refreshDisposable.dispose()
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
if let webView = self.webView {
webView.frame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: navigationBarHeight), size: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: max(1.0, layout.size.height - navigationBarHeight)))
}
}
func animateIn() {
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
func animateOut(completion: (() -> Void)? = nil) {
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in
completion?()
})
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
if let url = navigationAction.request.url, url.scheme == "tg" {
if url.host == "statsrefresh" {
var params = ""
if let query = url.query, let components = URLComponents(string: "/?" + query) {
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "params" {
params = value
}
}
}
}
}
self.refreshDisposable.set((channelStatsUrl(postbox: self.context.account.postbox, network: self.context.account.network, peerId: self.peerId, params: params, darkTheme: self.presentationData.theme.rootController.keyboardColor.keyboardAppearance == .dark)
|> deliverOnMainQueue).start(next: { [weak self] url in
guard let strongSelf = self else {
return
}
if let parsedUrl = URL(string: url) {
strongSelf.webView?.load(URLRequest(url: parsedUrl))
}
}, error: { _ in
}))
}
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
private func updateActivityIndicator(show: Bool) {
self.updateActivity(show)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.updateActivityIndicator(show: false)
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
self.updateActivityIndicator(show: true)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
self.updateActivityIndicator(show: false)
}
}

View File

@ -18,6 +18,8 @@ import WebSearchUI
import PeerAvatarGalleryUI
import MapResourceToAvatarSizes
import PhoneNumberFormat
import LegacyMediaPickerUI
import LocalMediaResources
private struct EditSettingsItemArguments {
let context: AccountContext
@ -505,7 +507,7 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
hasPhotos = true
}
let completedImpl: (UIImage) -> Void = { image in
let completedPhotoImpl: (UIImage) -> Void = { image in
if let data = image.jpegData(compressionQuality: 0.6) {
let resource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
@ -528,18 +530,96 @@ func editSettingsController(context: AccountContext, currentName: ItemListAvatar
}
}
let completedVideoImpl: (UIImage, URL, TGVideoEditAdjustments?) -> Void = { image, url, adjustments in
if let data = image.jpegData(compressionQuality: 0.6) {
let signal = Signal<TelegramMediaResource, UploadPeerPhotoError> { subscriber in
var filteredPath = url.path
if filteredPath.hasPrefix("file://") {
filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)])
}
let avAsset = AVURLAsset(url: URL(fileURLWithPath: filteredPath))
let entityRenderer: LegacyPaintEntityRenderer? = adjustments.flatMap { adjustments in
if let paintingData = adjustments.paintingData, paintingData.hasAnimation {
return LegacyPaintEntityRenderer(account: context.account, adjustments: adjustments)
} else {
return nil
}
}
let uploadInterface = LegacyLiveUploadInterface(account: context.account)
let signal = TGMediaVideoConverter.convert(avAsset, adjustments: adjustments, watcher: uploadInterface, entityRenderer: entityRenderer)!
let signalDisposable = signal.start(next: { next in
if let result = next as? TGMediaVideoConversionResult {
var value = stat()
if stat(result.fileURL.path, &value) == 0 {
if let data = try? Data(contentsOf: result.fileURL) {
let resource: TelegramMediaResource
if let liveUploadData = result.liveUploadData as? LegacyLiveUploadInterfaceResult {
resource = LocalFileMediaResource(fileId: liveUploadData.id)
} else {
resource = LocalFileMediaResource(fileId: arc4random64())
}
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
subscriber.putNext(resource)
}
}
subscriber.putCompletion()
}
}, error: { _ in
}, completed: nil)
let disposable = ActionDisposable {
signalDisposable?.dispose()
}
return ActionDisposable {
disposable.dispose()
}
}
let resource = LocalFileMediaResource(fileId: arc4random64())
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource)
updateState {
$0.withUpdatedUpdatingAvatar(.image(representation, true))
}
updateAvatarDisposable.set((signal
|> mapToSignal { videoResource in
return updateAccountPhoto(account: context.account, resource: resource, videoResource: videoResource, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: context.account.postbox, resource: resource, representations: representations)
})
} |> deliverOnMainQueue).start(next: { result in
switch result {
case .complete:
updateState {
$0.withUpdatedUpdatingAvatar(nil)
}
case .progress:
break
}
}))
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: hasPhotos, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { assetsController in
let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in
assetsController?.dismiss()
completedImpl(result)
completedPhotoImpl(result)
}))
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
mixin.didFinishWithImage = { image in
if let image = image {
completedImpl(image)
completedPhotoImpl(image)
}
}
mixin.didFinishWithVideo = { image, url, adjustments in
if let image = image, let url = url {
completedVideoImpl(image, url, adjustments)
}
}
mixin.didFinishWithDelete = {

View File

@ -1311,7 +1311,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
}
}
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: true)!
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: true, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { assetsController in
let controller = WebSearchController(context: context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: nil, completion: { result in

View File

@ -17,7 +17,7 @@ final class ShareControllerRecentPeersGridItem: GridItem {
let controllerInteraction: ShareControllerInteraction
let section: GridSection? = nil
let fillsRowWithHeight: CGFloat? = 130.0
let fillsRowWithHeight: (CGFloat, Bool)? = (130.0, true)
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ShareControllerInteraction) {
self.context = context

View File

@ -25,6 +25,7 @@ static_library(
"//submodules/GraphCore:GraphCore",
"//submodules/GraphUI:GraphUI",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/ItemListPeerItem:ItemListPeerItem",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -26,6 +26,7 @@ swift_library(
"//submodules/GraphCore:GraphCore",
"//submodules/GraphUI:GraphUI",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/ItemListPeerItem:ItemListPeerItem",
],
visibility = [
"//visibility:public",

View File

@ -16,12 +16,12 @@ import PresentationDataUtils
import AppBundle
import GraphUI
private final class StatsControllerArguments {
private final class ChannelStatsControllerArguments {
let context: AccountContext
let loadDetailedGraph: (ChannelStatsGraph, Int64) -> Signal<ChannelStatsGraph?, NoError>
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
let openMessage: (MessageId) -> Void
init(context: AccountContext, loadDetailedGraph: @escaping (ChannelStatsGraph, Int64) -> Signal<ChannelStatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void) {
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void) {
self.context = context
self.loadDetailedGraph = loadDetailedGraph
self.openMessage = openMessage
@ -47,34 +47,34 @@ private enum StatsEntry: ItemListNodeEntry {
case overview(PresentationTheme, ChannelStats)
case growthTitle(PresentationTheme, String)
case growthGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType)
case growthGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case followersTitle(PresentationTheme, String)
case followersGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType)
case followersGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case notificationsTitle(PresentationTheme, String)
case notificationsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType)
case notificationsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case viewsByHourTitle(PresentationTheme, String)
case viewsByHourGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType)
case viewsByHourGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case viewsBySourceTitle(PresentationTheme, String)
case viewsBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType)
case viewsBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case followersBySourceTitle(PresentationTheme, String)
case followersBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType)
case followersBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case languagesTitle(PresentationTheme, String)
case languagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType)
case languagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case postInteractionsTitle(PresentationTheme, String)
case postInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType)
case postInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case postsTitle(PresentationTheme, String)
case post(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Message, ChannelStatsMessageInteractions)
case instantPageInteractionsTitle(PresentationTheme, String)
case instantPageInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, ChannelStatsGraph, ChartType)
case instantPageInteractionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
var section: ItemListSectionId {
switch self {
@ -294,7 +294,7 @@ private enum StatsEntry: ItemListNodeEntry {
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! StatsControllerArguments
let arguments = arguments as! ChannelStatsControllerArguments
switch self {
case let .overviewHeader(_, text, dates):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
@ -311,29 +311,16 @@ private enum StatsEntry: ItemListNodeEntry {
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .overview(_, stats):
return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks)
case let .growthGraph(_, _, _, graph, type):
case let .growthGraph(_, _, _, graph, type),
let .followersGraph(_, _, _, graph, type),
let .notificationsGraph(_, _, _, graph, type),
let .viewsByHourGraph(_, _, _, graph, type),
let .viewsBySourceGraph(_, _, _, graph, type),
let .followersBySourceGraph(_, _, _, graph, type),
let .languagesGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .followersGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .notificationsGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .viewsByHourGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .viewsBySourceGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .followersBySourceGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .languagesGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .postInteractionsGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, getDetailsData: { date, completion in
let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in
if let graph = graph, case let .Loaded(_, data) = graph {
completion(data)
}
})
}, sectionId: self.section, style: .blocks)
case let .instantPageInteractionsGraph(_, _, _, graph, type):
case let .postInteractionsGraph(_, _, _, graph, type),
let .instantPageInteractionsGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, getDetailsData: { date, completion in
let _ = arguments.loadDetailedGraph(graph, Int64(date.timeIntervalSince1970) * 1000).start(next: { graph in
if let graph = graph, case let .Loaded(_, data) = graph {
@ -349,7 +336,7 @@ private enum StatsEntry: ItemListNodeEntry {
}
}
private func statsControllerEntries(data: ChannelStats?, messages: [Message]?, interactions: [MessageId: ChannelStatsMessageInteractions]?, presentationData: PresentationData) -> [StatsEntry] {
private func channelStatsControllerEntries(data: ChannelStats?, messages: [Message]?, interactions: [MessageId: ChannelStatsMessageInteractions]?, presentationData: PresentationData) -> [StatsEntry] {
var entries: [StatsEntry] = []
if let data = data {
@ -422,10 +409,7 @@ private func statsControllerEntries(data: ChannelStats?, messages: [Message]?, i
public func channelStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
var navigateToMessageImpl: ((MessageId) -> Void)?
let actionsDisposable = DisposableSet()
let checkCreationAvailabilityDisposable = MetaDisposable()
actionsDisposable.add(checkCreationAvailabilityDisposable)
let actionsDisposable = DisposableSet()
let dataPromise = Promise<ChannelStats?>(nil)
let messagesPromise = Promise<MessageHistoryView?>(nil)
@ -453,7 +437,7 @@ public func channelStatsController(context: AccountContext, peerId: PeerId, cach
})
dataPromise.set(.single(nil) |> then(dataSignal))
let arguments = StatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<ChannelStatsGraph?, NoError> in
let arguments = ChannelStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
return statsContext.loadDetailedGraph(graph, x: x)
}, openMessage: { messageId in
navigateToMessageImpl?(messageId)
@ -492,7 +476,7 @@ public func channelStatsController(context: AccountContext, peerId: PeerId, cach
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: statsControllerEntries(data: data, messages: messages, interactions: interactions, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: channelStatsControllerEntries(data: data, messages: messages, interactions: interactions, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
return (controllerState, (listState, arguments))
}

View File

@ -0,0 +1,558 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import MapKit
import TelegramPresentationData
import TelegramUIPreferences
import TelegramStringFormatting
import ItemListUI
import PresentationDataUtils
import AccountContext
import PresentationDataUtils
import AppBundle
import GraphUI
import ItemListPeerItem
private final class GroupStatsControllerArguments {
let context: AccountContext
let loadDetailedGraph: (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>
let openPeer: (PeerId) -> Void
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openPeer: @escaping (PeerId) -> Void) {
self.context = context
self.loadDetailedGraph = loadDetailedGraph
self.openPeer = openPeer
}
}
private enum StatsSection: Int32 {
case overview
case growth
case members
case newMembersBySource
case languages
case messages
case actions
case topHours
case topPosters
case topAdmins
case topInviters
}
private enum StatsEntry: ItemListNodeEntry {
case overviewHeader(PresentationTheme, String, String)
case overview(PresentationTheme, GroupStats)
case growthTitle(PresentationTheme, String)
case growthGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case membersTitle(PresentationTheme, String)
case membersGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case newMembersBySourceTitle(PresentationTheme, String)
case newMembersBySourceGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case languagesTitle(PresentationTheme, String)
case languagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case messagesTitle(PresentationTheme, String)
case messagesGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case actionsTitle(PresentationTheme, String)
case actionsGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case topHoursTitle(PresentationTheme, String)
case topHoursGraph(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, StatsGraph, ChartType)
case topPostersTitle(PresentationTheme, String)
case topPoster(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopPoster)
case topAdminsTitle(PresentationTheme, String)
case topAdmin(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopAdmin)
case topInvitersTitle(PresentationTheme, String)
case topInviter(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer, GroupStatsTopInviter)
var section: ItemListSectionId {
switch self {
case .overviewHeader, .overview:
return StatsSection.overview.rawValue
case .growthTitle, .growthGraph:
return StatsSection.growth.rawValue
case .membersTitle, .membersGraph:
return StatsSection.members.rawValue
case .newMembersBySourceTitle, .newMembersBySourceGraph:
return StatsSection.newMembersBySource.rawValue
case .languagesTitle, .languagesGraph:
return StatsSection.languages.rawValue
case .messagesTitle, . messagesGraph:
return StatsSection.messages.rawValue
case .actionsTitle, .actionsGraph:
return StatsSection.actions.rawValue
case .topHoursTitle, .topHoursGraph:
return StatsSection.topHours.rawValue
case .topPostersTitle, .topPoster:
return StatsSection.topPosters.rawValue
case .topAdminsTitle, .topAdmin:
return StatsSection.topAdmins.rawValue
case .topInvitersTitle, .topInviter:
return StatsSection.topInviters.rawValue
}
}
var stableId: Int32 {
switch self {
case .overviewHeader:
return 0
case .overview:
return 1
case .growthTitle:
return 2
case .growthGraph:
return 3
case .membersTitle:
return 4
case .membersGraph:
return 5
case .newMembersBySourceTitle:
return 6
case .newMembersBySourceGraph:
return 7
case .languagesTitle:
return 8
case .languagesGraph:
return 9
case .messagesTitle:
return 10
case .messagesGraph:
return 11
case .actionsTitle:
return 12
case .actionsGraph:
return 13
case .topHoursTitle:
return 14
case .topHoursGraph:
return 15
case .topPostersTitle:
return 1000
case let .topPoster(index, _, _, _, _, _):
return 1001 + index
case .topAdminsTitle:
return 2000
case let .topAdmin(index, _, _, _, _, _):
return 2001 + index
case .topInvitersTitle:
return 3000
case let .topInviter(index, _, _, _, _, _):
return 30001 + index
}
}
static func ==(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
switch lhs {
case let .overviewHeader(lhsTheme, lhsText, lhsDates):
if case let .overviewHeader(rhsTheme, rhsText, rhsDates) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsDates == rhsDates {
return true
} else {
return false
}
case let .overview(lhsTheme, lhsStats):
if case let .overview(rhsTheme, rhsStats) = rhs, lhsTheme === rhsTheme, lhsStats == rhsStats {
return true
} else {
return false
}
case let .growthTitle(lhsTheme, lhsText):
if case let .growthTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .growthGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .growthGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .membersTitle(lhsTheme, lhsText):
if case let .membersTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .membersGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .membersGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .newMembersBySourceTitle(lhsTheme, lhsText):
if case let .newMembersBySourceTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .newMembersBySourceGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .newMembersBySourceGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .languagesTitle(lhsTheme, lhsText):
if case let .languagesTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .languagesGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .languagesGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .messagesTitle(lhsTheme, lhsText):
if case let .messagesTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .messagesGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .messagesGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .actionsTitle(lhsTheme, lhsText):
if case let .actionsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .actionsGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .actionsGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .topHoursTitle(lhsTheme, lhsText):
if case let .topHoursTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .topHoursGraph(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsGraph, lhsType):
if case let .topHoursGraph(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsGraph, rhsType) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsGraph == rhsGraph, lhsType == rhsType {
return true
} else {
return false
}
case let .topPostersTitle(lhsTheme, lhsText):
if case let .topPostersTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .topPoster(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopPoster):
if case let .topPoster(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopPoster) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopPoster == rhsTopPoster {
return true
} else {
return false
}
case let .topAdminsTitle(lhsTheme, lhsText):
if case let .topAdminsTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .topAdmin(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopAdmin):
if case let .topAdmin(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopAdmin) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopAdmin == rhsTopAdmin {
return true
} else {
return false
}
case let .topInvitersTitle(lhsTheme, lhsText):
if case let .topInvitersTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .topInviter(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsPeer, lhsTopInviter):
if case let .topInviter(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsPeer, rhsTopInviter) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, arePeersEqual(lhsPeer, rhsPeer), lhsTopInviter == rhsTopInviter {
return true
} else {
return false
}
}
}
static func <(lhs: StatsEntry, rhs: StatsEntry) -> Bool {
return lhs.stableId < rhs.stableId
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! GroupStatsControllerArguments
switch self {
case let .overviewHeader(_, text, dates):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: dates, color: .generic), sectionId: self.section)
case let .growthTitle(_, text),
let .membersTitle(_, text),
let .newMembersBySourceTitle(_, text),
let .languagesTitle(_, text),
let .messagesTitle(_, text),
let .actionsTitle(_, text),
let .topHoursTitle(_, text),
let .topPostersTitle(_, text),
let .topAdminsTitle(_, text),
let .topInvitersTitle(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .overview(_, stats):
return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks)
case let .growthGraph(_, _, _, graph, type),
let .membersGraph(_, _, _, graph, type),
let .newMembersBySourceGraph(_, _, _, graph, type),
let .languagesGraph(_, _, _, graph, type),
let .messagesGraph(_, _, _, graph, type),
let .actionsGraph(_, _, _, graph, type),
let .topHoursGraph(_, _, _, graph, type):
return StatsGraphItem(presentationData: presentationData, graph: graph, type: type, sectionId: self.section, style: .blocks)
case let .topPoster(_, _, strings, dateTimeFormat, peer, topPoster):
var textComponents: [String] = []
if topPoster.messageCount > 0 {
textComponents.append(strings.Stats_GroupTopPosterMessages(topPoster.messageCount))
if topPoster.averageChars > 0 {
textComponents.append(strings.Stats_GroupTopPosterChars(topPoster.averageChars))
}
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
case let .topAdmin(_, _, strings, dateTimeFormat, peer, topAdmin):
var textComponents: [String] = []
if topAdmin.deletedCount > 0 {
textComponents.append(strings.Stats_GroupTopAdminDeletions(topAdmin.deletedCount))
}
if topAdmin.kickedCount > 0 {
textComponents.append(strings.Stats_GroupTopAdminKicks(topAdmin.kickedCount))
}
if topAdmin.bannedCount > 0 {
textComponents.append(strings.Stats_GroupTopAdminBans(topAdmin.bannedCount))
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
case let .topInviter(_, _, strings, dateTimeFormat, peer, topInviter):
var textComponents: [String] = []
textComponents.append(strings.Stats_GroupTopInviterInvites(topInviter.inviteCount))
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: {
arguments.openPeer(peer.id)
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
}
}
}
private func groupStatsControllerEntries(data: GroupStats?, peers: [PeerId: Peer]?, presentationData: PresentationData) -> [StatsEntry] {
var entries: [StatsEntry] = []
if let data = data {
let minDate = stringForDate(timestamp: data.period.minDate, strings: presentationData.strings)
let maxDate = stringForDate(timestamp: data.period.maxDate, strings: presentationData.strings)
entries.append(.overviewHeader(presentationData.theme, presentationData.strings.Stats_Overview, "\(minDate) \(maxDate)"))
entries.append(.overview(presentationData.theme, data))
if !data.growthGraph.isEmpty {
entries.append(.growthTitle(presentationData.theme, presentationData.strings.Stats_GroupGrowthTitle))
entries.append(.growthGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.growthGraph, .lines))
}
if !data.membersGraph.isEmpty {
entries.append(.membersTitle(presentationData.theme, presentationData.strings.Stats_GroupMembersTitle))
entries.append(.membersGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.membersGraph, .lines))
}
if !data.newMembersBySourceGraph.isEmpty {
entries.append(.newMembersBySourceTitle(presentationData.theme, presentationData.strings.Stats_GroupNewMembersBySourceTitle))
entries.append(.newMembersBySourceGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.newMembersBySourceGraph, .bars))
}
if !data.languagesGraph.isEmpty {
entries.append(.languagesTitle(presentationData.theme, presentationData.strings.Stats_GroupLanguagesTitle))
entries.append(.languagesGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.languagesGraph, .pie))
}
if !data.messagesGraph.isEmpty {
entries.append(.messagesTitle(presentationData.theme, presentationData.strings.Stats_GroupMessagesTitle))
entries.append(.messagesGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.messagesGraph, .bars))
}
if !data.actionsGraph.isEmpty {
entries.append(.actionsTitle(presentationData.theme, presentationData.strings.Stats_GroupActionsTitle))
entries.append(.actionsGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.actionsGraph, .lines))
}
if !data.topHoursGraph.isEmpty {
entries.append(.topHoursTitle(presentationData.theme, presentationData.strings.Stats_GroupTopHoursTitle))
entries.append(.topHoursGraph(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, data.topHoursGraph, .lines))
}
if let peers = peers {
if !data.topPosters.isEmpty {
entries.append(.topPostersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopPostersTitle))
var index: Int32 = 0
for topPoster in data.topPosters {
if let peer = peers[topPoster.peerId] {
entries.append(.topPoster(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topPoster))
index += 1
}
}
}
if !data.topAdmins.isEmpty {
entries.append(.topAdminsTitle(presentationData.theme, presentationData.strings.Stats_GroupTopAdminsTitle))
var index: Int32 = 0
for topAdmin in data.topAdmins {
if let peer = peers[topAdmin.peerId] {
entries.append(.topAdmin(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topAdmin))
index += 1
}
}
}
if !data.topInviters.isEmpty {
entries.append(.topInvitersTitle(presentationData.theme, presentationData.strings.Stats_GroupTopInvitersTitle))
var index: Int32 = 0
for topInviter in data.topInviters {
if let peer = peers[topInviter.peerId] {
entries.append(.topInviter(index, presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, topInviter))
index += 1
}
}
}
}
}
return entries
}
public func groupStatsController(context: AccountContext, peerId: PeerId, cachedPeerData: CachedPeerData) -> ViewController {
var openPeerImpl: ((PeerId) -> Void)?
let actionsDisposable = DisposableSet()
let dataPromise = Promise<GroupStats?>(nil)
let peersPromise = Promise<[PeerId: Peer]?>(nil)
var datacenterId: Int32 = 0
if let cachedData = cachedPeerData as? CachedChannelData {
datacenterId = cachedData.statsDatacenterId
}
let statsContext = GroupStatsContext(postbox: context.account.postbox, network: context.account.network, datacenterId: datacenterId, peerId: peerId)
let dataSignal: Signal<GroupStats?, NoError> = statsContext.state
|> map { state in
return state.stats
} |> afterNext({ [weak statsContext] stats in
if let statsContext = statsContext, let stats = stats {
if case .OnDemand = stats.newMembersBySourceGraph {
statsContext.loadGrowthGraph()
statsContext.loadMembersGraph()
statsContext.loadNewMembersBySourceGraph()
statsContext.loadLanguagesGraph()
statsContext.loadMessagesGraph()
statsContext.loadActionsGraph()
statsContext.loadTopHoursGraph()
}
}
})
dataPromise.set(.single(nil) |> then(dataSignal))
peersPromise.set(.single(nil) |> then(dataPromise.get()
|> filter { value in
return value != nil
}
|> take(1)
|> map { stats -> [PeerId]? in
guard let stats = stats else {
return nil
}
var peerIds = Set<PeerId>()
peerIds.formUnion(stats.topPosters.map { $0.peerId })
peerIds.formUnion(stats.topAdmins.map { $0.peerId })
peerIds.formUnion(stats.topInviters.map { $0.peerId })
return Array(peerIds)
}
|> mapToSignal { peerIds -> Signal<[PeerId: Peer]?, NoError> in
return context.account.postbox.transaction { transaction -> [PeerId: Peer]? in
var peers: [PeerId: Peer] = [:]
if let peerIds = peerIds {
for peerId in peerIds {
if let peer = transaction.getPeer(peerId) {
peers[peerId] = peer
}
}
}
return peers
}
}))
let arguments = GroupStatsControllerArguments(context: context, loadDetailedGraph: { graph, x -> Signal<StatsGraph?, NoError> in
return statsContext.loadDetailedGraph(graph, x: x)
}, openPeer: { peerId in
openPeerImpl?(peerId)
})
let longLoadingSignal: Signal<Bool, NoError> = .single(false) |> then(.single(true) |> delay(2.0, queue: Queue.mainQueue()))
let previousData = Atomic<GroupStats?>(value: nil)
let signal = combineLatest(context.sharedContext.presentationData, dataPromise.get(), peersPromise.get(), longLoadingSignal)
|> deliverOnMainQueue
|> map { presentationData, data, peers, longLoading -> (ItemListControllerState, (ItemListNodeState, Any)) in
let previous = previousData.swap(data)
var emptyStateItem: ItemListControllerEmptyStateItem?
if data == nil {
if longLoading {
emptyStateItem = StatsEmptyStateItem(theme: presentationData.theme, strings: presentationData.strings)
} else {
emptyStateItem = ItemListLoadingIndicatorEmptyStateItem(theme: presentationData.theme)
}
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ChannelInfo_Stats), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: groupStatsControllerEntries(data: data, peers: peers, presentationData: presentationData), style: .blocks, emptyStateItem: emptyStateItem, crossfadeState: previous == nil, animateChanges: false)
return (controllerState, (listState, arguments))
}
|> afterDisposed {
actionsDisposable.dispose()
let _ = statsContext.state
}
let controller = ItemListController(context: context, state: signal)
controller.contentOffsetChanged = { [weak controller] _, _ in
controller?.forEachItemNode({ itemNode in
if let itemNode = itemNode as? StatsGraphItemNode {
itemNode.resetInteraction()
}
})
}
controller.didDisappear = { [weak controller] _ in
controller?.clearItemNodesHighlight(animated: true)
}
openPeerImpl = { [weak controller] peerId in
if let navigationController = controller?.navigationController as? NavigationController {
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> take(1)
|> deliverOnMainQueue).start(next: { peer in
if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
navigationController.pushViewController(controller)
}
})
}
}
return controller
}

View File

@ -14,13 +14,13 @@ import ActivityIndicator
class StatsGraphItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let graph: ChannelStatsGraph
let graph: StatsGraph
let type: ChartType
let getDetailsData: ((Date, @escaping (String?) -> Void) -> Void)?
let sectionId: ItemListSectionId
let style: ItemListStyle
init(presentationData: ItemListPresentationData, graph: ChannelStatsGraph, type: ChartType, getDetailsData: ((Date, @escaping (String?) -> Void) -> Void)? = nil, sectionId: ItemListSectionId, style: ItemListStyle) {
init(presentationData: ItemListPresentationData, graph: StatsGraph, type: ChartType, getDetailsData: ((Date, @escaping (String?) -> Void) -> Void)? = nil, sectionId: ItemListSectionId, style: ItemListStyle) {
self.presentationData = presentationData
self.graph = graph
self.type = type
@ -126,7 +126,7 @@ class StatsGraphItemNode: ListViewItemNode {
let leftInset = params.leftInset
let rightInset: CGFloat = params.rightInset
var updatedTheme: PresentationTheme?
var updatedGraph: ChannelStatsGraph?
var updatedGraph: StatsGraph?
var updatedController: BaseChartController?
if currentItem?.presentationData.theme !== item.presentationData.theme {

View File

@ -9,13 +9,25 @@ import TelegramPresentationData
import ItemListUI
import PresentationDataUtils
protocol PeerStats {
}
extension ChannelStats: PeerStats {
}
extension GroupStats: PeerStats {
}
class StatsOverviewItem: ListViewItem, ItemListItem {
let presentationData: ItemListPresentationData
let stats: ChannelStats
let stats: PeerStats
let sectionId: ItemListSectionId
let style: ItemListStyle
init(presentationData: ItemListPresentationData, stats: ChannelStats, sectionId: ItemListSectionId, style: ItemListStyle) {
init(presentationData: ItemListPresentationData, stats: PeerStats, sectionId: ItemListSectionId, style: ItemListStyle) {
self.presentationData = presentationData
self.stats = stats
self.sectionId = sectionId
@ -64,19 +76,20 @@ class StatsOverviewItemNode: ListViewItemNode {
private let bottomStripeNode: ASDisplayNode
private let maskNode: ASImageNode
private let followersValueLabel: ImmediateTextNode
private let viewsPerPostValueLabel: ImmediateTextNode
private let sharesPerPostValueLabel: ImmediateTextNode
private let enabledNotificationsValueLabel: ImmediateTextNode
private let topLeftValueLabel: ImmediateTextNode
private let bottomLeftValueLabel: ImmediateTextNode
private let bottomRightValueLabel: ImmediateTextNode
private let topRightValueLabel: ImmediateTextNode
private let followersTitleLabel: ImmediateTextNode
private let viewsPerPostTitleLabel: ImmediateTextNode
private let sharesPerPostTitleLabel: ImmediateTextNode
private let enabledNotificationsTitleLabel: ImmediateTextNode
private let topLeftTitleLabel: ImmediateTextNode
private let bottomLeftTitleLabel: ImmediateTextNode
private let bottomRightTitleLabel: ImmediateTextNode
private let topRightTitleLabel: ImmediateTextNode
private let followersDeltaLabel: ImmediateTextNode
private let viewsPerPostDeltaLabel: ImmediateTextNode
private let sharesPerPostDeltaLabel: ImmediateTextNode
private let topLeftDeltaLabel: ImmediateTextNode
private let bottomLeftDeltaLabel: ImmediateTextNode
private let bottomRightDeltaLabel: ImmediateTextNode
private let topRightDeltaLabel: ImmediateTextNode
private var item: StatsOverviewItem?
@ -93,129 +106,60 @@ class StatsOverviewItemNode: ListViewItemNode {
self.maskNode = ASImageNode()
self.followersValueLabel = ImmediateTextNode()
self.viewsPerPostValueLabel = ImmediateTextNode()
self.sharesPerPostValueLabel = ImmediateTextNode()
self.enabledNotificationsValueLabel = ImmediateTextNode()
self.topLeftValueLabel = ImmediateTextNode()
self.bottomLeftValueLabel = ImmediateTextNode()
self.bottomRightValueLabel = ImmediateTextNode()
self.topRightValueLabel = ImmediateTextNode()
self.followersTitleLabel = ImmediateTextNode()
self.viewsPerPostTitleLabel = ImmediateTextNode()
self.sharesPerPostTitleLabel = ImmediateTextNode()
self.enabledNotificationsTitleLabel = ImmediateTextNode()
self.topLeftTitleLabel = ImmediateTextNode()
self.bottomLeftTitleLabel = ImmediateTextNode()
self.bottomRightTitleLabel = ImmediateTextNode()
self.topRightTitleLabel = ImmediateTextNode()
self.followersDeltaLabel = ImmediateTextNode()
self.viewsPerPostDeltaLabel = ImmediateTextNode()
self.sharesPerPostDeltaLabel = ImmediateTextNode()
self.topLeftDeltaLabel = ImmediateTextNode()
self.bottomLeftDeltaLabel = ImmediateTextNode()
self.bottomRightDeltaLabel = ImmediateTextNode()
self.topRightDeltaLabel = ImmediateTextNode()
super.init(layerBacked: false, dynamicBounce: false)
self.clipsToBounds = true
self.addSubnode(self.followersValueLabel)
self.addSubnode(self.viewsPerPostValueLabel)
self.addSubnode(self.sharesPerPostValueLabel)
self.addSubnode(self.enabledNotificationsValueLabel)
self.addSubnode(self.topLeftValueLabel)
self.addSubnode(self.bottomLeftValueLabel)
self.addSubnode(self.bottomRightValueLabel)
self.addSubnode(self.topRightValueLabel)
self.addSubnode(self.followersTitleLabel)
self.addSubnode(self.viewsPerPostTitleLabel)
self.addSubnode(self.sharesPerPostTitleLabel)
self.addSubnode(self.enabledNotificationsTitleLabel)
self.addSubnode(self.topLeftTitleLabel)
self.addSubnode(self.bottomLeftTitleLabel)
self.addSubnode(self.bottomRightTitleLabel)
self.addSubnode(self.topRightTitleLabel)
self.addSubnode(self.followersDeltaLabel)
self.addSubnode(self.viewsPerPostDeltaLabel)
self.addSubnode(self.sharesPerPostDeltaLabel)
self.addSubnode(self.topLeftDeltaLabel)
self.addSubnode(self.bottomLeftDeltaLabel)
self.addSubnode(self.bottomRightDeltaLabel)
self.addSubnode(self.topRightDeltaLabel)
}
func asyncLayout() -> (_ item: StatsOverviewItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeFollowersValueLabelLayout = TextNode.asyncLayout(self.followersValueLabel)
let makeViewsPerPostValueLabelLayout = TextNode.asyncLayout(self.viewsPerPostValueLabel)
let makeSharesPerPostValueLabelLayout = TextNode.asyncLayout(self.sharesPerPostValueLabel)
let makeEnabledNotificationsValueLabelLayout = TextNode.asyncLayout(self.enabledNotificationsValueLabel)
let makeTopLeftValueLabelLayout = TextNode.asyncLayout(self.topLeftValueLabel)
let makeTopRightValueLabelLayout = TextNode.asyncLayout(self.topRightValueLabel)
let makeBottomLeftValueLabelLayout = TextNode.asyncLayout(self.bottomLeftValueLabel)
let makeBottomRightValueLabelLayout = TextNode.asyncLayout(self.bottomRightValueLabel)
let makeFollowersTitleLabelLayout = TextNode.asyncLayout(self.followersTitleLabel)
let makeViewsPerPostTitleLabelLayout = TextNode.asyncLayout(self.viewsPerPostTitleLabel)
let makeSharesPerPostTitleLabelLayout = TextNode.asyncLayout(self.sharesPerPostTitleLabel)
let makeEnabledNotificationsTitleLabelLayout = TextNode.asyncLayout(self.enabledNotificationsTitleLabel)
let makeTopLeftTitleLabelLayout = TextNode.asyncLayout(self.topLeftTitleLabel)
let makeTopRightTitleLabelLayout = TextNode.asyncLayout(self.topRightTitleLabel)
let makeBottomLeftTitleLabelLayout = TextNode.asyncLayout(self.bottomLeftTitleLabel)
let makeBottomRightTitleLabelLayout = TextNode.asyncLayout(self.bottomRightTitleLabel)
let makeFollowersDeltaLabelLayout = TextNode.asyncLayout(self.followersDeltaLabel)
let makeViewsPerPostDeltaLabelLayout = TextNode.asyncLayout(self.viewsPerPostDeltaLabel)
let makeSharesPerPostDeltaLabelLayout = TextNode.asyncLayout(self.sharesPerPostDeltaLabel)
let makeTopLeftDeltaLabelLayout = TextNode.asyncLayout(self.topLeftDeltaLabel)
let makeTopRightDeltaLabelLayout = TextNode.asyncLayout(self.topRightDeltaLabel)
let makeBottomLeftDeltaLabelLayout = TextNode.asyncLayout(self.bottomLeftDeltaLabel)
let makeBottomRightDeltaLabelLayout = TextNode.asyncLayout(self.bottomRightDeltaLabel)
let currentItem = self.item
return { item, params, neighbors in
let leftInset = params.leftInset
let rightInset: CGFloat = params.rightInset
var updatedTheme: PresentationTheme?
if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.presentationData.theme
}
let valueFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let deltaFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let displayInteractions = item.stats.sharesPerPost.current > 0 || item.stats.viewsPerPost.current > 0
let (followersValueLabelLayout, followersValueLabelApply) = makeFollowersValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(Int(item.stats.followers.current)), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (viewsPerPostValueLabelLayout, viewsPerPostValueLabelApply) = makeViewsPerPostValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayInteractions ? compactNumericCountString(Int(item.stats.viewsPerPost.current)) : "", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (sharesPerPostValueLabelLayout, sharesPerPostValueLabelApply) = makeSharesPerPostValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayInteractions ? compactNumericCountString(Int(item.stats.sharesPerPost.current)) : "", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var enabledNotifications: Double = 0.0
if item.stats.enabledNotifications.total > 0 {
enabledNotifications = item.stats.enabledNotifications.value / item.stats.enabledNotifications.total
}
let (enabledNotificationsValueLabelLayout, enabledNotificationsValueLabelApply) = makeEnabledNotificationsValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: String(format: "%.02f%%", enabledNotifications * 100.0), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (followersTitleLabelLayout, followersTitleLabelApply) = makeFollowersTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_Followers, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (viewsPerPostTitleLabelLayout, viewsPerPostTitleLabelApply) = makeViewsPerPostTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayInteractions ? item.presentationData.strings.Stats_ViewsPerPost : "", font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (sharesPerPostTitleLabelLayout, sharesPerPostTitleLabelApply) = makeSharesPerPostTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayInteractions ? item.presentationData.strings.Stats_SharesPerPost : "", font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (enabledNotificationsTitleLabelLayout, enabledNotificationsTitleLabelApply) = makeEnabledNotificationsTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_EnabledNotifications, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let followersDeltaValue = item.stats.followers.current - item.stats.followers.previous
let followersDeltaCompact = compactNumericCountString(abs(Int(followersDeltaValue)))
let followersDelta = followersDeltaValue > 0 ? "+\(followersDeltaCompact)" : "-\(followersDeltaCompact)"
var followersDeltaPercentage = 0.0
if item.stats.followers.previous > 0.0 {
followersDeltaPercentage = abs(followersDeltaValue / item.stats.followers.previous)
}
let followersDeltaText = abs(followersDeltaPercentage) > 0.0 ? String(format: "%@ (%.02f%%)", followersDelta, followersDeltaPercentage * 100.0) : ""
let (followersDeltaLabelLayout, followersDeltaLabelApply) = makeFollowersDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: followersDeltaText, font: deltaFont, textColor: followersDeltaValue > 0.0 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let viewsPerPostDeltaValue = item.stats.viewsPerPost.current - item.stats.viewsPerPost.previous
let viewsPerPostDeltaCompact = compactNumericCountString(abs(Int(viewsPerPostDeltaValue)))
let viewsPerPostDelta = viewsPerPostDeltaValue > 0 ? "+\(viewsPerPostDeltaCompact)" : "-\(viewsPerPostDeltaCompact)"
var viewsPerPostDeltaPercentage = 0.0
if item.stats.viewsPerPost.previous > 0.0 {
viewsPerPostDeltaPercentage = abs(viewsPerPostDeltaValue / item.stats.viewsPerPost.previous)
}
let viewsPerPostDeltaText = abs(viewsPerPostDeltaPercentage) > 0.0 && displayInteractions ? String(format: "%@ (%.02f%%)", viewsPerPostDelta, viewsPerPostDeltaPercentage * 100.0) : ""
let (viewsPerPostDeltaLabelLayout, viewsPerPostDeltaLabelApply) = makeViewsPerPostDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: viewsPerPostDeltaText, font: deltaFont, textColor: viewsPerPostDeltaValue > 0.0 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let sharesPerPostDeltaValue = item.stats.sharesPerPost.current - item.stats.sharesPerPost.previous
let sharesPerPostDeltaCompact = compactNumericCountString(abs(Int(sharesPerPostDeltaValue)))
let sharesPerPostDelta = sharesPerPostDeltaValue > 0 ? "+\(sharesPerPostDeltaCompact)" : "-\(sharesPerPostDeltaCompact)"
var sharesPerPostDeltaPercentage = 0.0
if item.stats.sharesPerPost.previous > 0.0 {
sharesPerPostDeltaPercentage = abs(sharesPerPostDeltaValue / item.stats.sharesPerPost.previous)
}
let sharesPerPostDeltaText = abs(sharesPerPostDeltaPercentage) > 0.0 && displayInteractions ? String(format: "%@ (%.02f%%)", sharesPerPostDelta, sharesPerPostDeltaPercentage * 100.0) : ""
let (sharesPerPostDeltaLabelLayout, sharesPerPostDeltaLabelApply) = makeSharesPerPostDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: sharesPerPostDeltaText, font: deltaFont, textColor: sharesPerPostDeltaValue > 0.0 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
let itemBackgroundColor: UIColor
@ -227,63 +171,207 @@ class StatsOverviewItemNode: ListViewItemNode {
let sideInset: CGFloat = 16.0
var height: CGFloat = topInset * 2.0
height += enabledNotificationsValueLabelLayout.size.height + enabledNotificationsTitleLabelLayout.size.height
var twoColumnLayout = true
if max(followersValueLabelLayout.size.width + followersDeltaLabelLayout.size.width + horizontalSpacing + enabledNotificationsValueLabelLayout.size.width, viewsPerPostValueLabelLayout.size.width + viewsPerPostDeltaLabelLayout.size.width + horizontalSpacing + sharesPerPostValueLabelLayout.size.width + sharesPerPostDeltaLabelLayout.size.width) > params.width - leftInset - rightInset {
twoColumnLayout = false
}
let leftInset = params.leftInset
let rightInset: CGFloat = params.rightInset
var updatedTheme: PresentationTheme?
if twoColumnLayout {
if !item.stats.viewsPerPost.current.isZero || !item.stats.sharesPerPost.current.isZero {
height += verticalSpacing
height += sharesPerPostValueLabelLayout.size.height + sharesPerPostTitleLabelLayout.size.height
}
} else {
height += verticalSpacing
height += enabledNotificationsValueLabelLayout.size.height + enabledNotificationsTitleLabelLayout.size.height
if !item.stats.viewsPerPost.current.isZero {
height += verticalSpacing
height += viewsPerPostValueLabelLayout.size.height + viewsPerPostTitleLabelLayout.size.height
}
if !item.stats.sharesPerPost.current.isZero {
height += verticalSpacing
height += sharesPerPostValueLabelLayout.size.height + sharesPerPostTitleLabelLayout.size.height
}
if currentItem?.presentationData.theme !== item.presentationData.theme {
updatedTheme = item.presentationData.theme
}
switch item.style {
case .plain:
itemBackgroundColor = item.presentationData.theme.list.plainBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemPlainSeparatorColor
contentSize = CGSize(width: params.width, height: height)
insets = itemListNeighborsPlainInsets(neighbors)
case .blocks:
itemBackgroundColor = item.presentationData.theme.list.itemBlocksBackgroundColor
itemSeparatorColor = item.presentationData.theme.list.itemBlocksSeparatorColor
contentSize = CGSize(width: params.width, height: height)
insets = itemListNeighborsGroupedInsets(neighbors)
}
let valueFont = Font.semibold(item.presentationData.fontSize.itemListBaseFontSize)
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let deltaFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
let topLeftValueLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let topRightValueLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let bottomLeftValueLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let bottomRightValueLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let topLeftTitleLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let topRightTitleLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let bottomLeftTitleLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let bottomRightTitleLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let topLeftDeltaLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let topRightDeltaLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let bottomLeftDeltaLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
let bottomRightDeltaLabelLayoutAndApply: ((Display.TextNodeLayout, () -> Display.TextNode))?
var twoColumnLayout = true
func deltaText(_ value: StatsValue) -> (String, Bool) {
let deltaValue = value.current - value.previous
let deltaCompact = compactNumericCountString(abs(Int(deltaValue)))
let delta = deltaValue > 0 ? "+\(deltaCompact)" : "-\(deltaCompact)"
var deltaPercentage = 0.0
if value.previous > 0.0 {
deltaPercentage = abs(deltaValue / value.previous)
}
return (abs(deltaPercentage) > 0.0 ? String(format: "%@ (%.02f%%)", delta, deltaPercentage * 100.0) : "", deltaValue > 0.0)
}
if let stats = item.stats as? ChannelStats {
let displayBottomRow = stats.sharesPerPost.current > 0 || stats.viewsPerPost.current > 0
topLeftValueLabelLayoutAndApply = makeTopLeftValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(Int(stats.followers.current)), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var enabledNotifications: Double = 0.0
if stats.enabledNotifications.total > 0 {
enabledNotifications = stats.enabledNotifications.value / stats.enabledNotifications.total
}
topRightValueLabelLayoutAndApply = makeTopRightValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: String(format: "%.02f%%", enabledNotifications * 100.0), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
bottomLeftValueLabelLayoutAndApply = makeBottomLeftValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayBottomRow ? compactNumericCountString(Int(stats.viewsPerPost.current)) : "", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
bottomRightValueLabelLayoutAndApply = makeBottomRightValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayBottomRow ? compactNumericCountString(Int(stats.sharesPerPost.current)) : "", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
topLeftTitleLabelLayoutAndApply = makeTopLeftTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_Followers, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
topRightTitleLabelLayoutAndApply = makeTopRightTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_EnabledNotifications, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
bottomLeftTitleLabelLayoutAndApply = makeBottomLeftTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayBottomRow ? item.presentationData.strings.Stats_ViewsPerPost : "", font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
bottomRightTitleLabelLayoutAndApply = makeBottomRightTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayBottomRow ? item.presentationData.strings.Stats_SharesPerPost : "", font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let followersDelta = deltaText(stats.followers)
topLeftDeltaLabelLayoutAndApply = makeTopLeftDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: followersDelta.0, font: deltaFont, textColor: followersDelta.1 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
topRightDeltaLabelLayoutAndApply = nil
let viewsPerPostDelta = deltaText(stats.viewsPerPost)
bottomLeftDeltaLabelLayoutAndApply = makeBottomLeftDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: viewsPerPostDelta.0, font: deltaFont, textColor: viewsPerPostDelta.1 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let sharesPerPostDelta = deltaText(stats.sharesPerPost)
bottomRightDeltaLabelLayoutAndApply = makeBottomRightDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: sharesPerPostDelta.0, font: deltaFont, textColor: sharesPerPostDelta.1 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
height += topRightValueLabelLayoutAndApply!.0.size.height + topRightTitleLabelLayoutAndApply!.0.size.height
if max(topLeftValueLabelLayoutAndApply!.0.size.width + topLeftDeltaLabelLayoutAndApply!.0.size.width + horizontalSpacing + topRightValueLabelLayoutAndApply!.0.size.width, bottomLeftValueLabelLayoutAndApply!.0.size.width + bottomLeftDeltaLabelLayoutAndApply!.0.size.width + horizontalSpacing + bottomRightValueLabelLayoutAndApply!.0.size.width + bottomRightDeltaLabelLayoutAndApply!.0.size.width) > params.width - leftInset - rightInset {
twoColumnLayout = false
}
if twoColumnLayout {
if !stats.viewsPerPost.current.isZero || !stats.sharesPerPost.current.isZero {
height += verticalSpacing
height += bottomRightValueLabelLayoutAndApply!.0.size.height + bottomRightTitleLabelLayoutAndApply!.0.size.height
}
} else {
height += verticalSpacing
height += topRightValueLabelLayoutAndApply!.0.size.height + topRightTitleLabelLayoutAndApply!.0.size.height
if !stats.viewsPerPost.current.isZero {
height += verticalSpacing
height += bottomLeftValueLabelLayoutAndApply!.0.size.height + bottomLeftTitleLabelLayoutAndApply!.0.size.height
}
if !stats.sharesPerPost.current.isZero {
height += verticalSpacing
height += bottomRightValueLabelLayoutAndApply!.0.size.height + bottomRightTitleLabelLayoutAndApply!.0.size.height
}
}
} else if let stats = item.stats as? GroupStats {
let displayBottomRow = stats.viewers.current > 0 || stats.posters.current > 0
topLeftValueLabelLayoutAndApply = makeTopLeftValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(Int(stats.members.current)), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
topRightValueLabelLayoutAndApply = makeTopRightValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: compactNumericCountString(Int(stats.messages.current)), font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
bottomLeftValueLabelLayoutAndApply = makeBottomLeftValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayBottomRow ? compactNumericCountString(Int(stats.viewers.current)) : "", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
bottomRightValueLabelLayoutAndApply = makeBottomRightValueLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayBottomRow ? compactNumericCountString(Int(stats.posters.current)) : "", font: valueFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
topLeftTitleLabelLayoutAndApply = makeTopLeftTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_GroupMembers, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
topRightTitleLabelLayoutAndApply = makeTopRightTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Stats_GroupMessages, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
bottomLeftTitleLabelLayoutAndApply = makeBottomLeftTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayBottomRow ? item.presentationData.strings.Stats_GroupViewers : "", font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
bottomRightTitleLabelLayoutAndApply = makeBottomRightTitleLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: displayBottomRow ? item.presentationData.strings.Stats_GroupPosters : "", font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let membersDelta = deltaText(stats.members)
topLeftDeltaLabelLayoutAndApply = makeTopLeftDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: membersDelta.0, font: deltaFont, textColor: membersDelta.1 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let messagesDelta = deltaText(stats.messages)
topRightDeltaLabelLayoutAndApply = makeTopRightDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: messagesDelta.0, font: deltaFont, textColor: messagesDelta.1 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let viewersDelta = deltaText(stats.viewers)
bottomLeftDeltaLabelLayoutAndApply = makeBottomLeftDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: viewersDelta.0, font: deltaFont, textColor: viewersDelta.1 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let postersDelta = deltaText(stats.posters)
bottomRightDeltaLabelLayoutAndApply = makeBottomRightDeltaLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: postersDelta.0, font: deltaFont, textColor: postersDelta.1 ? item.presentationData.theme.list.freeTextSuccessColor : item.presentationData.theme.list.freeTextErrorColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
height += topRightValueLabelLayoutAndApply!.0.size.height + topRightTitleLabelLayoutAndApply!.0.size.height
if max(topLeftValueLabelLayoutAndApply!.0.size.width + topLeftDeltaLabelLayoutAndApply!.0.size.width + horizontalSpacing + topRightValueLabelLayoutAndApply!.0.size.width, bottomLeftValueLabelLayoutAndApply!.0.size.width + bottomLeftDeltaLabelLayoutAndApply!.0.size.width + horizontalSpacing + bottomRightValueLabelLayoutAndApply!.0.size.width + bottomRightDeltaLabelLayoutAndApply!.0.size.width) > params.width - leftInset - rightInset {
twoColumnLayout = false
}
if twoColumnLayout {
if !stats.viewers.current.isZero || !stats.posters.current.isZero {
height += verticalSpacing
height += bottomRightValueLabelLayoutAndApply!.0.size.height + bottomRightTitleLabelLayoutAndApply!.0.size.height
}
} else {
height += verticalSpacing
height += topRightValueLabelLayoutAndApply!.0.size.height + topRightTitleLabelLayoutAndApply!.0.size.height
if !stats.viewers.current.isZero {
height += verticalSpacing
height += bottomLeftValueLabelLayoutAndApply!.0.size.height + bottomLeftTitleLabelLayoutAndApply!.0.size.height
}
if !stats.posters.current.isZero {
height += verticalSpacing
height += bottomRightValueLabelLayoutAndApply!.0.size.height + bottomRightTitleLabelLayoutAndApply!.0.size.height
}
}
} else {
topLeftValueLabelLayoutAndApply = nil
topRightValueLabelLayoutAndApply = nil
bottomLeftValueLabelLayoutAndApply = nil
bottomRightValueLabelLayoutAndApply = nil
topLeftTitleLabelLayoutAndApply = nil
topRightTitleLabelLayoutAndApply = nil
bottomLeftTitleLabelLayoutAndApply = nil
bottomRightTitleLabelLayoutAndApply = nil
topLeftDeltaLabelLayoutAndApply = nil
topRightDeltaLabelLayoutAndApply = nil
bottomLeftDeltaLabelLayoutAndApply = nil
bottomRightDeltaLabelLayoutAndApply = nil
}
let contentSize = CGSize(width: params.width, height: height)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
if let strongSelf = self {
strongSelf.item = item
let _ = followersValueLabelApply()
let _ = viewsPerPostValueLabelApply()
let _ = sharesPerPostValueLabelApply()
let _ = enabledNotificationsValueLabelApply()
let _ = followersTitleLabelApply()
let _ = viewsPerPostTitleLabelApply()
let _ = sharesPerPostTitleLabelApply()
let _ = enabledNotificationsTitleLabelApply()
let _ = followersDeltaLabelApply()
let _ = viewsPerPostDeltaLabelApply()
let _ = sharesPerPostDeltaLabelApply()
let _ = topLeftValueLabelLayoutAndApply?.1()
let _ = topRightValueLabelLayoutAndApply?.1()
let _ = bottomLeftValueLabelLayoutAndApply?.1()
let _ = bottomRightValueLabelLayoutAndApply?.1()
let _ = topLeftTitleLabelLayoutAndApply?.1()
let _ = topRightTitleLabelLayoutAndApply?.1()
let _ = bottomLeftTitleLabelLayoutAndApply?.1()
let _ = bottomRightTitleLabelLayoutAndApply?.1()
let _ = topLeftDeltaLabelLayoutAndApply?.1()
let _ = topRightDeltaLabelLayoutAndApply?.1()
let _ = bottomLeftDeltaLabelLayoutAndApply?.1()
let _ = bottomRightDeltaLabelLayoutAndApply?.1()
if let _ = updatedTheme {
strongSelf.topStripeNode.backgroundColor = itemSeparatorColor
@ -348,25 +436,48 @@ class StatsOverviewItemNode: ListViewItemNode {
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - bottomStripeInset, height: separatorHeight))
}
strongSelf.followersValueLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: topInset), size: followersValueLabelLayout.size)
strongSelf.followersTitleLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: strongSelf.followersValueLabel.frame.maxY), size: followersTitleLabelLayout.size)
strongSelf.followersDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.followersValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.followersValueLabel.frame.maxY - followersDeltaLabelLayout.size.height - 2.0), size: followersDeltaLabelLayout.size)
var secondColumnX = sideInset + leftInset
let secondColumnX = twoColumnLayout ? max(layout.size.width / 2.0, sideInset + leftInset + max(followersValueLabelLayout.size.width + followersDeltaLabelLayout.size.width, viewsPerPostValueLabelLayout.size.width + viewsPerPostDeltaLabelLayout.size.width) + horizontalSpacing) : sideInset + leftInset
if let topLeftValueLabelLayout = topLeftValueLabelLayoutAndApply?.0, let topLeftTitleLabelLayout = topLeftTitleLabelLayoutAndApply?.0 {
strongSelf.topLeftValueLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: topInset), size: topLeftValueLabelLayout.size)
strongSelf.topLeftTitleLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: strongSelf.topLeftValueLabel.frame.maxY), size: topLeftTitleLabelLayout.size)
if twoColumnLayout {
let topWidth = topLeftValueLabelLayout.size.width + (topLeftDeltaLabelLayoutAndApply?.0.size.width ?? 0)
let bottomWidth = (bottomLeftValueLabelLayoutAndApply?.0.size.width ?? 0.0) + (bottomLeftDeltaLabelLayoutAndApply?.0.size.width ?? 0.0)
secondColumnX = max(layout.size.width / 2.0, sideInset + leftInset + max(topWidth, bottomWidth) + horizontalSpacing)
}
}
if let topLeftDeltaLabelLayout = topLeftDeltaLabelLayoutAndApply?.0 {
strongSelf.topLeftDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.topLeftValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.topLeftValueLabel.frame.maxY - topLeftDeltaLabelLayout.size.height - 2.0), size: topLeftDeltaLabelLayout.size)
}
if let topRightValueLabelLayout = topRightValueLabelLayoutAndApply?.0, let topRightTitleLabelLayout = topRightTitleLabelLayoutAndApply?.0 {
let topRightY = twoColumnLayout ? topInset : strongSelf.topLeftTitleLabel.frame.maxY + verticalSpacing
strongSelf.topRightValueLabel.frame = CGRect(origin: CGPoint(x: secondColumnX, y: topRightY), size: topRightValueLabelLayout.size)
strongSelf.topRightTitleLabel.frame = CGRect(origin: CGPoint(x: secondColumnX, y: strongSelf.topRightValueLabel.frame.maxY), size: topRightTitleLabelLayout.size)
}
if let topRightDeltaLabelLayout = topRightDeltaLabelLayoutAndApply?.0 {
strongSelf.topRightDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.topRightValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.topRightValueLabel.frame.maxY - topRightDeltaLabelLayout.size.height - 2.0), size: topRightDeltaLabelLayout.size)
}
let enabledNotificationsY = twoColumnLayout ? topInset : strongSelf.followersTitleLabel.frame.maxY + verticalSpacing
strongSelf.enabledNotificationsValueLabel.frame = CGRect(origin: CGPoint(x: secondColumnX, y: enabledNotificationsY), size: enabledNotificationsValueLabelLayout.size)
strongSelf.enabledNotificationsTitleLabel.frame = CGRect(origin: CGPoint(x: secondColumnX, y: strongSelf.enabledNotificationsValueLabel.frame.maxY), size: enabledNotificationsTitleLabelLayout.size)
if let bottomLeftValueLabelLayout = bottomLeftValueLabelLayoutAndApply?.0, let bottomLeftTitleLabelLayout = bottomLeftTitleLabelLayoutAndApply?.0 {
let bottomLeftY = twoColumnLayout ? strongSelf.topLeftTitleLabel.frame.maxY + verticalSpacing : strongSelf.topRightTitleLabel.frame.maxY + verticalSpacing
strongSelf.bottomLeftValueLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: bottomLeftY), size: bottomLeftValueLabelLayout.size)
strongSelf.bottomLeftTitleLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: strongSelf.bottomLeftValueLabel.frame.maxY), size: bottomLeftTitleLabelLayout.size)
}
if let bottomLeftDeltaLabelLayout = bottomLeftDeltaLabelLayoutAndApply?.0 {
strongSelf.bottomLeftDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.bottomLeftValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.bottomLeftValueLabel.frame.maxY - bottomLeftDeltaLabelLayout.size.height - 2.0), size: bottomLeftDeltaLabelLayout.size)
}
let viewsPerPostY = twoColumnLayout ? strongSelf.followersTitleLabel.frame.maxY + verticalSpacing : strongSelf.enabledNotificationsTitleLabel.frame.maxY + verticalSpacing
strongSelf.viewsPerPostValueLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: viewsPerPostY), size: viewsPerPostValueLabelLayout.size)
strongSelf.viewsPerPostTitleLabel.frame = CGRect(origin: CGPoint(x: sideInset + leftInset, y: strongSelf.viewsPerPostValueLabel.frame.maxY), size: viewsPerPostTitleLabelLayout.size)
strongSelf.viewsPerPostDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.viewsPerPostValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.viewsPerPostValueLabel.frame.maxY - viewsPerPostDeltaLabelLayout.size.height - 2.0), size: viewsPerPostDeltaLabelLayout.size)
let sharesPerPostY = twoColumnLayout ? strongSelf.enabledNotificationsTitleLabel.frame.maxY + verticalSpacing : strongSelf.viewsPerPostTitleLabel.frame.maxY + verticalSpacing
strongSelf.sharesPerPostValueLabel.frame = CGRect(origin: CGPoint(x: secondColumnX, y: sharesPerPostY), size: sharesPerPostValueLabelLayout.size)
strongSelf.sharesPerPostTitleLabel.frame = CGRect(origin: CGPoint(x: secondColumnX, y: strongSelf.sharesPerPostValueLabel.frame.maxY), size: sharesPerPostTitleLabelLayout.size)
strongSelf.sharesPerPostDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.sharesPerPostValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.sharesPerPostValueLabel.frame.maxY - sharesPerPostDeltaLabelLayout.size.height - 2.0), size: sharesPerPostDeltaLabelLayout.size)
if let bottomRightValueLabelLayout = bottomRightValueLabelLayoutAndApply?.0, let bottomRightTitleLabelLayout = bottomRightTitleLabelLayoutAndApply?.0 {
let bottomRightY = twoColumnLayout ? strongSelf.topRightTitleLabel.frame.maxY + verticalSpacing : strongSelf.bottomLeftTitleLabel.frame.maxY + verticalSpacing
strongSelf.bottomRightValueLabel.frame = CGRect(origin: CGPoint(x: secondColumnX, y: bottomRightY), size: bottomRightValueLabelLayout.size)
strongSelf.bottomRightTitleLabel.frame = CGRect(origin: CGPoint(x: secondColumnX, y: strongSelf.bottomRightValueLabel.frame.maxY), size: bottomRightTitleLabelLayout.size)
}
if let bottomRightDeltaLabelLayout = bottomRightDeltaLabelLayoutAndApply?.0 {
strongSelf.bottomRightDeltaLabel.frame = CGRect(origin: CGPoint(x: strongSelf.bottomRightValueLabel.frame.maxX + horizontalSpacing, y: strongSelf.bottomRightValueLabel.frame.maxY - bottomRightDeltaLabelLayout.size.height - 2.0), size: bottomRightDeltaLabelLayout.size)
}
}
})
}

View File

@ -112,6 +112,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-209337866] = { return Api.LangPackDifference.parse_langPackDifference($0) }
dict[84438264] = { return Api.WallPaperSettings.parse_wallPaperSettings($0) }
dict[1152191385] = { return Api.EmojiURL.parse_EmojiURL($0) }
dict[1611985938] = { return Api.StatsGroupTopAdmin.parse_statsGroupTopAdmin($0) }
dict[-791039645] = { return Api.channels.ChannelParticipant.parse_channelParticipant($0) }
dict[-1736378792] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordEmpty($0) }
dict[-763367294] = { return Api.InputCheckPasswordSRP.parse_inputCheckPasswordSRP($0) }
@ -443,6 +444,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-524237339] = { return Api.PageTableRow.parse_pageTableRow($0) }
dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) }
dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) }
dict[418631927] = { return Api.StatsGroupTopPoster.parse_statsGroupTopPoster($0) }
dict[-2128640689] = { return Api.account.SentEmailCode.parse_sentEmailCode($0) }
dict[-1038136962] = { return Api.EncryptedFile.parse_encryptedFileEmpty($0) }
dict[1248893260] = { return Api.EncryptedFile.parse_encryptedFile($0) }
@ -542,6 +544,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1918567619] = { return Api.Updates.parse_updatesCombined($0) }
dict[1957577280] = { return Api.Updates.parse_updates($0) }
dict[301019932] = { return Api.Updates.parse_updateShortSentMessage($0) }
dict[447818040] = { return Api.stats.MegagroupStats.parse_megagroupStats($0) }
dict[-884757282] = { return Api.StatsAbsValueAndPrev.parse_statsAbsValueAndPrev($0) }
dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) }
dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) }
@ -593,6 +596,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-2082087340] = { return Api.Message.parse_messageEmpty($0) }
dict[-1642487306] = { return Api.Message.parse_messageService($0) }
dict[1160515173] = { return Api.Message.parse_message($0) }
dict[831924812] = { return Api.StatsGroupTopInviter.parse_statsGroupTopInviter($0) }
dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) }
dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) }
dict[-182231723] = { return Api.InputFileLocation.parse_inputEncryptedFileLocation($0) }
@ -954,6 +958,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.EmojiURL:
_1.serialize(buffer, boxed)
case let _1 as Api.StatsGroupTopAdmin:
_1.serialize(buffer, boxed)
case let _1 as Api.channels.ChannelParticipant:
_1.serialize(buffer, boxed)
case let _1 as Api.InputCheckPasswordSRP:
@ -1136,6 +1142,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.DraftMessage:
_1.serialize(buffer, boxed)
case let _1 as Api.StatsGroupTopPoster:
_1.serialize(buffer, boxed)
case let _1 as Api.account.SentEmailCode:
_1.serialize(buffer, boxed)
case let _1 as Api.EncryptedFile:
@ -1240,6 +1248,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.Updates:
_1.serialize(buffer, boxed)
case let _1 as Api.stats.MegagroupStats:
_1.serialize(buffer, boxed)
case let _1 as Api.StatsAbsValueAndPrev:
_1.serialize(buffer, boxed)
case let _1 as Api.MessageMedia:
@ -1280,6 +1290,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.Message:
_1.serialize(buffer, boxed)
case let _1 as Api.StatsGroupTopInviter:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.RecentStickers:
_1.serialize(buffer, boxed)
case let _1 as Api.InputFileLocation:

View File

@ -4896,6 +4896,52 @@ public extension Api {
}
}
}
public enum StatsGroupTopAdmin: TypeConstructorDescription {
case statsGroupTopAdmin(userId: Int32, deleted: Int32, kicked: Int32, banned: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .statsGroupTopAdmin(let userId, let deleted, let kicked, let banned):
if boxed {
buffer.appendInt32(1611985938)
}
serializeInt32(userId, buffer: buffer, boxed: false)
serializeInt32(deleted, buffer: buffer, boxed: false)
serializeInt32(kicked, buffer: buffer, boxed: false)
serializeInt32(banned, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .statsGroupTopAdmin(let userId, let deleted, let kicked, let banned):
return ("statsGroupTopAdmin", [("userId", userId), ("deleted", deleted), ("kicked", kicked), ("banned", banned)])
}
}
public static func parse_statsGroupTopAdmin(_ reader: BufferReader) -> StatsGroupTopAdmin? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.StatsGroupTopAdmin.statsGroupTopAdmin(userId: _1!, deleted: _2!, kicked: _3!, banned: _4!)
}
else {
return nil
}
}
}
public enum InputCheckPasswordSRP: TypeConstructorDescription {
case inputCheckPasswordEmpty
@ -12822,6 +12868,48 @@ public extension Api {
}
}
}
public enum StatsGroupTopPoster: TypeConstructorDescription {
case statsGroupTopPoster(userId: Int32, messages: Int32, avgChars: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .statsGroupTopPoster(let userId, let messages, let avgChars):
if boxed {
buffer.appendInt32(418631927)
}
serializeInt32(userId, buffer: buffer, boxed: false)
serializeInt32(messages, buffer: buffer, boxed: false)
serializeInt32(avgChars, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .statsGroupTopPoster(let userId, let messages, let avgChars):
return ("statsGroupTopPoster", [("userId", userId), ("messages", messages), ("avgChars", avgChars)])
}
}
public static func parse_statsGroupTopPoster(_ reader: BufferReader) -> StatsGroupTopPoster? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.StatsGroupTopPoster.statsGroupTopPoster(userId: _1!, messages: _2!, avgChars: _3!)
}
else {
return nil
}
}
}
public enum EncryptedFile: TypeConstructorDescription {
case encryptedFileEmpty
@ -17028,6 +17116,44 @@ public extension Api {
}
}
}
public enum StatsGroupTopInviter: TypeConstructorDescription {
case statsGroupTopInviter(userId: Int32, invitations: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .statsGroupTopInviter(let userId, let invitations):
if boxed {
buffer.appendInt32(831924812)
}
serializeInt32(userId, buffer: buffer, boxed: false)
serializeInt32(invitations, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .statsGroupTopInviter(let userId, let invitations):
return ("statsGroupTopInviter", [("userId", userId), ("invitations", invitations)])
}
}
public static func parse_statsGroupTopInviter(_ reader: BufferReader) -> StatsGroupTopInviter? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.StatsGroupTopInviter.statsGroupTopInviter(userId: _1!, invitations: _2!)
}
else {
return nil
}
}
}
public enum InputFileLocation: TypeConstructorDescription {
case inputEncryptedFileLocation(id: Int64, accessHash: Int64)

View File

@ -662,6 +662,148 @@ public struct stats {
}
}
public enum MegagroupStats: TypeConstructorDescription {
case megagroupStats(period: Api.StatsDateRangeDays, members: Api.StatsAbsValueAndPrev, messages: Api.StatsAbsValueAndPrev, viewers: Api.StatsAbsValueAndPrev, posters: Api.StatsAbsValueAndPrev, growthGraph: Api.StatsGraph, membersGraph: Api.StatsGraph, newMembersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, messagesGraph: Api.StatsGraph, actionsGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, topPosters: [Api.StatsGroupTopPoster], topAdmins: [Api.StatsGroupTopAdmin], topInviters: [Api.StatsGroupTopInviter], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .megagroupStats(let period, let members, let messages, let viewers, let posters, let growthGraph, let membersGraph, let newMembersBySourceGraph, let languagesGraph, let messagesGraph, let actionsGraph, let topHoursGraph, let topPosters, let topAdmins, let topInviters, let users):
if boxed {
buffer.appendInt32(447818040)
}
period.serialize(buffer, true)
members.serialize(buffer, true)
messages.serialize(buffer, true)
viewers.serialize(buffer, true)
posters.serialize(buffer, true)
growthGraph.serialize(buffer, true)
membersGraph.serialize(buffer, true)
newMembersBySourceGraph.serialize(buffer, true)
languagesGraph.serialize(buffer, true)
messagesGraph.serialize(buffer, true)
actionsGraph.serialize(buffer, true)
topHoursGraph.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(topPosters.count))
for item in topPosters {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(topAdmins.count))
for item in topAdmins {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(topInviters.count))
for item in topInviters {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .megagroupStats(let period, let members, let messages, let viewers, let posters, let growthGraph, let membersGraph, let newMembersBySourceGraph, let languagesGraph, let messagesGraph, let actionsGraph, let topHoursGraph, let topPosters, let topAdmins, let topInviters, let users):
return ("megagroupStats", [("period", period), ("members", members), ("messages", messages), ("viewers", viewers), ("posters", posters), ("growthGraph", growthGraph), ("membersGraph", membersGraph), ("newMembersBySourceGraph", newMembersBySourceGraph), ("languagesGraph", languagesGraph), ("messagesGraph", messagesGraph), ("actionsGraph", actionsGraph), ("topHoursGraph", topHoursGraph), ("topPosters", topPosters), ("topAdmins", topAdmins), ("topInviters", topInviters), ("users", users)])
}
}
public static func parse_megagroupStats(_ reader: BufferReader) -> MegagroupStats? {
var _1: Api.StatsDateRangeDays?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays
}
var _2: Api.StatsAbsValueAndPrev?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
}
var _3: Api.StatsAbsValueAndPrev?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
}
var _4: Api.StatsAbsValueAndPrev?
if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
}
var _5: Api.StatsAbsValueAndPrev?
if let signature = reader.readInt32() {
_5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
}
var _6: Api.StatsGraph?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
var _7: Api.StatsGraph?
if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
var _8: Api.StatsGraph?
if let signature = reader.readInt32() {
_8 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
var _9: Api.StatsGraph?
if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
var _10: Api.StatsGraph?
if let signature = reader.readInt32() {
_10 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
var _11: Api.StatsGraph?
if let signature = reader.readInt32() {
_11 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
var _12: Api.StatsGraph?
if let signature = reader.readInt32() {
_12 = Api.parse(reader, signature: signature) as? Api.StatsGraph
}
var _13: [Api.StatsGroupTopPoster]?
if let _ = reader.readInt32() {
_13 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopPoster.self)
}
var _14: [Api.StatsGroupTopAdmin]?
if let _ = reader.readInt32() {
_14 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopAdmin.self)
}
var _15: [Api.StatsGroupTopInviter]?
if let _ = reader.readInt32() {
_15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsGroupTopInviter.self)
}
var _16: [Api.User]?
if let _ = reader.readInt32() {
_16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
let _c10 = _10 != nil
let _c11 = _11 != nil
let _c12 = _12 != nil
let _c13 = _13 != nil
let _c14 = _14 != nil
let _c15 = _15 != nil
let _c16 = _16 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 {
return Api.stats.MegagroupStats.megagroupStats(period: _1!, members: _2!, messages: _3!, viewers: _4!, posters: _5!, growthGraph: _6!, membersGraph: _7!, newMembersBySourceGraph: _8!, languagesGraph: _9!, messagesGraph: _10!, actionsGraph: _11!, topHoursGraph: _12!, topPosters: _13!, topAdmins: _14!, topInviters: _15!, users: _16!)
}
else {
return nil
}
}
}
}
}
public extension Api {
@ -2170,429 +2312,3 @@ public struct help {
}
}
}
public extension Api {
public struct updates {
public enum Difference: TypeConstructorDescription {
case differenceEmpty(date: Int32, seq: Int32)
case difference(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], state: Api.updates.State)
case differenceSlice(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], intermediateState: Api.updates.State)
case differenceTooLong(pts: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .differenceEmpty(let date, let seq):
if boxed {
buffer.appendInt32(1567990072)
}
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt32(seq, buffer: buffer, boxed: false)
break
case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state):
if boxed {
buffer.appendInt32(16030880)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newMessages.count))
for item in newMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newEncryptedMessages.count))
for item in newEncryptedMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(otherUpdates.count))
for item in otherUpdates {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
state.serialize(buffer, true)
break
case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState):
if boxed {
buffer.appendInt32(-1459938943)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newMessages.count))
for item in newMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newEncryptedMessages.count))
for item in newEncryptedMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(otherUpdates.count))
for item in otherUpdates {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
intermediateState.serialize(buffer, true)
break
case .differenceTooLong(let pts):
if boxed {
buffer.appendInt32(1258196845)
}
serializeInt32(pts, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .differenceEmpty(let date, let seq):
return ("differenceEmpty", [("date", date), ("seq", seq)])
case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state):
return ("difference", [("newMessages", newMessages), ("newEncryptedMessages", newEncryptedMessages), ("otherUpdates", otherUpdates), ("chats", chats), ("users", users), ("state", state)])
case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState):
return ("differenceSlice", [("newMessages", newMessages), ("newEncryptedMessages", newEncryptedMessages), ("otherUpdates", otherUpdates), ("chats", chats), ("users", users), ("intermediateState", intermediateState)])
case .differenceTooLong(let pts):
return ("differenceTooLong", [("pts", pts)])
}
}
public static func parse_differenceEmpty(_ reader: BufferReader) -> Difference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.updates.Difference.differenceEmpty(date: _1!, seq: _2!)
}
else {
return nil
}
}
public static func parse_difference(_ reader: BufferReader) -> Difference? {
var _1: [Api.Message]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _2: [Api.EncryptedMessage]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self)
}
var _3: [Api.Update]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
}
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
var _6: Api.updates.State?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.updates.State
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.updates.Difference.difference(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, state: _6!)
}
else {
return nil
}
}
public static func parse_differenceSlice(_ reader: BufferReader) -> Difference? {
var _1: [Api.Message]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _2: [Api.EncryptedMessage]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self)
}
var _3: [Api.Update]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
}
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
var _6: Api.updates.State?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.updates.State
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.updates.Difference.differenceSlice(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, intermediateState: _6!)
}
else {
return nil
}
}
public static func parse_differenceTooLong(_ reader: BufferReader) -> Difference? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.updates.Difference.differenceTooLong(pts: _1!)
}
else {
return nil
}
}
}
public enum State: TypeConstructorDescription {
case state(pts: Int32, qts: Int32, date: Int32, seq: Int32, unreadCount: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .state(let pts, let qts, let date, let seq, let unreadCount):
if boxed {
buffer.appendInt32(-1519637954)
}
serializeInt32(pts, buffer: buffer, boxed: false)
serializeInt32(qts, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt32(seq, buffer: buffer, boxed: false)
serializeInt32(unreadCount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .state(let pts, let qts, let date, let seq, let unreadCount):
return ("state", [("pts", pts), ("qts", qts), ("date", date), ("seq", seq), ("unreadCount", unreadCount)])
}
}
public static func parse_state(_ reader: BufferReader) -> State? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
_4 = reader.readInt32()
var _5: Int32?
_5 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.updates.State.state(pts: _1!, qts: _2!, date: _3!, seq: _4!, unreadCount: _5!)
}
else {
return nil
}
}
}
public enum ChannelDifference: TypeConstructorDescription {
case channelDifferenceEmpty(flags: Int32, pts: Int32, timeout: Int32?)
case channelDifference(flags: Int32, pts: Int32, timeout: Int32?, newMessages: [Api.Message], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User])
case channelDifferenceTooLong(flags: Int32, timeout: Int32?, dialog: Api.Dialog, messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .channelDifferenceEmpty(let flags, let pts, let timeout):
if boxed {
buffer.appendInt32(1041346555)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(pts, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
break
case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users):
if boxed {
buffer.appendInt32(543450958)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(pts, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newMessages.count))
for item in newMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(otherUpdates.count))
for item in otherUpdates {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users):
if boxed {
buffer.appendInt32(-1531132162)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
dialog.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .channelDifferenceEmpty(let flags, let pts, let timeout):
return ("channelDifferenceEmpty", [("flags", flags), ("pts", pts), ("timeout", timeout)])
case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users):
return ("channelDifference", [("flags", flags), ("pts", pts), ("timeout", timeout), ("newMessages", newMessages), ("otherUpdates", otherUpdates), ("chats", chats), ("users", users)])
case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users):
return ("channelDifferenceTooLong", [("flags", flags), ("timeout", timeout), ("dialog", dialog), ("messages", messages), ("chats", chats), ("users", users)])
}
}
public static func parse_channelDifferenceEmpty(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.updates.ChannelDifference.channelDifferenceEmpty(flags: _1!, pts: _2!, timeout: _3)
}
else {
return nil
}
}
public static func parse_channelDifference(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
var _4: [Api.Message]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _5: [Api.Update]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
}
var _6: [Api.Chat]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _7: [Api.User]?
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.updates.ChannelDifference.channelDifference(flags: _1!, pts: _2!, timeout: _3, newMessages: _4!, otherUpdates: _5!, chats: _6!, users: _7!)
}
else {
return nil
}
}
public static func parse_channelDifferenceTooLong(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() }
var _3: Api.Dialog?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Dialog
}
var _4: [Api.Message]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _5: [Api.Chat]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.updates.ChannelDifference.channelDifferenceTooLong(flags: _1!, timeout: _2, dialog: _3!, messages: _4!, chats: _5!, users: _6!)
}
else {
return nil
}
}
}
}
}

View File

@ -1,4 +1,430 @@
public extension Api {
public struct updates {
public enum Difference: TypeConstructorDescription {
case differenceEmpty(date: Int32, seq: Int32)
case difference(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], state: Api.updates.State)
case differenceSlice(newMessages: [Api.Message], newEncryptedMessages: [Api.EncryptedMessage], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User], intermediateState: Api.updates.State)
case differenceTooLong(pts: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .differenceEmpty(let date, let seq):
if boxed {
buffer.appendInt32(1567990072)
}
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt32(seq, buffer: buffer, boxed: false)
break
case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state):
if boxed {
buffer.appendInt32(16030880)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newMessages.count))
for item in newMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newEncryptedMessages.count))
for item in newEncryptedMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(otherUpdates.count))
for item in otherUpdates {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
state.serialize(buffer, true)
break
case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState):
if boxed {
buffer.appendInt32(-1459938943)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newMessages.count))
for item in newMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newEncryptedMessages.count))
for item in newEncryptedMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(otherUpdates.count))
for item in otherUpdates {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
intermediateState.serialize(buffer, true)
break
case .differenceTooLong(let pts):
if boxed {
buffer.appendInt32(1258196845)
}
serializeInt32(pts, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .differenceEmpty(let date, let seq):
return ("differenceEmpty", [("date", date), ("seq", seq)])
case .difference(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let state):
return ("difference", [("newMessages", newMessages), ("newEncryptedMessages", newEncryptedMessages), ("otherUpdates", otherUpdates), ("chats", chats), ("users", users), ("state", state)])
case .differenceSlice(let newMessages, let newEncryptedMessages, let otherUpdates, let chats, let users, let intermediateState):
return ("differenceSlice", [("newMessages", newMessages), ("newEncryptedMessages", newEncryptedMessages), ("otherUpdates", otherUpdates), ("chats", chats), ("users", users), ("intermediateState", intermediateState)])
case .differenceTooLong(let pts):
return ("differenceTooLong", [("pts", pts)])
}
}
public static func parse_differenceEmpty(_ reader: BufferReader) -> Difference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.updates.Difference.differenceEmpty(date: _1!, seq: _2!)
}
else {
return nil
}
}
public static func parse_difference(_ reader: BufferReader) -> Difference? {
var _1: [Api.Message]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _2: [Api.EncryptedMessage]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self)
}
var _3: [Api.Update]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
}
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
var _6: Api.updates.State?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.updates.State
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.updates.Difference.difference(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, state: _6!)
}
else {
return nil
}
}
public static func parse_differenceSlice(_ reader: BufferReader) -> Difference? {
var _1: [Api.Message]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _2: [Api.EncryptedMessage]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.EncryptedMessage.self)
}
var _3: [Api.Update]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
}
var _4: [Api.Chat]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _5: [Api.User]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
var _6: Api.updates.State?
if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.updates.State
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.updates.Difference.differenceSlice(newMessages: _1!, newEncryptedMessages: _2!, otherUpdates: _3!, chats: _4!, users: _5!, intermediateState: _6!)
}
else {
return nil
}
}
public static func parse_differenceTooLong(_ reader: BufferReader) -> Difference? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.updates.Difference.differenceTooLong(pts: _1!)
}
else {
return nil
}
}
}
public enum State: TypeConstructorDescription {
case state(pts: Int32, qts: Int32, date: Int32, seq: Int32, unreadCount: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .state(let pts, let qts, let date, let seq, let unreadCount):
if boxed {
buffer.appendInt32(-1519637954)
}
serializeInt32(pts, buffer: buffer, boxed: false)
serializeInt32(qts, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt32(seq, buffer: buffer, boxed: false)
serializeInt32(unreadCount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .state(let pts, let qts, let date, let seq, let unreadCount):
return ("state", [("pts", pts), ("qts", qts), ("date", date), ("seq", seq), ("unreadCount", unreadCount)])
}
}
public static func parse_state(_ reader: BufferReader) -> State? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
_4 = reader.readInt32()
var _5: Int32?
_5 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.updates.State.state(pts: _1!, qts: _2!, date: _3!, seq: _4!, unreadCount: _5!)
}
else {
return nil
}
}
}
public enum ChannelDifference: TypeConstructorDescription {
case channelDifferenceEmpty(flags: Int32, pts: Int32, timeout: Int32?)
case channelDifference(flags: Int32, pts: Int32, timeout: Int32?, newMessages: [Api.Message], otherUpdates: [Api.Update], chats: [Api.Chat], users: [Api.User])
case channelDifferenceTooLong(flags: Int32, timeout: Int32?, dialog: Api.Dialog, messages: [Api.Message], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .channelDifferenceEmpty(let flags, let pts, let timeout):
if boxed {
buffer.appendInt32(1041346555)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(pts, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
break
case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users):
if boxed {
buffer.appendInt32(543450958)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(pts, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(newMessages.count))
for item in newMessages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(otherUpdates.count))
for item in otherUpdates {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users):
if boxed {
buffer.appendInt32(-1531132162)
}
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(timeout!, buffer: buffer, boxed: false)}
dialog.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .channelDifferenceEmpty(let flags, let pts, let timeout):
return ("channelDifferenceEmpty", [("flags", flags), ("pts", pts), ("timeout", timeout)])
case .channelDifference(let flags, let pts, let timeout, let newMessages, let otherUpdates, let chats, let users):
return ("channelDifference", [("flags", flags), ("pts", pts), ("timeout", timeout), ("newMessages", newMessages), ("otherUpdates", otherUpdates), ("chats", chats), ("users", users)])
case .channelDifferenceTooLong(let flags, let timeout, let dialog, let messages, let chats, let users):
return ("channelDifferenceTooLong", [("flags", flags), ("timeout", timeout), ("dialog", dialog), ("messages", messages), ("chats", chats), ("users", users)])
}
}
public static func parse_channelDifferenceEmpty(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
if _c1 && _c2 && _c3 {
return Api.updates.ChannelDifference.channelDifferenceEmpty(flags: _1!, pts: _2!, timeout: _3)
}
else {
return nil
}
}
public static func parse_channelDifference(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_3 = reader.readInt32() }
var _4: [Api.Message]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _5: [Api.Update]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Update.self)
}
var _6: [Api.Chat]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _7: [Api.User]?
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.updates.ChannelDifference.channelDifference(flags: _1!, pts: _2!, timeout: _3, newMessages: _4!, otherUpdates: _5!, chats: _6!, users: _7!)
}
else {
return nil
}
}
public static func parse_channelDifferenceTooLong(_ reader: BufferReader) -> ChannelDifference? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() }
var _3: Api.Dialog?
if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Dialog
}
var _4: [Api.Message]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Message.self)
}
var _5: [Api.Chat]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.updates.ChannelDifference.channelDifferenceTooLong(flags: _1!, timeout: _2, dialog: _3!, messages: _4!, chats: _5!, users: _6!)
}
else {
return nil
}
}
}
}
}
public extension Api {
public struct upload {
public enum WebFile: TypeConstructorDescription {
case webFile(size: Int32, mimeType: String, fileType: Api.storage.FileType, mtime: Int32, bytes: Buffer)
@ -3995,13 +4421,12 @@ public extension Api {
})
}
public static func getBroadcastStats(flags: Int32, channel: Api.InputChannel, tzOffset: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stats.BroadcastStats>) {
public static func getBroadcastStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stats.BroadcastStats>) {
let buffer = Buffer()
buffer.appendInt32(-433058374)
buffer.appendInt32(-1421720550)
serializeInt32(flags, buffer: buffer, boxed: false)
channel.serialize(buffer, true)
serializeInt32(tzOffset, buffer: buffer, boxed: false)
return (FunctionDescription(name: "stats.getBroadcastStats", parameters: [("flags", flags), ("channel", channel), ("tzOffset", tzOffset)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastStats? in
return (FunctionDescription(name: "stats.getBroadcastStats", parameters: [("flags", flags), ("channel", channel)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastStats? in
let reader = BufferReader(buffer)
var result: Api.stats.BroadcastStats?
if let signature = reader.readInt32() {
@ -4010,6 +4435,21 @@ public extension Api {
return result
})
}
public static func getMegagroupStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stats.MegagroupStats>) {
let buffer = Buffer()
buffer.appendInt32(-589330937)
serializeInt32(flags, buffer: buffer, boxed: false)
channel.serialize(buffer, true)
return (FunctionDescription(name: "stats.getMegagroupStats", parameters: [("flags", flags), ("channel", channel)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.MegagroupStats? in
let reader = BufferReader(buffer)
var result: Api.stats.MegagroupStats?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.stats.MegagroupStats
}
return result
})
}
}
public struct auth {
public static func checkPhone(phoneNumber: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.CheckedPhone>) {

View File

@ -161,6 +161,7 @@ private var declaredEncodables: Void = {
declareEncodable(TelegramMediaFile.VideoThumbnail.self, f: { TelegramMediaFile.VideoThumbnail(decoder: $0) })
declareEncodable(CachedChatContextResult.self, f: { CachedChatContextResult(decoder: $0) })
declareEncodable(PeerAccessRestrictionInfo.self, f: { PeerAccessRestrictionInfo(decoder: $0) })
declareEncodable(TelegramMediaImage.VideoRepresentation.self, f: { TelegramMediaImage.VideoRepresentation(decoder: $0) })
return
}()

View File

@ -1,608 +0,0 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
import MtProtoKit
import SyncCore
public struct ChannelStatsDateRange: Equatable {
public let minDate: Int32
public let maxDate: Int32
}
public struct ChannelStatsValue: Equatable {
public let current: Double
public let previous: Double
}
public struct ChannelStatsPercentValue: Equatable {
public let value: Double
public let total: Double
}
public enum ChannelStatsGraph: Equatable {
case OnDemand(token: String)
case Failed(error: String)
case Loaded(token: String?, data: String)
case Empty
public var isEmpty: Bool {
switch self {
case .Empty:
return true
case let .Failed(error):
return error.lowercased().contains("not enough data")
default:
return false
}
}
var token: String? {
switch self {
case let .OnDemand(token):
return token
case let .Loaded(token, _):
return token
default:
return nil
}
}
}
public struct ChannelStatsMessageInteractions: Equatable {
public let messageId: MessageId
public let views: Int32
public let forwards: Int32
}
public final class ChannelStats: Equatable {
public let period: ChannelStatsDateRange
public let followers: ChannelStatsValue
public let viewsPerPost: ChannelStatsValue
public let sharesPerPost: ChannelStatsValue
public let enabledNotifications: ChannelStatsPercentValue
public let growthGraph: ChannelStatsGraph
public let followersGraph: ChannelStatsGraph
public let muteGraph: ChannelStatsGraph
public let topHoursGraph: ChannelStatsGraph
public let interactionsGraph: ChannelStatsGraph
public let instantPageInteractionsGraph: ChannelStatsGraph
public let viewsBySourceGraph: ChannelStatsGraph
public let newFollowersBySourceGraph: ChannelStatsGraph
public let languagesGraph: ChannelStatsGraph
public let messageInteractions: [ChannelStatsMessageInteractions]
public init(period: ChannelStatsDateRange, followers: ChannelStatsValue, viewsPerPost: ChannelStatsValue, sharesPerPost: ChannelStatsValue, enabledNotifications: ChannelStatsPercentValue, growthGraph: ChannelStatsGraph, followersGraph: ChannelStatsGraph, muteGraph: ChannelStatsGraph, topHoursGraph: ChannelStatsGraph, interactionsGraph: ChannelStatsGraph, instantPageInteractionsGraph: ChannelStatsGraph, viewsBySourceGraph: ChannelStatsGraph, newFollowersBySourceGraph: ChannelStatsGraph, languagesGraph: ChannelStatsGraph, messageInteractions: [ChannelStatsMessageInteractions]) {
self.period = period
self.followers = followers
self.viewsPerPost = viewsPerPost
self.sharesPerPost = sharesPerPost
self.enabledNotifications = enabledNotifications
self.growthGraph = growthGraph
self.followersGraph = followersGraph
self.muteGraph = muteGraph
self.topHoursGraph = topHoursGraph
self.interactionsGraph = interactionsGraph
self.instantPageInteractionsGraph = instantPageInteractionsGraph
self.viewsBySourceGraph = viewsBySourceGraph
self.newFollowersBySourceGraph = newFollowersBySourceGraph
self.languagesGraph = languagesGraph
self.messageInteractions = messageInteractions
}
public static func == (lhs: ChannelStats, rhs: ChannelStats) -> Bool {
if lhs.period != rhs.period {
return false
}
if lhs.followers != rhs.followers {
return false
}
if lhs.viewsPerPost != rhs.viewsPerPost {
return false
}
if lhs.sharesPerPost != rhs.sharesPerPost {
return false
}
if lhs.enabledNotifications != rhs.enabledNotifications {
return false
}
if lhs.growthGraph != rhs.growthGraph {
return false
}
if lhs.followersGraph != rhs.followersGraph {
return false
}
if lhs.muteGraph != rhs.muteGraph {
return false
}
if lhs.topHoursGraph != rhs.topHoursGraph {
return false
}
if lhs.interactionsGraph != rhs.interactionsGraph {
return false
}
if lhs.instantPageInteractionsGraph != rhs.instantPageInteractionsGraph {
return false
}
if lhs.viewsBySourceGraph != rhs.viewsBySourceGraph {
return false
}
if lhs.newFollowersBySourceGraph != rhs.newFollowersBySourceGraph {
return false
}
if lhs.languagesGraph != rhs.languagesGraph {
return false
}
if lhs.messageInteractions != rhs.messageInteractions {
return false
}
return true
}
public func withUpdatedGrowthGraph(_ growthGraph: ChannelStatsGraph) -> ChannelStats {
return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, instantPageInteractionsGraph: self.instantPageInteractionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph, messageInteractions: self.messageInteractions)
}
public func withUpdatedFollowersGraph(_ followersGraph: ChannelStatsGraph) -> ChannelStats {
return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, instantPageInteractionsGraph: self.instantPageInteractionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph, messageInteractions: self.messageInteractions)
}
public func withUpdatedMuteGraph(_ muteGraph: ChannelStatsGraph) -> ChannelStats {
return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, instantPageInteractionsGraph: self.instantPageInteractionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph, messageInteractions: self.messageInteractions)
}
public func withUpdatedTopHoursGraph(_ viewsByHourGraph: ChannelStatsGraph) -> ChannelStats {
return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: viewsByHourGraph, interactionsGraph: self.interactionsGraph, instantPageInteractionsGraph: self.instantPageInteractionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph, messageInteractions: self.messageInteractions)
}
public func withUpdatedInteractionsGraph(_ interactionsGraph: ChannelStatsGraph) -> ChannelStats {
return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: interactionsGraph, instantPageInteractionsGraph: self.instantPageInteractionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph, messageInteractions: self.messageInteractions)
}
public func withUpdatedInstantPageInteractionsGraph(_ instantPageInteractionsGraph: ChannelStatsGraph) -> ChannelStats {
return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, instantPageInteractionsGraph: instantPageInteractionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph, messageInteractions: self.messageInteractions)
}
public func withUpdatedViewsBySourceGraph(_ viewsBySourceGraph: ChannelStatsGraph) -> ChannelStats {
return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, instantPageInteractionsGraph: self.instantPageInteractionsGraph, viewsBySourceGraph: viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph, messageInteractions: self.messageInteractions)
}
public func withUpdatedNewFollowersBySourceGraph(_ newFollowersBySourceGraph: ChannelStatsGraph) -> ChannelStats {
return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, instantPageInteractionsGraph: self.instantPageInteractionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: newFollowersBySourceGraph, languagesGraph: self.languagesGraph, messageInteractions: self.messageInteractions)
}
public func withUpdatedLanguagesGraph(_ languagesGraph: ChannelStatsGraph) -> ChannelStats {
return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, instantPageInteractionsGraph: self.instantPageInteractionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: languagesGraph, messageInteractions: self.messageInteractions)
}
public func withUpdatedMessageInteractions(_ messageInteractions: [ChannelStatsMessageInteractions]) -> ChannelStats {
return ChannelStats(period: self.period, followers: self.followers, viewsPerPost: self.viewsPerPost, sharesPerPost: self.sharesPerPost, enabledNotifications: self.enabledNotifications, growthGraph: self.growthGraph, followersGraph: self.followersGraph, muteGraph: self.muteGraph, topHoursGraph: self.topHoursGraph, interactionsGraph: self.interactionsGraph, instantPageInteractionsGraph: self.instantPageInteractionsGraph, viewsBySourceGraph: self.viewsBySourceGraph, newFollowersBySourceGraph: self.newFollowersBySourceGraph, languagesGraph: self.languagesGraph, messageInteractions: messageInteractions)
}
}
public struct ChannelStatsContextState: Equatable {
public var stats: ChannelStats?
}
private func requestStats(postbox: Postbox, network: Network, datacenterId: Int32, peerId: PeerId, dark: Bool = false) -> Signal<ChannelStats?, NoError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(peerId)
} |> mapToSignal { peer -> Signal<ChannelStats?, NoError> in
guard let peer = peer, let inputChannel = apiInputChannel(peer) else {
return .never()
}
var flags: Int32 = 0
if dark {
flags |= (1 << 1)
}
let signal: Signal<Api.stats.BroadcastStats, MTRpcError>
if network.datacenterId != datacenterId {
signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil)
|> castError(MTRpcError.self)
|> mapToSignal { worker in
return worker.request(Api.functions.stats.getBroadcastStats(flags: flags, channel: inputChannel, tzOffset: 0))
}
} else {
signal = network.request(Api.functions.stats.getBroadcastStats(flags: flags, channel: inputChannel, tzOffset: 0))
}
return signal
|> map { result -> ChannelStats? in
return ChannelStats(apiBroadcastStats: result, peerId: peerId)
}
|> retryRequest
}
}
private func requestGraph(network: Network, datacenterId: Int32, token: String, x: Int64? = nil) -> Signal<ChannelStatsGraph?, NoError> {
var flags: Int32 = 0
if let _ = x {
flags |= (1 << 0)
}
let signal: Signal<Api.StatsGraph, MTRpcError>
if network.datacenterId != datacenterId {
signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil)
|> castError(MTRpcError.self)
|> mapToSignal { worker in
return worker.request(Api.functions.stats.loadAsyncGraph(flags: flags, token: token, x: x))
}
} else {
signal = network.request(Api.functions.stats.loadAsyncGraph(flags: flags, token: token, x: x))
}
return signal
|> map { result -> ChannelStatsGraph? in
return ChannelStatsGraph(apiStatsGraph: result)
}
|> `catch` { _ -> Signal<ChannelStatsGraph?, NoError> in
return .single(nil)
}
}
private final class ChannelStatsContextImpl {
private let postbox: Postbox
private let network: Network
private let datacenterId: Int32
private let peerId: PeerId
private var _state: ChannelStatsContextState {
didSet {
if self._state != oldValue {
self._statePromise.set(.single(self._state))
}
}
}
private let _statePromise = Promise<ChannelStatsContextState>()
var state: Signal<ChannelStatsContextState, NoError> {
return self._statePromise.get()
}
private let disposable = MetaDisposable()
private let disposables = DisposableDict<String>()
init(postbox: Postbox, network: Network, datacenterId: Int32, peerId: PeerId) {
assert(Queue.mainQueue().isCurrent())
self.postbox = postbox
self.network = network
self.datacenterId = datacenterId
self.peerId = peerId
self._state = ChannelStatsContextState(stats: nil)
self._statePromise.set(.single(self._state))
self.load()
}
deinit {
assert(Queue.mainQueue().isCurrent())
self.disposable.dispose()
self.disposables.dispose()
}
private func load() {
assert(Queue.mainQueue().isCurrent())
self.disposable.set((requestStats(postbox: self.postbox, network: self.network, datacenterId: self.datacenterId, peerId: self.peerId)
|> deliverOnMainQueue).start(next: { [weak self] stats in
if let strongSelf = self {
strongSelf._state = ChannelStatsContextState(stats: stats)
strongSelf._statePromise.set(.single(strongSelf._state))
}
}))
}
func loadGrowthGraph() {
guard let stats = self._state.stats else {
return
}
if case let .OnDemand(token) = stats.growthGraph {
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|> deliverOnMainQueue).start(next: { [weak self] graph in
if let strongSelf = self, let graph = graph {
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedGrowthGraph(graph))
strongSelf._statePromise.set(.single(strongSelf._state))
}
}), forKey: token)
}
}
func loadFollowersGraph() {
guard let stats = self._state.stats else {
return
}
if case let .OnDemand(token) = stats.followersGraph {
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|> deliverOnMainQueue).start(next: { [weak self] graph in
if let strongSelf = self, let graph = graph {
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedFollowersGraph(graph))
strongSelf._statePromise.set(.single(strongSelf._state))
}
}), forKey: token)
}
}
func loadMuteGraph() {
guard let stats = self._state.stats else {
return
}
if case let .OnDemand(token) = stats.muteGraph {
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|> deliverOnMainQueue).start(next: { [weak self] graph in
if let strongSelf = self, let graph = graph {
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedMuteGraph(graph))
strongSelf._statePromise.set(.single(strongSelf._state))
}
}), forKey: token)
}
}
func loadTopHoursGraph() {
guard let stats = self._state.stats else {
return
}
if case let .OnDemand(token) = stats.topHoursGraph {
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|> deliverOnMainQueue).start(next: { [weak self] graph in
if let strongSelf = self, let graph = graph {
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedTopHoursGraph(graph))
strongSelf._statePromise.set(.single(strongSelf._state))
}
}), forKey: token)
}
}
func loadInteractionsGraph() {
guard let stats = self._state.stats else {
return
}
if case let .OnDemand(token) = stats.interactionsGraph {
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|> deliverOnMainQueue).start(next: { [weak self] graph in
if let strongSelf = self, let graph = graph {
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedInteractionsGraph(graph))
strongSelf._statePromise.set(.single(strongSelf._state))
}
}), forKey: token)
}
}
func loadInstantPageInteractionsGraph() {
guard let stats = self._state.stats else {
return
}
if case let .OnDemand(token) = stats.instantPageInteractionsGraph {
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|> deliverOnMainQueue).start(next: { [weak self] graph in
if let strongSelf = self, let graph = graph {
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedInstantPageInteractionsGraph(graph))
strongSelf._statePromise.set(.single(strongSelf._state))
}
}), forKey: token)
}
}
func loadViewsBySourceGraph() {
guard let stats = self._state.stats else {
return
}
if case let .OnDemand(token) = stats.viewsBySourceGraph {
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|> deliverOnMainQueue).start(next: { [weak self] graph in
if let strongSelf = self, let graph = graph {
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedViewsBySourceGraph(graph))
strongSelf._statePromise.set(.single(strongSelf._state))
}
}), forKey: token)
}
}
func loadNewFollowersBySourceGraph() {
guard let stats = self._state.stats else {
return
}
if case let .OnDemand(token) = stats.newFollowersBySourceGraph {
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|> deliverOnMainQueue).start(next: { [weak self] graph in
if let strongSelf = self, let graph = graph {
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedNewFollowersBySourceGraph(graph))
strongSelf._statePromise.set(.single(strongSelf._state))
}
}), forKey: token)
}
}
func loadLanguagesGraph() {
guard let stats = self._state.stats else {
return
}
if case let .OnDemand(token) = stats.languagesGraph {
self.disposables.set((requestGraph(network: self.network, datacenterId: self.datacenterId, token: token)
|> deliverOnMainQueue).start(next: { [weak self] graph in
if let strongSelf = self, let graph = graph {
strongSelf._state = ChannelStatsContextState(stats: strongSelf._state.stats?.withUpdatedLanguagesGraph(graph))
strongSelf._statePromise.set(.single(strongSelf._state))
}
}), forKey: token)
}
}
func loadDetailedGraph(_ graph: ChannelStatsGraph, x: Int64) -> Signal<ChannelStatsGraph?, NoError> {
if let token = graph.token {
return requestGraph(network: self.network, datacenterId: self.datacenterId, token: token, x: x)
} else {
return .single(nil)
}
}
}
public final class ChannelStatsContext {
private let impl: QueueLocalObject<ChannelStatsContextImpl>
public var state: Signal<ChannelStatsContextState, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.state.start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
public init(postbox: Postbox, network: Network, datacenterId: Int32, peerId: PeerId) {
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
return ChannelStatsContextImpl(postbox: postbox, network: network, datacenterId: datacenterId, peerId: peerId)
})
}
public func loadGrowthGraph() {
self.impl.with { impl in
impl.loadGrowthGraph()
}
}
public func loadFollowersGraph() {
self.impl.with { impl in
impl.loadFollowersGraph()
}
}
public func loadMuteGraph() {
self.impl.with { impl in
impl.loadMuteGraph()
}
}
public func loadTopHoursGraph() {
self.impl.with { impl in
impl.loadTopHoursGraph()
}
}
public func loadInteractionsGraph() {
self.impl.with { impl in
impl.loadInteractionsGraph()
}
}
public func loadInstantPageInteractionsGraph() {
self.impl.with { impl in
impl.loadInstantPageInteractionsGraph()
}
}
public func loadViewsBySourceGraph() {
self.impl.with { impl in
impl.loadViewsBySourceGraph()
}
}
public func loadNewFollowersBySourceGraph() {
self.impl.with { impl in
impl.loadNewFollowersBySourceGraph()
}
}
public func loadLanguagesGraph() {
self.impl.with { impl in
impl.loadLanguagesGraph()
}
}
public func loadDetailedGraph(_ graph: ChannelStatsGraph, x: Int64) -> Signal<ChannelStatsGraph?, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.loadDetailedGraph(graph, x: x).start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
}
extension ChannelStatsGraph {
init(apiStatsGraph: Api.StatsGraph) {
switch apiStatsGraph {
case let .statsGraph(_, json, zoomToken):
if case let .dataJSON(string) = json, let data = string.data(using: .utf8) {
do {
let decodedData = try JSONSerialization.jsonObject(with: data, options: [])
guard let item = decodedData as? [String: Any] else {
self = .Failed(error: "")
return
}
if let columns = item["columns"] as? [[Any]] {
if columns.isEmpty {
self = .Empty
} else {
self = .Loaded(token: zoomToken, data: string)
}
} else {
self = .Empty
}
} catch {
self = .Failed(error: "")
}
} else {
self = .Failed(error: "")
}
case let .statsGraphError(error):
self = .Failed(error: error)
case let .statsGraphAsync(token):
self = .OnDemand(token: token)
}
}
}
extension ChannelStatsDateRange {
init(apiStatsDateRangeDays: Api.StatsDateRangeDays) {
switch apiStatsDateRangeDays {
case let .statsDateRangeDays(minDate, maxDate):
self = ChannelStatsDateRange(minDate: minDate, maxDate: maxDate)
}
}
}
extension ChannelStatsValue {
init(apiStatsAbsValueAndPrev: Api.StatsAbsValueAndPrev) {
switch apiStatsAbsValueAndPrev {
case let .statsAbsValueAndPrev(current, previous):
self = ChannelStatsValue(current: current, previous: previous)
}
}
}
extension ChannelStatsPercentValue {
init(apiPercentValue: Api.StatsPercentValue) {
switch apiPercentValue {
case let .statsPercentValue(part, total):
self = ChannelStatsPercentValue(value: part, total: total)
}
}
}
extension ChannelStatsMessageInteractions {
init(apiMessageInteractionCounters: Api.MessageInteractionCounters, peerId: PeerId) {
switch apiMessageInteractionCounters {
case let .messageInteractionCounters(msgId, views, forwards):
self = ChannelStatsMessageInteractions(messageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: msgId), views: views, forwards: forwards)
}
}
}
extension ChannelStats {
convenience init(apiBroadcastStats: Api.stats.BroadcastStats, peerId: PeerId) {
switch apiBroadcastStats {
case let .broadcastStats(period, followers, viewsPerPost, sharesPerPost, enabledNotifications, apiGrowthGraph, apiFollowersGraph, apiMuteGraph, apiTopHoursGraph, apiInteractionsGraph, apiInstantViewInteractionsGraph, apiViewsBySourceGraph, apiNewFollowersBySourceGraph, apiLanguagesGraph, recentMessageInteractions):
let growthGraph = ChannelStatsGraph(apiStatsGraph: apiGrowthGraph)
let isEmpty = growthGraph.isEmpty
self.init(period: ChannelStatsDateRange(apiStatsDateRangeDays: period), followers: ChannelStatsValue(apiStatsAbsValueAndPrev: followers), viewsPerPost: ChannelStatsValue(apiStatsAbsValueAndPrev: viewsPerPost), sharesPerPost: ChannelStatsValue(apiStatsAbsValueAndPrev: sharesPerPost), enabledNotifications: ChannelStatsPercentValue(apiPercentValue: enabledNotifications), growthGraph: growthGraph, followersGraph: ChannelStatsGraph(apiStatsGraph: apiFollowersGraph), muteGraph: ChannelStatsGraph(apiStatsGraph: apiMuteGraph), topHoursGraph: ChannelStatsGraph(apiStatsGraph: apiTopHoursGraph), interactionsGraph: isEmpty ? .Empty : ChannelStatsGraph(apiStatsGraph: apiInteractionsGraph), instantPageInteractionsGraph: isEmpty ? .Empty : ChannelStatsGraph(apiStatsGraph: apiInstantViewInteractionsGraph), viewsBySourceGraph: isEmpty ? .Empty : ChannelStatsGraph(apiStatsGraph: apiViewsBySourceGraph), newFollowersBySourceGraph: isEmpty ? .Empty : ChannelStatsGraph(apiStatsGraph: apiNewFollowersBySourceGraph), languagesGraph: isEmpty ? .Empty : ChannelStatsGraph(apiStatsGraph: apiLanguagesGraph), messageInteractions: recentMessageInteractions.map { ChannelStatsMessageInteractions(apiMessageInteractionCounters: $0, peerId: peerId) })
}
}
}

View File

@ -1,34 +0,0 @@
import Foundation
import SwiftSignalKit
import Postbox
import TelegramApi
public enum ChannelStatsUrlError {
case generic
}
public func channelStatsUrl(postbox: Postbox, network: Network, peerId: PeerId, params: String, darkTheme: Bool) -> Signal<String, ChannelStatsUrlError> {
return postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> castError(ChannelStatsUrlError.self)
|> mapToSignal { inputPeer -> Signal<String, ChannelStatsUrlError> in
guard let inputPeer = inputPeer else {
return .fail(.generic)
}
var flags: Int32 = 0
if darkTheme {
flags |= (1 << 0)
}
return network.request(Api.functions.messages.getStatsURL(flags: flags, peer: inputPeer, params: params))
|> map { result -> String in
switch result {
case let .statsURL(url):
return url
}
}
|> `catch` { _ -> Signal<String, ChannelStatsUrlError> in
return .fail(.generic)
}
}
}

View File

@ -123,7 +123,7 @@ func managedSavedStickers(postbox: Postbox, network: Network) -> Signal<Void, No
func managedGreetingStickers(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudGreetingStickers, reverseHashOrder: false, forceFetch: false, fetch: { hash in
return network.request(Api.functions.messages.getStickers(emoticon: "👋⭐️", hash: hash))
return network.request(Api.functions.messages.getStickers(emoticon: "👋⭐️", hash: 0))
|> retryRequest
|> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in
switch result {

View File

@ -277,6 +277,25 @@ public func updatePeerPhotoInternal(postbox: Postbox, network: Network, stateMan
}
}
public func updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal<Void, NoError> {
switch reference {
case let .cloud(imageId, accessHash, fileReference):
return network.request(Api.functions.photos.updateProfilePhoto(id: .inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))))
|> `catch` { _ -> Signal<Api.UserProfilePhoto, NoError> in
return .complete()
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return network.request(Api.functions.photos.deletePhotos(id: [.inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))]))
|> `catch` { _ -> Signal<[Int64], NoError> in
return .single([])
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
}
}
}
public func removeAccountPhoto(network: Network, reference: TelegramMediaImageReference?) -> Signal<Void, NoError> {
if let reference = reference {
switch reference {

File diff suppressed because it is too large Load Diff

View File

@ -64,7 +64,6 @@ public func randomGreetingSticker(account: Account) -> Signal<FoundStickerItem?,
for entry in transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudGreetingStickers) {
if let item = entry.contents as? RecentMediaItem, let file = item.media as? TelegramMediaFile {
stickerItems.append(FoundStickerItem(file: file, stringRepresentations: []))
break
}
}
return stickerItems.randomElement()

View File

@ -48,7 +48,7 @@ func telegramMediaImageFromApiPhoto(_ photo: Api.Photo) -> TelegramMediaImage? {
let resource: TelegramMediaResource
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
resource = CloudDocumentSizeMediaResource(datacenterId: dcId, documentId: id, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, fileReference: fileReference.makeData())
resource = CloudPhotoSizeMediaResource(datacenterId: dcId, photoId: id, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, fileReference: fileReference.makeData())
}
videoRepresentations.append(TelegramMediaImage.VideoRepresentation(
@ -58,7 +58,7 @@ func telegramMediaImageFromApiPhoto(_ photo: Api.Photo) -> TelegramMediaImage? {
}
}
return TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: id), representations: representations, immediateThumbnailData: immediateThumbnailData, reference: .cloud(imageId: id, accessHash: accessHash, fileReference: fileReference.makeData()), partialReference: nil, flags: imageFlags)
return TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: id), representations: representations, videoRepresentations: videoRepresentations, immediateThumbnailData: immediateThumbnailData, reference: .cloud(imageId: id, accessHash: accessHash, fileReference: fileReference.makeData()), partialReference: nil, flags: imageFlags)
case .photoEmpty:
return nil
}

View File

@ -2125,7 +2125,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
strongSelf.view.endEditing(true)
strongSelf.push(channelStatsController(context: context, peerId: peer.id, cachedPeerData: cachedData))
let statsController: ViewController
if let channel = peer as? TelegramChannel, case .group = channel.info {
statsController = groupStatsController(context: context, peerId: peer.id, cachedPeerData: cachedData)
} else {
statsController = channelStatsController(context: context, peerId: peer.id, cachedPeerData: cachedData)
}
strongSelf.push(statsController)
})))
}
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_Search, icon: { theme in

View File

@ -168,6 +168,7 @@ private final class ChatEmptyNodeNearbyChatContent: ASDisplayNode, ChatEmptyNode
clearRecentlyUsedStickers: {
}
)
inputNodeInteraction.displayStickerPlaceholder = false
let index = ItemCollectionItemIndex(index: 0, id: 0)
let collectionId = ItemCollectionId(namespace: 0, id: 0)

View File

@ -88,7 +88,7 @@ final class TrendingPanePackEntry: Identifiable, Comparable {
func item(account: Account, interaction: TrendingPaneInteraction, grid: Bool) -> GridItem {
let info = self.info
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: false, info: self.info, topItems: self.topItems, grid: grid, topSeparator: self.topSeparator, regularInsets: false, installed: self.installed, unread: self.unread, open: {
return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: false, info: self.info, topItems: self.topItems, topSeparator: self.topSeparator, regularInsets: false, installed: self.installed, unread: self.unread, open: {
interaction.openPack(info)
}, install: {
interaction.installPack(info)

View File

@ -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, grid: false, 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, 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)
@ -713,14 +713,14 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode {
searchNode.updateLayout(size: searchNodeFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: insets.bottom + layout.safeInsets.bottom, inputHeight: layout.inputHeight ?? 0.0, deviceMetrics: layout.deviceMetrics, transition: transition)
}
let itemSize: CGSize
if case .tablet = layout.deviceMetrics.type, layout.size.width > 480.0 {
itemSize = CGSize(width: floor(layout.size.width / 2.0), height: 128.0)
} else {
itemSize = CGSize(width: layout.size.width, height: 128.0)
var itemSize = CGSize(width: layout.size.width, height: 128.0)
if case .regular = layout.metrics.widthClass, layout.size.width > 480.0 {
itemSize.width -= 60.0
insets.left += 30.0
insets.right += 30.0
}
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: layout.safeInsets.left, bottom: insets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), preloadSize: 300.0, type: .fixed(itemSize: itemSize, fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: insets.left + layout.safeInsets.left, bottom: insets.bottom + layout.safeInsets.bottom, right: insets.right + layout.safeInsets.right), preloadSize: 300.0, type: .fixed(itemSize: itemSize, fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
transition.updateFrame(node: self.gridNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)))
@ -1032,7 +1032,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, grid: false, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
interaction.open(info)
}, install: {
interaction.install(info, topItems, !installed)

View File

@ -27,33 +27,6 @@ private final class AVURLAssetCopyItem: MediaResourceDataFetchCopyLocalItem {
}
}
class VideoConversionWatcher: TGMediaVideoFileWatcher {
private let update: (String, Int) -> Void
private var path: String?
init(update: @escaping (String, Int) -> Void) {
self.update = update
super.init()
}
override func setup(withFileURL fileURL: URL!) {
self.path = fileURL?.path
super.setup(withFileURL: fileURL)
}
override func fileUpdated(_ completed: Bool) -> Any! {
if let path = self.path {
var value = stat()
if stat(path, &value) == 0 {
self.update(path, Int(value.st_size))
}
}
return super.fileUpdated(completed)
}
}
struct VideoConversionConfiguration {
static var defaultValue: VideoConversionConfiguration {
return VideoConversionConfiguration(remuxToFMp4: false)

View File

@ -14,6 +14,7 @@ import LegacyUI
import ImageCompression
import LocalMediaResources
import AppBundle
import LegacyMediaPickerUI
final class InstantVideoControllerRecordingStatus {
let micLevel: Signal<Float, NoError>

View File

@ -46,7 +46,7 @@ private func chatMessageGalleryControllerData(context: AccountContext, message:
switch action.action {
case let .photoUpdated(image):
if let peer = messageMainPeer(message), let image = image {
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id)])
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations, peer, message.timestamp, nil, message.id)])
let galleryController = AvatarGalleryController(context: context, peer: peer, remoteEntries: promise, replaceRootController: { controller, ready in
})

View File

@ -23,7 +23,7 @@ final class PaneSearchBarPlaceholderItem: GridItem {
let activate: () -> Void
let section: GridSection? = nil
let fillsRowWithHeight: CGFloat? = 56.0
let fillsRowWithHeight: (CGFloat, Bool)? = (56.0, true)
init(theme: PresentationTheme, strings: PresentationStrings, type: PaneSearchBarType, activate: @escaping () -> Void) {
self.theme = theme

View File

@ -13,6 +13,8 @@ import PhotoResources
import PeerAvatarGalleryUI
import TelegramStringFormatting
import ActivityIndicator
import TelegramUniversalVideoContent
import GalleryUI
enum PeerInfoHeaderButtonKey: Hashable {
case message
@ -149,14 +151,14 @@ final class PeerInfoHeaderNavigationTransition {
enum PeerInfoAvatarListItem: Equatable {
case topImage([ImageRepresentationWithReference])
case image(TelegramMediaImageReference?, [ImageRepresentationWithReference])
case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [TelegramMediaImage.VideoRepresentation])
var id: WrappedMediaResourceId {
switch self {
case let .topImage(representations):
let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation
return WrappedMediaResourceId(representation.resource.id)
case let .image(_, representations):
case let .image(_, representations, _):
let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation
return WrappedMediaResourceId(representation.resource.id)
}
@ -166,6 +168,8 @@ enum PeerInfoAvatarListItem: Equatable {
final class PeerInfoAvatarListItemNode: ASDisplayNode {
private let context: AccountContext
let imageNode: TransformImageNode
private var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
let isReady = Promise<Bool>()
private var didSetReady: Bool = false
@ -192,13 +196,38 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
func setup(item: PeerInfoAvatarListItem, synchronous: Bool) {
let representations: [ImageRepresentationWithReference]
let videoRepresentations: [TelegramMediaImage.VideoRepresentation]
var id: Int64?
switch item {
case let .topImage(topRepresentations):
representations = topRepresentations
case let .image(_, imageRepresentations):
videoRepresentations = []
case let .image(reference, imageRepresentations, videoRepresentationsValue):
representations = imageRepresentations
videoRepresentations = videoRepresentationsValue
if case let .cloud(imageId, _, _) = reference {
id = imageId
}
}
self.imageNode.setSignal(chatAvatarGalleryPhoto(account: self.context.account, representations: representations, autoFetchFullSize: true, attemptSynchronously: synchronous), attemptSynchronously: synchronous, dispatchOnDisplayLink: false)
if let video = videoRepresentations.last, let id = id {
let mediaManager = self.context.sharedContext.mediaManager
let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id), fileReference: videoFileReference, streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black)
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
videoNode.isUserInteractionEnabled = false
videoNode.ownsContentNodeUpdated = { [weak self] owns in
if let strongSelf = self {
strongSelf.videoNode?.isHidden = !owns
}
}
self.videoContent = videoContent
self.videoNode = videoNode
self.addSubnode(videoNode)
}
}
func update(size: CGSize, transition: ContainedViewLayoutTransition) {
@ -206,7 +235,18 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
let makeLayout = self.imageNode.asyncLayout()
let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
let _ = applyLayout()
transition.updateFrame(node: self.imageNode, frame: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize))
let imageFrame = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)
transition.updateFrame(node: self.imageNode, frame: imageFrame)
if let videoNode = self.videoNode {
videoNode.updateLayout(size: imageSize, transition: .immediate)
videoNode.frame = imageFrame
videoNode.canAttachContent = true
if videoNode.hasAttachedContext {
videoNode.play()
}
}
}
}
@ -537,8 +577,8 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
switch entry {
case let .topImage(representations, _):
items.append(.topImage(representations))
case let .image(_, reference, representations, _, _, _, _):
items.append(.image(reference, representations))
case let .image(_, reference, representations, videoRepresentations, _, _, _, _):
items.append(.image(reference, representations, videoRepresentations))
}
}
strongSelf.galleryEntries = entries

View File

@ -2806,7 +2806,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
self.view.endEditing(true)
controller.push(channelStatsController(context: self.context, peerId: peer.id, cachedPeerData: cachedData))
let statsController: ViewController
if let channel = peer as? TelegramChannel, case .group = channel.info {
statsController = groupStatsController(context: self.context, peerId: peer.id, cachedPeerData: cachedData)
} else {
statsController = channelStatsController(context: self.context, peerId: peer.id, cachedPeerData: cachedData)
}
controller.push(statsController)
}
private func openReport(user: Bool) {

View File

@ -106,7 +106,7 @@ private enum StickerSearchEntry: Identifiable, Comparable {
case let .global(_, info, topItems, installed, topSeparator):
let itemContext = StickerPaneSearchGlobalItemContext()
itemContext.canPlayMedia = true
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
interaction.open(info)
}, install: {
interaction.install(info, topItems, !installed)

View File

@ -81,7 +81,6 @@ final class StickerPaneSearchGlobalItem: GridItem {
let listAppearance: Bool
let info: StickerPackCollectionInfo
let topItems: [StickerPackItem]
let grid: Bool
let topSeparator: Bool
let regularInsets: Bool
let installed: Bool
@ -93,7 +92,7 @@ final class StickerPaneSearchGlobalItem: GridItem {
let itemContext: StickerPaneSearchGlobalItemContext
let section: GridSection?
var fillsRowWithHeight: CGFloat? {
var fillsRowWithHeight: (CGFloat, Bool)? {
var additionalHeight: CGFloat = 0.0
if self.regularInsets {
additionalHeight = 12.0 + 12.0
@ -104,17 +103,16 @@ final class StickerPaneSearchGlobalItem: GridItem {
}
}
return self.grid ? nil : (128.0 + additionalHeight)
return (128.0 + additionalHeight, false)
}
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, 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, 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.info = info
self.topItems = topItems
self.grid = grid
self.topSeparator = topSeparator
self.regularInsets = regularInsets
self.installed = installed

View File

@ -15,6 +15,7 @@ public enum NativeVideoContentId: Hashable {
case message(UInt32, MediaId)
case instantPage(MediaId, MediaId)
case contextResult(Int64, String)
case profileVideo(Int64)
}
public final class NativeVideoContent: UniversalVideoContent {

View File

@ -324,7 +324,7 @@ class WebSearchGalleryController: ViewController {
centralItemNode.activateAsInitial()
if presentationArguments.animated {
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface)
centralItemNode.animateIn(from: transitionArguments.transitionNode, addToTransitionSurface: transitionArguments.addToTransitionSurface, completion: {})
}
if let checkNode = self.checkNode, let controllerInteraction = self.controllerInteraction, let selectionState = controllerInteraction.selectionState {

View File

@ -291,7 +291,7 @@ final class WebSearchVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void) {
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
guard let videoNode = self.videoNode else {
return
}