mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
03a84fda99
commit
df70d5d718
@ -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";
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)))
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)))
|
||||
|
@ -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)
|
||||
|
@ -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]))
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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];
|
||||
|
@ -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)
|
||||
|
@ -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];
|
||||
|
@ -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 = ^
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -11,4 +11,6 @@
|
||||
- (void)setScrubberPosition:(NSTimeInterval)position reset:(bool)reset;
|
||||
- (void)setScrubberPlaying:(bool)value;
|
||||
|
||||
- (NSTimeInterval)coverPosition;
|
||||
|
||||
@end
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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]))
|
||||
|
@ -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 {
|
@ -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]))
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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?()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -26,6 +26,7 @@ swift_library(
|
||||
"//submodules/GraphCore:GraphCore",
|
||||
"//submodules/GraphUI:GraphUI",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/ItemListPeerItem:ItemListPeerItem",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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))
|
||||
}
|
558
submodules/StatisticsUI/Sources/GroupStatsController.swift
Normal file
558
submodules/StatisticsUI/Sources/GroupStatsController.swift
Normal 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
|
||||
}
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>) {
|
||||
|
@ -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
|
||||
}()
|
||||
|
@ -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) })
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
1066
submodules/TelegramCore/Sources/PeerStatistics.swift
Normal file
1066
submodules/TelegramCore/Sources/PeerStatistics.swift
Normal file
File diff suppressed because it is too large
Load Diff
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -14,6 +14,7 @@ import LegacyUI
|
||||
import ImageCompression
|
||||
import LocalMediaResources
|
||||
import AppBundle
|
||||
import LegacyMediaPickerUI
|
||||
|
||||
final class InstantVideoControllerRecordingStatus {
|
||||
let micLevel: Signal<Float, NoError>
|
||||
|
@ -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
|
||||
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user