Video improvements

This commit is contained in:
Isaac 2024-10-29 00:40:32 +01:00
parent b3d94d8092
commit b3e0d64b37
33 changed files with 966 additions and 229 deletions

View File

@ -126,13 +126,18 @@ public final class RoundedRectangle: Component {
}
public final class FilledRoundedRectangleComponent: Component {
public enum CornerRadius: Equatable {
case value(CGFloat)
case minEdge
}
public let color: UIColor
public let cornerRadius: CGFloat
public let cornerRadius: CornerRadius
public let smoothCorners: Bool
public init(
color: UIColor,
cornerRadius: CGFloat,
cornerRadius: CornerRadius,
smoothCorners: Bool
) {
self.color = color
@ -216,9 +221,17 @@ public final class FilledRoundedRectangleComponent: Component {
transition.setTintColor(view: self, color: component.color)
if self.currentCornerRadius != component.cornerRadius {
let cornerRadius: CGFloat
switch component.cornerRadius {
case let .value(value):
cornerRadius = value
case .minEdge:
cornerRadius = min(availableSize.width, availableSize.height) * 0.5
}
if self.currentCornerRadius != cornerRadius {
let previousCornerRadius = self.currentCornerRadius
self.currentCornerRadius = component.cornerRadius
self.currentCornerRadius = cornerRadius
if transition.animation.isImmediate {
self.applyStaticCornerRadius()
} else {
@ -236,7 +249,7 @@ public final class FilledRoundedRectangleComponent: Component {
}
}
transition.setCornerRadius(layer: self.layer, cornerRadius: component.cornerRadius, completion: { [weak self] completed in
transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius, completion: { [weak self] completed in
guard let self, completed else {
return
}

View File

@ -57,6 +57,9 @@ swift_library(
"//submodules/TelegramUI/Components/SaveProgressScreen",
"//submodules/TelegramUI/Components/RasterizedCompositionComponent",
"//submodules/TelegramUI/Components/BadgeComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/ComponentFlow",
],
visibility = [

View File

@ -303,7 +303,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
self.pager.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: layout.size)
self.pager.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
self.pager.containerLayoutUpdated(layout, navigationBarHeight: self.areControlsHidden ? 0.0 : navigationBarHeight, transition: transition)
}
open func setControlsHidden(_ hidden: Bool, animated: Bool) {

View File

@ -11,6 +11,11 @@ public enum GalleryItemNodeNavigationStyle {
}
open class GalleryItemNode: ASDisplayNode {
public enum ActiveEdge {
case left
case right
}
private var _index: Int?
public var index: Int {
get {
@ -111,4 +116,14 @@ open class GalleryItemNode: ASDisplayNode {
open var keyShortcuts: [KeyShortcut] {
return []
}
open func hasActiveEdgeAction(edge: ActiveEdge) -> Bool {
return false
}
open func setActiveEdgeAction(edge: ActiveEdge?) {
}
open func adjustActiveEdgeAction(distance: CGFloat) {
}
}

View File

@ -9,6 +9,10 @@ private func edgeWidth(width: CGFloat) -> CGFloat {
return min(44.0, floor(width / 6.0))
}
private func activeEdgeWidth(width: CGFloat) -> CGFloat {
return floor(width * 0.4)
}
let fadeWidth: CGFloat = 70.0
private let leftFadeImage = generateImage(CGSize(width: fadeWidth, height: 32.0), opaque: false, rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
@ -85,6 +89,9 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
private let leftFadeNode: ASDisplayNode
private let rightFadeNode: ASDisplayNode
private var highlightedSide: Bool?
private var activeSide: Bool?
private var canPerformSideNavigationAction: Bool = false
private var sideActionInitialPosition: CGPoint?
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
@ -118,6 +125,8 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
public var pagingEnabledPromise = Promise<Bool>(true)
private var pagingEnabledDisposable: Disposable?
private var edgeLongTapTimer: Foundation.Timer?
public init(pageGap: CGFloat, disableTapNavigation: Bool) {
self.pageGap = pageGap
self.disableTapNavigation = disableTapNavigation
@ -170,57 +179,99 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
recognizer.delegate = self.wrappedGestureRecognizerDelegate
self.tapRecognizer = recognizer
recognizer.tapActionAtPoint = { [weak self] point in
guard let strongSelf = self, strongSelf.pagingEnabled else {
guard let strongSelf = self else {
return .fail
}
let size = strongSelf.bounds
var highlightedSide: Bool?
if point.x < edgeWidth(width: size.width) && strongSelf.canGoToPreviousItem() {
if strongSelf.items.count > 1 {
highlightedSide = false
var activeSide: Bool?
if point.x < edgeWidth(width: size.width) {
if strongSelf.canGoToPreviousItem() {
if strongSelf.items.count > 1 {
highlightedSide = false
}
}
} else if point.x > size.width - edgeWidth(width: size.width) && strongSelf.canGoToNextItem() {
if strongSelf.items.count > 1 {
if point.y < 80.0 {
highlightedSide = nil
} else {
} else if point.x > size.width - edgeWidth(width: size.width) {
if strongSelf.canGoToNextItem() {
if strongSelf.items.count > 1 {
highlightedSide = true
}
}
}
if point.x < activeEdgeWidth(width: size.width) {
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .left) {
activeSide = false
}
} else if point.x > size.width - activeEdgeWidth(width: size.width) {
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .right) {
activeSide = true
}
}
if highlightedSide == nil {
if !strongSelf.pagingEnabled {
highlightedSide = nil
}
if highlightedSide == nil && activeSide == nil {
return .fail
}
if let result = strongSelf.hitTest(point, with: nil), let _ = result.asyncdisplaykit_node as? ASButtonNode {
return .fail
}
return .keepWithSingleTap
if activeSide != nil {
return .waitForHold(timeout: 0.3, acceptTap: true)
} else {
return .keepWithSingleTap
}
}
recognizer.highlight = { [weak self] point in
guard let strongSelf = self, strongSelf.pagingEnabled else {
guard let strongSelf = self else {
return
}
let size = strongSelf.bounds
var highlightedSide: Bool?
if let point = point {
if point.x < edgeWidth(width: size.width) && strongSelf.canGoToPreviousItem() {
if strongSelf.items.count > 1 {
highlightedSide = false
var activeSide: Bool?
if let point {
if point.x < edgeWidth(width: size.width) {
if strongSelf.canGoToPreviousItem() {
if strongSelf.items.count > 1 {
highlightedSide = false
}
}
} else if point.x > size.width - edgeWidth(width: size.width) && strongSelf.canGoToNextItem() {
if strongSelf.items.count > 1 {
highlightedSide = true
} else if point.x > size.width - edgeWidth(width: size.width) {
if strongSelf.canGoToNextItem() {
if strongSelf.items.count > 1 {
highlightedSide = true
}
}
}
if point.x < activeEdgeWidth(width: size.width) {
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .left) {
activeSide = false
}
} else if point.x > size.width - activeEdgeWidth(width: size.width) {
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .right) {
activeSide = true
}
}
}
if !strongSelf.pagingEnabled {
highlightedSide = nil
}
if strongSelf.highlightedSide != highlightedSide {
strongSelf.highlightedSide = highlightedSide
if highlightedSide != nil {
strongSelf.canPerformSideNavigationAction = true
}
let leftAlpha: CGFloat
let rightAlpha: CGFloat
if let highlightedSide = highlightedSide {
@ -247,6 +298,47 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
}
}
}
if strongSelf.activeSide != activeSide {
strongSelf.activeSide = activeSide
if let activeSide, let centralIndex = strongSelf.centralItemIndex, let _ = strongSelf.visibleItemNode(at: centralIndex) {
if strongSelf.edgeLongTapTimer == nil {
strongSelf.edgeLongTapTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false, block: { _ in
guard let self else {
return
}
if let centralIndex = self.centralItemIndex, let itemNode = self.visibleItemNode(at: centralIndex) {
itemNode.setActiveEdgeAction(edge: activeSide ? .right : .left)
}
self.canPerformSideNavigationAction = false
let leftAlpha: CGFloat
let rightAlpha: CGFloat
leftAlpha = 0.0
rightAlpha = 0.0
if self.leftFadeNode.alpha != leftAlpha {
self.leftFadeNode.alpha = leftAlpha
self.leftFadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, timingFunction: kCAMediaTimingFunctionSpring)
}
if self.rightFadeNode.alpha != rightAlpha {
self.rightFadeNode.alpha = rightAlpha
self.rightFadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, timingFunction: kCAMediaTimingFunctionSpring)
}
})
}
} else if let edgeLongTapTimer = strongSelf.edgeLongTapTimer {
edgeLongTapTimer.invalidate()
strongSelf.edgeLongTapTimer = nil
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex) {
itemNode.setActiveEdgeAction(edge: nil)
}
}
}
}
self.view.addGestureRecognizer(recognizer)
}
@ -258,8 +350,9 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state {
case .ended:
self.sideActionInitialPosition = nil
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
if case .tap = gesture {
if case .tap = gesture, self.canPerformSideNavigationAction {
let size = self.bounds.size
if location.x < edgeWidth(width: size.width) && self.canGoToPreviousItem() {
self.goToPreviousItem()
@ -268,6 +361,21 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
}
}
}
case .cancelled:
self.sideActionInitialPosition = nil
case .began:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, case .hold = gesture {
self.sideActionInitialPosition = location
}
case .changed:
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, case .hold = gesture {
if let sideActionInitialPosition = self.sideActionInitialPosition {
let distance = location.x - sideActionInitialPosition.x
if let centralIndex = self.centralItemIndex, let itemNode = self.visibleItemNode(at: centralIndex) {
itemNode.adjustActiveEdgeAction(distance: distance)
}
}
}
default:
break
}

View File

@ -0,0 +1,97 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import AppBundle
final class GalleryRateToastAnimationComponent: Component {
init() {
}
static func ==(lhs: GalleryRateToastAnimationComponent, rhs: GalleryRateToastAnimationComponent) -> Bool {
return true
}
final class View: UIView {
private let itemViewContainer: UIView
private var itemViews: [UIImageView] = []
override init(frame: CGRect) {
self.itemViewContainer = UIView()
super.init(frame: frame)
self.addSubview(self.itemViewContainer)
let image = UIImage(bundleImageName: "Media Gallery/VideoRateToast")?.withRenderingMode(.alwaysTemplate)
for _ in 0 ..< 2 {
let itemView = UIImageView(image: image)
itemView.tintColor = .white
self.itemViews.append(itemView)
self.itemViewContainer.addSubview(itemView)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupAnimations() {
let beginTime = self.layer.convertTime(CACurrentMediaTime(), from: nil)
for i in 0 ..< self.itemViews.count {
if self.itemViews[i].layer.animation(forKey: "idle-opacity") != nil {
continue
}
let delay = Double(i) * 0.1
let animation = CABasicAnimation(keyPath: "opacity")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.beginTime = beginTime + delay
animation.fromValue = 0.6 as NSNumber
animation.toValue = 1.0 as NSNumber
animation.repeatCount = Float.infinity
animation.autoreverses = true
animation.fillMode = .both
animation.duration = 0.4
self.itemViews[i].layer.add(animation, forKey: "idle-opacity")
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
scaleAnimation.beginTime = beginTime + delay
scaleAnimation.fromValue = 0.9 as NSNumber
scaleAnimation.toValue = 1.1 as NSNumber
scaleAnimation.repeatCount = Float.infinity
scaleAnimation.autoreverses = true
scaleAnimation.fillMode = .both
scaleAnimation.duration = 0.4
self.itemViews[i].layer.add(scaleAnimation, forKey: "idle-scale")
}
}
func update(component: GalleryRateToastAnimationComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let itemSize = self.itemViews[0].image?.size ?? CGSize(width: 10.0, height: 10.0)
let itemSpacing: CGFloat = 1.0
let size = CGSize(width: itemSize.width * 2.0 + itemSpacing, height: 12.0)
for i in 0 ..< self.itemViews.count {
let itemFrame = CGRect(origin: CGPoint(x: CGFloat(i) * (itemSize.width + itemSpacing), y: UIScreenPixel), size: itemSize)
self.itemViews[i].frame = itemFrame
}
self.setupAnimations()
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -0,0 +1,122 @@
import Foundation
import UIKit
import Display
import ComponentFlow
import AppBundle
import MultilineTextComponent
import AnimatedTextComponent
final class GalleryRateToastComponent: Component {
let rate: Double
init(rate: Double) {
self.rate = rate
}
static func ==(lhs: GalleryRateToastComponent, rhs: GalleryRateToastComponent) -> Bool {
if lhs.rate != rhs.rate {
return false
}
return true
}
final class View: UIView {
private let background = ComponentView<Empty>()
private let text = ComponentView<Empty>()
private let arrows = ComponentView<Empty>()
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: GalleryRateToastComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let insets = UIEdgeInsets(top: 5.0, left: 11.0, bottom: 5.0, right: 16.0)
let spacing: CGFloat = 5.0
var rateString = String(format: "%.1f", component.rate)
if rateString.hasSuffix(".0") {
rateString = rateString.replacingOccurrences(of: ".0", with: "")
}
var textItems: [AnimatedTextComponent.Item] = []
if let dotRange = rateString.range(of: ".") {
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("pre"), content: .text(String(rateString[rateString.startIndex ..< dotRange.lowerBound]))))
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("dot"), content: .text(".")))
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("post"), content: .text(String(rateString[dotRange.upperBound...]))))
} else {
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("pre"), content: .text(rateString)))
}
textItems.append(AnimatedTextComponent.Item(id: AnyHashable("x"), content: .text("x")))
let textSize = self.text.update(
transition: transition,
component: AnyComponent(AnimatedTextComponent(
font: Font.semibold(17.0),
color: .white,
items: textItems
)),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
let arrowsSize = self.arrows.update(
transition: transition,
component: AnyComponent(GalleryRateToastAnimationComponent()),
environment: {},
containerSize: CGSize(width: 200.0, height: 100.0)
)
let size = CGSize(width: insets.left + insets.right + textSize.width + arrowsSize.width, height: insets.top + insets.bottom + max(textSize.height, arrowsSize.height))
let _ = self.background.update(
transition: transition,
component: AnyComponent(FilledRoundedRectangleComponent(
color: UIColor(white: 0.0, alpha: 0.5),
cornerRadius: .minEdge,
smoothCorners: false
)),
environment: {},
containerSize: size
)
let backgroundFrame = CGRect(origin: CGPoint(), size: size)
if let backgroundView = self.background.view {
if backgroundView.superview == nil {
self.addSubview(backgroundView)
}
transition.setFrame(view: backgroundView, frame: backgroundFrame)
}
let textFrame = CGRect(origin: CGPoint(x: insets.left, y: floorToScreenPixels((size.height - textSize.height) * 0.5)), size: textSize)
if let textView = self.text.view {
if textView.superview == nil {
textView.layer.anchorPoint = CGPoint()
self.addSubview(textView)
}
transition.setPosition(view: textView, position: textFrame.origin)
textView.bounds = CGRect(origin: CGPoint(), size: textFrame.size)
}
let arrowsFrame = CGRect(origin: CGPoint(x: textFrame.maxX + spacing, y: floorToScreenPixels((size.height - arrowsSize.height) * 0.5)), size: arrowsSize)
if let arrowsView = self.arrows.view {
if arrowsView.superview == nil {
self.addSubview(arrowsView)
}
transition.setFrame(view: arrowsView, frame: arrowsFrame)
}
return size
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View File

@ -32,6 +32,7 @@ import SectionTitleContextItem
import RasterizedCompositionComponent
import BadgeComponent
import ComponentFlow
import ComponentDisplayAdapters
public enum UniversalVideoGalleryItemContentInfo {
case message(Message, Int?)
@ -1318,7 +1319,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private var playOnContentOwnership = false
private var skipInitialPause = false
private var ignorePauseStatus = false
private var validLayout: (ContainerViewLayout, CGFloat)?
private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat)?
private var didPause = false
private var isPaused = true
private var dismissOnOrientationChange = false
@ -1368,6 +1369,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private var activePictureInPictureNavigationController: NavigationController?
private var activePictureInPictureController: ViewController?
private var activeEdgeRateState: (initialRate: Double, currentRate: Double)?
private var activeEdgeRateIndicator: ComponentView<Empty>?
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.context = context
self.presentationData = presentationData
@ -1603,6 +1607,46 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
pictureInPictureNode.updateLayout(placeholderSize, transition: transition)
}
}
if let activeEdgeRateState = self.activeEdgeRateState {
var activeEdgeRateIndicatorTransition = transition
let activeEdgeRateIndicator: ComponentView<Empty>
if let current = self.activeEdgeRateIndicator {
activeEdgeRateIndicator = current
} else {
activeEdgeRateIndicator = ComponentView()
self.activeEdgeRateIndicator = activeEdgeRateIndicator
activeEdgeRateIndicatorTransition = .immediate
}
let activeEdgeRateIndicatorSize = activeEdgeRateIndicator.update(
transition: ComponentTransition(activeEdgeRateIndicatorTransition),
component: AnyComponent(GalleryRateToastComponent(
rate: activeEdgeRateState.currentRate
)),
environment: {},
containerSize: CGSize(width: 200.0, height: 100.0)
)
let activeEdgeRateIndicatorFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - activeEdgeRateIndicatorSize.width) * 0.5), y: max(navigationBarHeight, layout.statusBarHeight ?? 0.0) + 8.0), size: activeEdgeRateIndicatorSize)
if let activeEdgeRateIndicatorView = activeEdgeRateIndicator.view {
if activeEdgeRateIndicatorView.superview == nil {
self.view.addSubview(activeEdgeRateIndicatorView)
transition.animateTransformScale(view: activeEdgeRateIndicatorView, from: 0.001)
if transition.isAnimated {
activeEdgeRateIndicatorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
activeEdgeRateIndicatorTransition.updateFrame(view: activeEdgeRateIndicatorView, frame: activeEdgeRateIndicatorFrame)
}
} else if let activeEdgeRateIndicator = self.activeEdgeRateIndicator {
self.activeEdgeRateIndicator = nil
if let activeEdgeRateIndicatorView = activeEdgeRateIndicator.view {
transition.updateAlpha(layer: activeEdgeRateIndicatorView.layer, alpha: 0.0, completion: { [weak activeEdgeRateIndicatorView] _ in
activeEdgeRateIndicatorView?.removeFromSuperview()
})
transition.updateTransformScale(layer: activeEdgeRateIndicatorView.layer, scale: 0.001)
}
}
if dismiss {
self.dismiss()
@ -3938,6 +3982,59 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
return keyShortcuts
}
override func hasActiveEdgeAction(edge: ActiveEdge) -> Bool {
if case .right = edge {
return true
} else {
return false
}
}
override func setActiveEdgeAction(edge: ActiveEdge?) {
guard let videoNode = self.videoNode else {
return
}
if let edge, case .right = edge {
let effectiveRate: Double
if let current = self.activeEdgeRateState {
effectiveRate = min(2.5, current.initialRate + 0.5)
self.activeEdgeRateState = (current.initialRate, effectiveRate)
} else {
guard let playbackRate = self.playbackRate else {
return
}
effectiveRate = min(2.5, playbackRate + 0.5)
self.activeEdgeRateState = (playbackRate, effectiveRate)
}
videoNode.setBaseRate(effectiveRate)
} else if let (initialRate, _) = self.activeEdgeRateState {
self.activeEdgeRateState = nil
videoNode.setBaseRate(initialRate)
}
if let validLayout = self.validLayout {
self.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: .animated(duration: 0.35, curve: .spring))
}
}
override func adjustActiveEdgeAction(distance: CGFloat) {
guard let videoNode = self.videoNode else {
return
}
if let current = self.activeEdgeRateState {
var rateFraction = Double(distance) / 100.0
rateFraction = max(0.0, min(1.0, rateFraction))
let rateDistance = (current.initialRate + 0.5) * (1.0 - rateFraction) + 2.5 * rateFraction
let effectiveRate = max(1.0, min(2.5, rateDistance))
self.activeEdgeRateState = (current.initialRate, effectiveRate)
videoNode.setBaseRate(effectiveRate)
if let validLayout = self.validLayout {
self.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: .animated(duration: 0.35, curve: .spring))
}
}
}
}
final class HeaderContextReferenceContentSource: ContextReferenceContentSource {

View File

@ -902,7 +902,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1401868056] = { return Api.StarsSubscription.parse_starsSubscription($0) }
dict[88173912] = { return Api.StarsSubscriptionPricing.parse_starsSubscriptionPricing($0) }
dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($0) }
dict[-1216644148] = { return Api.StarsTransaction.parse_starsTransaction($0) }
dict[903148150] = { return Api.StarsTransaction.parse_starsTransaction($0) }
dict[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) }
dict[-110658899] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAPI($0) }
dict[1617438738] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAds($0) }
@ -1005,7 +1005,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1576161051] = { return Api.Update.parse_updateDeleteMessages($0) }
dict[1407644140] = { return Api.Update.parse_updateDeleteQuickReply($0) }
dict[1450174413] = { return Api.Update.parse_updateDeleteQuickReplyMessages($0) }
dict[-1870238482] = { return Api.Update.parse_updateDeleteScheduledMessages($0) }
dict[-223929981] = { return Api.Update.parse_updateDeleteScheduledMessages($0) }
dict[654302845] = { return Api.Update.parse_updateDialogFilter($0) }
dict[-1512627963] = { return Api.Update.parse_updateDialogFilterOrder($0) }
dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) }

View File

@ -1010,13 +1010,13 @@ public extension Api {
}
public extension Api {
enum StarsTransaction: TypeConstructorDescription {
case starsTransaction(flags: Int32, id: String, stars: Int64, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipDate: Int32?, floodskipNumber: Int32?)
case starsTransaction(flags: Int32, id: String, stars: Int64, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipNumber: Int32?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipDate, let floodskipNumber):
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber):
if boxed {
buffer.appendInt32(-1216644148)
buffer.appendInt32(903148150)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(id, buffer: buffer, boxed: false)
@ -1038,7 +1038,6 @@ public extension Api {
if Int(flags) & Int(1 << 12) != 0 {serializeInt32(subscriptionPeriod!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 13) != 0 {serializeInt32(giveawayPostId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 14) != 0 {stargift!.serialize(buffer, true)}
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(floodskipDate!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(floodskipNumber!, buffer: buffer, boxed: false)}
break
}
@ -1046,8 +1045,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipDate, let floodskipNumber):
return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any), ("subscriptionPeriod", subscriptionPeriod as Any), ("giveawayPostId", giveawayPostId as Any), ("stargift", stargift as Any), ("floodskipDate", floodskipDate as Any), ("floodskipNumber", floodskipNumber as Any)])
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber):
return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any), ("subscriptionPeriod", subscriptionPeriod as Any), ("giveawayPostId", giveawayPostId as Any), ("stargift", stargift as Any), ("floodskipNumber", floodskipNumber as Any)])
}
}
@ -1094,8 +1093,6 @@ public extension Api {
} }
var _17: Int32?
if Int(_1!) & Int(1 << 15) != 0 {_17 = reader.readInt32() }
var _18: Int32?
if Int(_1!) & Int(1 << 15) != 0 {_18 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
@ -1113,9 +1110,8 @@ public extension Api {
let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil
let _c16 = (Int(_1!) & Int(1 << 14) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil
let _c18 = (Int(_1!) & Int(1 << 15) == 0) || _18 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 {
return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipDate: _17, floodskipNumber: _18)
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 {
return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipNumber: _17)
}
else {
return nil

View File

@ -676,7 +676,7 @@ public extension Api {
case updateDeleteMessages(messages: [Int32], pts: Int32, ptsCount: Int32)
case updateDeleteQuickReply(shortcutId: Int32)
case updateDeleteQuickReplyMessages(shortcutId: Int32, messages: [Int32])
case updateDeleteScheduledMessages(peer: Api.Peer, messages: [Int32])
case updateDeleteScheduledMessages(flags: Int32, peer: Api.Peer, messages: [Int32], sentMessages: [Int32]?)
case updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?)
case updateDialogFilterOrder(order: [Int32])
case updateDialogFilters
@ -1246,16 +1246,22 @@ public extension Api {
serializeInt32(item, buffer: buffer, boxed: false)
}
break
case .updateDeleteScheduledMessages(let peer, let messages):
case .updateDeleteScheduledMessages(let flags, let peer, let messages, let sentMessages):
if boxed {
buffer.appendInt32(-1870238482)
buffer.appendInt32(-223929981)
}
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(messages.count))
for item in messages {
serializeInt32(item, buffer: buffer, boxed: false)
}
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(sentMessages!.count))
for item in sentMessages! {
serializeInt32(item, buffer: buffer, boxed: false)
}}
break
case .updateDialogFilter(let flags, let id, let filter):
if boxed {
@ -2097,8 +2103,8 @@ public extension Api {
return ("updateDeleteQuickReply", [("shortcutId", shortcutId as Any)])
case .updateDeleteQuickReplyMessages(let shortcutId, let messages):
return ("updateDeleteQuickReplyMessages", [("shortcutId", shortcutId as Any), ("messages", messages as Any)])
case .updateDeleteScheduledMessages(let peer, let messages):
return ("updateDeleteScheduledMessages", [("peer", peer as Any), ("messages", messages as Any)])
case .updateDeleteScheduledMessages(let flags, let peer, let messages, let sentMessages):
return ("updateDeleteScheduledMessages", [("flags", flags as Any), ("peer", peer as Any), ("messages", messages as Any), ("sentMessages", sentMessages as Any)])
case .updateDialogFilter(let flags, let id, let filter):
return ("updateDialogFilter", [("flags", flags as Any), ("id", id as Any), ("filter", filter as Any)])
case .updateDialogFilterOrder(let order):
@ -3309,18 +3315,26 @@ public extension Api {
}
}
public static func parse_updateDeleteScheduledMessages(_ reader: BufferReader) -> Update? {
var _1: Api.Peer?
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
_2 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: [Int32]?
var _3: [Int32]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
_3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
var _4: [Int32]?
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.Update.updateDeleteScheduledMessages(peer: _1!, messages: _2!)
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.Update.updateDeleteScheduledMessages(flags: _1!, peer: _2!, messages: _3!, sentMessages: _4)
}
else {
return nil

View File

@ -105,7 +105,7 @@ final class VideoChatExpandedSpeakingToastComponent: Component {
transition: transition,
component: AnyComponent(FilledRoundedRectangleComponent(
color: UIColor(white: 0.0, alpha: 0.9),
cornerRadius: size.height * 0.5,
cornerRadius: .value(size.height * 0.5),
smoothCorners: false
)),
environment: {},

View File

@ -1224,7 +1224,7 @@ final class VideoChatScreenComponent: Component {
)),
background: AnyComponent(FilledRoundedRectangleComponent(
color: UIColor(white: 1.0, alpha: 0.1),
cornerRadius: navigationButtonDiameter * 0.5,
cornerRadius: .value(navigationButtonDiameter * 0.5),
smoothCorners: false
)),
effectAlignment: .center,

View File

@ -212,6 +212,7 @@ struct AccountMutableState {
var namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]]
var updatedOutgoingUniqueMessageIds: [Int64: Int32]
var storedStories: [StoryId: UpdatesStoredStory]
var sentScheduledMessageIds: Set<MessageId>
var resetForumTopicLists: [PeerId: StateResetForumTopics] = [:]
@ -231,7 +232,7 @@ struct AccountMutableState {
var authorizationListUpdated: Bool = false
init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialReferencedReplyMessageIds: ReferencedReplyMessageIds, initialReferencedGeneralMessageIds: Set<MessageId>, initialStoredMessages: Set<MessageId>, initialStoredStories: [StoryId: UpdatesStoredStory], initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>]) {
init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialReferencedReplyMessageIds: ReferencedReplyMessageIds, initialReferencedGeneralMessageIds: Set<MessageId>, initialStoredMessages: Set<MessageId>, initialStoredStories: [StoryId: UpdatesStoredStory], initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], initialSentScheduledMessageIds: Set<MessageId>) {
self.initialState = initialState
self.state = initialState.state
self.peers = initialPeers
@ -240,6 +241,7 @@ struct AccountMutableState {
self.referencedGeneralMessageIds = initialReferencedGeneralMessageIds
self.storedMessages = initialStoredMessages
self.storedStories = initialStoredStories
self.sentScheduledMessageIds = initialSentScheduledMessageIds
self.readInboxMaxIds = initialReadInboxMaxIds
self.channelStates = initialState.channelStates
self.peerChatInfos = initialState.peerChatInfos
@ -249,7 +251,7 @@ struct AccountMutableState {
self.updatedOutgoingUniqueMessageIds = [:]
}
init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], apiChats: [PeerId: Api.Chat], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedReplyMessageIds: ReferencedReplyMessageIds, referencedGeneralMessageIds: Set<MessageId>, storedMessages: Set<MessageId>, storedStories: [StoryId: UpdatesStoredStory], readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], dismissBotWebViews: [Int64], branchOperationIndex: Int) {
init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], apiChats: [PeerId: Api.Chat], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedReplyMessageIds: ReferencedReplyMessageIds, referencedGeneralMessageIds: Set<MessageId>, storedMessages: Set<MessageId>, storedStories: [StoryId: UpdatesStoredStory], sentScheduledMessageIds: Set<MessageId>, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], dismissBotWebViews: [Int64], branchOperationIndex: Int) {
self.initialState = initialState
self.operations = operations
self.state = state
@ -260,6 +262,7 @@ struct AccountMutableState {
self.referencedGeneralMessageIds = referencedGeneralMessageIds
self.storedMessages = storedMessages
self.storedStories = storedStories
self.sentScheduledMessageIds = sentScheduledMessageIds
self.peerChatInfos = peerChatInfos
self.readInboxMaxIds = readInboxMaxIds
self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp
@ -271,7 +274,7 @@ struct AccountMutableState {
}
func branch() -> AccountMutableState {
return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, apiChats: self.apiChats, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedReplyMessageIds: self.referencedReplyMessageIds, referencedGeneralMessageIds: self.referencedGeneralMessageIds, storedMessages: self.storedMessages, storedStories: self.storedStories, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, dismissBotWebViews: self.dismissBotWebViews, branchOperationIndex: self.operations.count)
return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, apiChats: self.apiChats, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedReplyMessageIds: self.referencedReplyMessageIds, referencedGeneralMessageIds: self.referencedGeneralMessageIds, storedMessages: self.storedMessages, storedStories: self.storedStories, sentScheduledMessageIds: self.sentScheduledMessageIds, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, dismissBotWebViews: self.dismissBotWebViews, branchOperationIndex: self.operations.count)
}
mutating func merge(_ other: AccountMutableState) {
@ -282,6 +285,8 @@ struct AccountMutableState {
self.storedStories[id] = story
}
self.sentScheduledMessageIds.formUnion(other.sentScheduledMessageIds)
for i in other.branchOperationIndex ..< other.operations.count {
self.addOperation(other.operations[i])
}
@ -357,6 +362,10 @@ struct AccountMutableState {
self.addOperation(.DeleteMessages(messageIds))
}
mutating func addSentScheduledMessageIds(_ messageIds: [MessageId]) {
self.sentScheduledMessageIds.formUnion(messageIds)
}
mutating func editMessage(_ id: MessageId, message: StoreMessage) {
self.addOperation(.EditMessage(id, message))
}
@ -842,13 +851,15 @@ struct AccountReplayedFinalState {
let updatedRevenueBalances: [PeerId: RevenueStats.Balances]
let updatedStarsBalance: [PeerId: Int64]
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
let sentScheduledMessageIds: Set<MessageId>
}
struct AccountFinalStateEvents {
let addedIncomingMessageIds: [MessageId]
let addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)]
let wasScheduledMessageIds:[MessageId]
let wasScheduledMessageIds: [MessageId]
let deletedMessageIds: [DeletedMessageId]
let sentScheduledMessageIds: Set<MessageId>
let updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]]
let updatedWebpages: [MediaId: TelegramMediaWebpage]
let updatedCalls: [Api.PhoneCall]
@ -873,10 +884,10 @@ struct AccountFinalStateEvents {
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
var isEmpty: Bool {
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty
}
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: Int64] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:]) {
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: Int64] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set()) {
self.addedIncomingMessageIds = addedIncomingMessageIds
self.addedReactionEvents = addedReactionEvents
self.wasScheduledMessageIds = wasScheduledMessageIds
@ -903,6 +914,7 @@ struct AccountFinalStateEvents {
self.updatedRevenueBalances = updatedRevenueBalances
self.updatedStarsBalance = updatedStarsBalance
self.updatedStarsRevenueStatus = updatedStarsRevenueStatus
self.sentScheduledMessageIds = sentScheduledMessageIds
}
init(state: AccountReplayedFinalState) {
@ -932,6 +944,7 @@ struct AccountFinalStateEvents {
self.updatedRevenueBalances = state.updatedRevenueBalances
self.updatedStarsBalance = state.updatedStarsBalance
self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus
self.sentScheduledMessageIds = state.sentScheduledMessageIds
}
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
@ -961,6 +974,9 @@ struct AccountFinalStateEvents {
let isPremiumUpdated = self.isPremiumUpdated || other.isPremiumUpdated
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }))
var sentScheduledMessageIds = self.sentScheduledMessageIds
sentScheduledMessageIds.formUnion(other.sentScheduledMessageIds)
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }), sentScheduledMessageIds: sentScheduledMessageIds)
}
}

View File

@ -529,7 +529,7 @@ func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set<PeerId>, a
}
}
let state = AccountMutableState(initialState: AccountInitialState(state: (transaction.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates, channelsToPollExplicitely: channelsToPollExplicitely), initialPeers: peers, initialReferencedReplyMessageIds: referencedReplyMessageIds, initialReferencedGeneralMessageIds: referencedGeneralMessageIds, initialStoredMessages: storedMessages, initialStoredStories: storedStories, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp)
let state = AccountMutableState(initialState: AccountInitialState(state: (transaction.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates, channelsToPollExplicitely: channelsToPollExplicitely), initialPeers: peers, initialReferencedReplyMessageIds: referencedReplyMessageIds, initialReferencedGeneralMessageIds: referencedGeneralMessageIds, initialStoredMessages: storedMessages, initialStoredStories: storedStories, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp, initialSentScheduledMessageIds: Set())
return state
}
@ -1666,12 +1666,19 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
if let message = StoreMessage(apiMessage: apiMessage, accountPeerId: accountPeerId, peerIsForum: peerIsForum, namespace: Namespaces.Message.QuickReplyCloud) {
updatedState.addQuickReplyMessages([message])
}
case let .updateDeleteScheduledMessages(peer, messages):
case let .updateDeleteScheduledMessages(_, peer, messages, sentMessages):
var messageIds: [MessageId] = []
var sentMessageIds: [MessageId] = []
for message in messages {
messageIds.append(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.ScheduledCloud, id: message))
}
if let sentMessages {
for message in sentMessages {
sentMessageIds.append(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: message))
}
}
updatedState.deleteMessages(messageIds)
updatedState.addSentScheduledMessageIds(sentMessageIds)
case let .updateDeleteQuickReplyMessages(_, messages):
var messageIds: [MessageId] = []
for message in messages {
@ -2628,7 +2635,7 @@ func pollChannelOnce(accountPeerId: PeerId, postbox: Postbox, network: Network,
peerChatInfos[peerId] = PeerChatInfo(notificationSettings: notificationSettings)
}
}
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedReplyMessageIds: ReferencedReplyMessageIds(), initialReferencedGeneralMessageIds: Set(), initialStoredMessages: Set(), initialStoredStories: [:], initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedReplyMessageIds: ReferencedReplyMessageIds(), initialReferencedGeneralMessageIds: Set(), initialStoredMessages: Set(), initialStoredStories: [:], initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:], initialSentScheduledMessageIds: Set())
return pollChannel(accountPeerId: accountPeerId, postbox: postbox, network: network, peer: peer, state: initialState)
|> mapToSignal { (finalState, _, timeout) -> Signal<Int32, NoError> in
return resolveAssociatedMessages(accountPeerId: accountPeerId, postbox: postbox, network: network, state: finalState)
@ -2685,7 +2692,7 @@ public func standalonePollChannelOnce(accountPeerId: PeerId, postbox: Postbox, n
peerChatInfos[peerId] = PeerChatInfo(notificationSettings: notificationSettings)
}
}
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedReplyMessageIds: ReferencedReplyMessageIds(), initialReferencedGeneralMessageIds: Set(), initialStoredMessages: Set(), initialStoredStories: [:], initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedReplyMessageIds: ReferencedReplyMessageIds(), initialReferencedGeneralMessageIds: Set(), initialStoredMessages: Set(), initialStoredStories: [:], initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:], initialSentScheduledMessageIds: Set())
return pollChannel(accountPeerId: accountPeerId, postbox: postbox, network: network, peer: peer, state: initialState)
|> mapToSignal { (finalState, _, timeout) -> Signal<Never, NoError> in
return resolveAssociatedMessages(accountPeerId: accountPeerId, postbox: postbox, network: network, state: finalState)
@ -5342,5 +5349,29 @@ func replayFinalState(
_internal_setStarsReactionDefaultToPrivate(isPrivate: updatedStarsReactionsAreAnonymousByDefault, transaction: transaction)
}
return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, addedReactionEvents: addedReactionEvents, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, deletedMessageIds: deletedMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedGroupCallParticipants: updatedGroupCallParticipants, storyUpdates: storyUpdates, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates, updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: updatedRevenueBalances, updatedStarsBalance: updatedStarsBalance, updatedStarsRevenueStatus: updatedStarsRevenueStatus)
return AccountReplayedFinalState(
state: finalState,
addedIncomingMessageIds: addedIncomingMessageIds,
addedReactionEvents: addedReactionEvents,
wasScheduledMessageIds: wasScheduledMessageIds,
addedSecretMessageIds: addedSecretMessageIds,
deletedMessageIds: deletedMessageIds,
updatedTypingActivities: updatedTypingActivities,
updatedWebpages: updatedWebpages,
updatedCalls: updatedCalls,
addedCallSignalingData: addedCallSignalingData,
updatedGroupCallParticipants: updatedGroupCallParticipants,
storyUpdates: storyUpdates,
updatedPeersNearby: updatedPeersNearby,
isContactUpdates: isContactUpdates,
delayNotificatonsUntil: delayNotificatonsUntil,
updatedIncomingThreadReadStates: updatedIncomingThreadReadStates,
updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates,
updateConfig: updateConfig,
isPremiumUpdated: isPremiumUpdated,
updatedRevenueBalances: updatedRevenueBalances,
updatedStarsBalance: updatedStarsBalance,
updatedStarsRevenueStatus: updatedStarsRevenueStatus,
sentScheduledMessageIds: finalState.state.sentScheduledMessageIds
)
}

View File

@ -63,6 +63,7 @@ public enum DeletedMessageId: Hashable {
final class MessagesRemovedContext {
private var messagesRemovedInteractively = Set<DeletedMessageId>()
private var messagesRemovedRemotely = Set<DeletedMessageId>()
private var messagesRemovedInteractivelyLock = NSLock()
func synchronouslyIsMessageDeletedInteractively(ids: [MessageId]) -> [EngineMessage.Id] {
@ -85,6 +86,26 @@ final class MessagesRemovedContext {
return result
}
func synchronouslyIsMessageDeletedRemotely(ids: [MessageId]) -> [EngineMessage.Id] {
var result: [EngineMessage.Id] = []
self.messagesRemovedInteractivelyLock.lock()
for id in ids {
let mappedId: DeletedMessageId
if id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup {
mappedId = .global(id.id)
} else {
mappedId = .messageId(id)
}
if self.messagesRemovedRemotely.contains(mappedId) {
result.append(id)
}
}
self.messagesRemovedInteractivelyLock.unlock()
return result
}
func addIsMessagesDeletedInteractively(ids: [DeletedMessageId]) {
if ids.isEmpty {
return
@ -94,6 +115,16 @@ final class MessagesRemovedContext {
self.messagesRemovedInteractively.formUnion(ids)
self.messagesRemovedInteractivelyLock.unlock()
}
func addIsMessagesDeletedRemotely(ids: [DeletedMessageId]) {
if ids.isEmpty {
return
}
self.messagesRemovedInteractivelyLock.lock()
self.messagesRemovedRemotely.formUnion(ids)
self.messagesRemovedInteractivelyLock.unlock()
}
}
public final class AccountStateManager {
@ -297,6 +328,11 @@ public final class AccountStateManager {
return self.forceSendPendingStarsReactionPipe.signal()
}
fileprivate let sentScheduledMessageIdsPipe = ValuePipe<Set<MessageId>>()
public var sentScheduledMessageIds: Signal<Set<MessageId>, NoError> {
return self.sentScheduledMessageIdsPipe.signal()
}
private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:]
private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext()
private var updatedRevenueBalancesContext = UpdatedRevenueBalancesSubscriberContext()
@ -690,6 +726,7 @@ public final class AccountStateManager {
if let result = result, !result.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
}
return result
@ -835,6 +872,7 @@ public final class AccountStateManager {
if let replayedState = replayedState {
if !replayedState.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: replayedState.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: replayedState.deletedMessageIds)
}
return (difference, replayedState, false, false)
@ -973,6 +1011,7 @@ public final class AccountStateManager {
if let result = result, !result.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
}
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
@ -1080,6 +1119,9 @@ public final class AccountStateManager {
if !events.updatedIncomingThreadReadStates.isEmpty || !events.updatedOutgoingThreadReadStates.isEmpty {
strongSelf.threadReadStateUpdatesPipe.putNext((events.updatedIncomingThreadReadStates, events.updatedOutgoingThreadReadStates))
}
if !events.sentScheduledMessageIds.isEmpty {
strongSelf.sentScheduledMessageIdsPipe.putNext(events.sentScheduledMessageIds)
}
if !events.isContactUpdates.isEmpty {
strongSelf.addIsContactUpdates(events.isContactUpdates)
}
@ -1248,6 +1290,7 @@ public final class AccountStateManager {
if let result = result, !result.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
}
return result
@ -1296,6 +1339,7 @@ public final class AccountStateManager {
if let result = result, !result.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
}
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
@ -1388,6 +1432,7 @@ public final class AccountStateManager {
if let replayedState = replayedState, !replayedState.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: replayedState.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: replayedState.deletedMessageIds)
}
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
@ -1889,6 +1934,12 @@ public final class AccountStateManager {
}
}
public var sentScheduledMessageIds: Signal<Set<MessageId>, NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.sentScheduledMessageIds.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
}
}
func forceSendPendingStarsReaction(messageId: MessageId) {
self.impl.with { impl in
impl.forceSendPendingStarsReactionPipe.putNext(messageId)
@ -2128,6 +2179,10 @@ public final class AccountStateManager {
public func synchronouslyIsMessageDeletedInteractively(ids: [EngineMessage.Id]) -> [EngineMessage.Id] {
return self.messagesRemovedContext.synchronouslyIsMessageDeletedInteractively(ids: ids)
}
public func synchronouslyIsMessageDeletedRemotely(ids: [EngineMessage.Id]) -> [EngineMessage.Id] {
return self.messagesRemovedContext.synchronouslyIsMessageDeletedRemotely(ids: ids)
}
}
func resolveNotificationSettings(list: [TelegramPeerNotificationSettings], defaultSettings: MessageNotificationSettings) -> (sound: PeerMessageSound, notify: Bool, displayContents: Bool) {

View File

@ -178,7 +178,9 @@ private func validatePeerReadState(network: Network, postbox: Postbox, stateMana
if case let .idBased(updatedMaxIncomingReadId, _, _, updatedCount, updatedMarkedUnread) = readState {
if updatedCount != 0 || updatedMarkedUnread {
if localMaxIncomingReadId > updatedMaxIncomingReadId {
return .retry
if !"".isEmpty {
return .retry
}
}
}
}

View File

@ -1401,6 +1401,10 @@ public extension TelegramEngine {
return self.account.stateManager.synchronouslyIsMessageDeletedInteractively(ids: ids)
}
public func synchronouslyIsMessageDeletedRemotely(ids: [EngineMessage.Id]) -> [EngineMessage.Id] {
return self.account.stateManager.synchronouslyIsMessageDeletedRemotely(ids: ids)
}
public func synchronouslyLookupCorrelationId(correlationId: Int64) -> EngineMessage.Id? {
return self.account.pendingMessageManager.synchronouslyLookupCorrelationId(correlationId: correlationId)
}

View File

@ -490,8 +490,7 @@ private final class StarsContextImpl {
private extension StarsContext.State.Transaction {
init?(apiTransaction: Api.StarsTransaction, peerId: EnginePeer.Id?, transaction: Transaction) {
switch apiTransaction {
case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod, giveawayPostId, starGift, floodskipDate, floodskipNumber):
let _ = floodskipDate
case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod, giveawayPostId, starGift, floodskipNumber):
let _ = floodskipNumber
let parsedPeer: StarsContext.State.Transaction.Peer

View File

@ -6118,14 +6118,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
override public func getStatusNode() -> ASDisplayNode? {
if let statusNode = self.mosaicStatusNode {
return statusNode
}
for contentNode in self.contentNodes {
if let statusNode = contentNode.getStatusNode() {
return statusNode
}
}
if let statusNode = self.mosaicStatusNode {
return statusNode
}
return nil
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "play.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,73 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 1.411133 cm
1.000000 1.000000 1.000000 scn
0.000000 8.588867 m
0.000000 0.588867 l
0.000000 -0.235178 0.940764 -0.705560 1.600000 -0.211133 c
6.933333 3.788867 l
7.466667 4.188867 7.466667 4.988867 6.933333 5.388867 c
1.600000 9.388867 l
0.940764 9.883294 0.000000 9.412912 0.000000 8.588867 c
h
f
n
Q
endstream
endobj
3 0 obj
378
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 8.000000 12.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000000468 00000 n
0000000490 00000 n
0000000662 00000 n
0000000736 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
795
%%EOF

View File

@ -1272,7 +1272,6 @@ extension ChatControllerImpl {
let _ = (strongSelf.shouldDivertMessagesToScheduled(messages: transformedMessages)
|> deliverOnMainQueue).start(next: { shouldDivert in
let signal: Signal<[MessageId?], NoError>
var stayInThisChat = false
var shouldOpenScheduledMessages = false
if forwardSourcePeerIds.count > 1 {
var forwardedMessages = forwardedMessages
@ -1302,7 +1301,6 @@ extension ChatControllerImpl {
}
return ids
}
stayInThisChat = true
} else {
var transformedMessages = transformedMessages
if shouldDivert {
@ -1334,56 +1332,6 @@ extension ChatControllerImpl {
strongSelf.layoutActionOnViewTransitionAction = nil
layoutActionOnViewTransitionAction()
}
if stayInThisChat {
strongSelf.dismissAllUndoControllers()
//TODO:localize
strongSelf.present(
UndoOverlayController(
presentationData: strongSelf.presentationData,
content: .info(
title: "Improving video...",
text: "The video will be published after it's optimized for the bese viewing experience.",
timeout: 8.0,
customUndoText: nil
),
elevatedLayout: false,
position: .top,
action: { _ in
return true
}
),
in: .current
)
} else {
strongSelf.openScheduledMessages(force: true, completion: { c in
guard let self else {
return
}
c.dismissAllUndoControllers()
//TODO:localize
c.present(
UndoOverlayController(
presentationData: self.presentationData,
content: .info(
title: "Improving video...",
text: "The video will be published after it's optimized for the bese viewing experience.",
timeout: 8.0,
customUndoText: nil
),
elevatedLayout: false,
position: .top,
action: { _ in
return true
}
),
in: .current
)
})
}
}
}
})
@ -4659,7 +4607,7 @@ extension ChatControllerImpl {
title: "Improving video...",
text: "The video will be published after it's optimized for the bese viewing experience.",
customUndoText: nil,
timeout: 6.0
timeout: 3.5
),
elevatedLayout: false,
position: .top,
@ -4993,6 +4941,20 @@ extension ChatControllerImpl {
}), in: .current)
})
if case .scheduledMessages = self.subject {
self.postedScheduledMessagesEventsDisposable = (self.context.account.stateManager.sentScheduledMessageIds
|> deliverOnMainQueue).start(next: { [weak self] ids in
guard let self, let peerId = self.chatLocation.peerId else {
return
}
let filteredIds = Array(ids).filter({ $0.peerId == peerId })
if filteredIds.isEmpty {
return
}
self.displayPostedScheduledMessagesToast(ids: filteredIds)
})
}
self.displayNodeDidLoad()
}
}

View File

@ -106,14 +106,9 @@ extension ChatControllerImpl {
}
}
//TODO:localize
/*if "".isEmpty, let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
for media in message.media {
if let file = media as? TelegramMediaFile, file.isVideo, !file.isInstantVideo, !file.isAnimated {
tip = .videoProcessing
}
}
}*/
if messages.contains(where: { $0.pendingProcessingAttribute != nil }) {
tip = .videoProcessing
}
if actions.tip == nil {
actions.tip = tip

View File

@ -0,0 +1,101 @@
import Foundation
import UIKit
import SwiftSignalKit
import Postbox
import TelegramCore
import AsyncDisplayKit
import Display
import UndoUI
import AccountContext
import ChatControllerInteraction
extension ChatControllerImpl {
func displayPostedScheduledMessagesToast(ids: [EngineMessage.Id]) {
let timestamp = CFAbsoluteTimeGetCurrent()
if self.lastPostedScheduledMessagesToastTimestamp + 0.4 >= timestamp {
return
}
self.lastPostedScheduledMessagesToastTimestamp = timestamp
guard case .scheduledMessages = self.presentationInterfaceState.subject else {
return
}
let _ = (self.context.engine.data.get(
EngineDataList(ids.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:)))
)
|> deliverOnMainQueue).startStandalone(next: { [weak self] messages in
guard let self else {
return
}
let messages = messages.compactMap { $0 }
var found: (message: EngineMessage, file: TelegramMediaFile)?
outer: for message in messages {
for media in message.media {
if let file = media as? TelegramMediaFile, file.isVideo {
found = (message, file)
break outer
}
}
}
guard let (message, file) = found else {
return
}
guard case let .loaded(isEmpty, _) = self.chatDisplayNode.historyNode.currentHistoryState else {
return
}
if isEmpty {
if let navigationController = self.navigationController as? NavigationController, let topController = navigationController.viewControllers.first(where: { c in
if let c = c as? ChatController, c.chatLocation == self.chatLocation {
return true
}
return false
}) as? ChatControllerImpl {
topController.controllerInteraction?.presentControllerInCurrent(UndoOverlayController(
presentationData: self.presentationData,
content: .media(
context: self.context,
file: .message(message: MessageReference(message._asMessage()), media: file),
title: nil,
text: "Video Published",
undoText: nil,
customAction: nil
),
elevatedLayout: false,
position: .top,
animateInAsReplacement: false,
action: { _ in false }
), nil)
self.dismiss()
}
} else {
//TODO:localize
self.controllerInteraction?.presentControllerInCurrent(UndoOverlayController(
presentationData: self.presentationData,
content: .media(
context: self.context,
file: .message(message: MessageReference(message._asMessage()), media: file),
title: nil,
text: "Video Published",
undoText: "View",
customAction: { [weak self] in
guard let self else {
return
}
self.dismiss()
}
),
elevatedLayout: false,
position: .top,
animateInAsReplacement: false,
action: { _ in false }
), nil)
}
})
}
}

View File

@ -654,6 +654,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var layoutActionOnViewTransitionAction: (() -> Void)?
var lastPostedScheduledMessagesToastTimestamp: Double = 0.0
var postedScheduledMessagesEventsDisposable: Disposable?
public init(
context: AccountContext,
chatLocation: ChatLocation,
@ -7194,6 +7197,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.recorderDataDisposable.dispose()
self.displaySendWhenOnlineTipDisposable.dispose()
self.networkSpeedEventsDisposable?.dispose()
self.postedScheduledMessagesEventsDisposable?.dispose()
}
deallocate()
}
@ -9136,52 +9140,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
func shouldDivertMessagesToScheduled(targetPeer: EnginePeer? = nil, messages: [EnqueueMessage]) -> Signal<Bool, NoError> {
return .single(false)
/*guard let peer = targetPeer?._asPeer() ?? self.presentationInterfaceState.renderedPeer?.peer else {
return .single(false)
}
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
} else {
return .single(false)
}
//TODO:release
if !"".isEmpty {
return .single(false)
}
var forwardMessageIds: [EngineMessage.Id] = []
for message in messages {
if case let .message(_, _, _, mediaReference, _, _, _, _, _, _) = message, let media = mediaReference?.media {
if let file = media as? TelegramMediaFile, file.isVideo && !file.isInstantVideo && !file.isAnimated {
return .single(true)
}
} else if case let .forward(sourceId, _, _, _, _) = message {
forwardMessageIds.append(sourceId)
}
}
if forwardMessageIds.isEmpty {
return .single(false)
} else {
return self.context.engine.data.get(
EngineDataList(forwardMessageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:)))
)
|> map { messages -> Bool in
for message in messages {
guard let message else {
continue
}
for media in message.media {
if let file = media as? TelegramMediaFile, file.isVideo && !file.isInstantVideo && !file.isAnimated {
return true
}
}
}
return false
}
}*/
}
func sendMessages(_ messages: [EnqueueMessage], media: Bool = false, commit: Bool = false) {
@ -9240,30 +9198,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
layoutActionOnViewTransitionAction()
}
self.openScheduledMessages(force: true, completion: { [weak self] c in
guard let self else {
return
}
c.dismissAllUndoControllers()
//TODO:localize
c.present(
UndoOverlayController(
presentationData: self.presentationData,
content: .info(
title: "Improving video...",
text: "The video will be published after it's optimized for the bese viewing experience.",
timeout: 8.0,
customUndoText: nil
),
elevatedLayout: false,
position: .top,
action: { _ in
return true
}
),
in: .current
)
self.openScheduledMessages(force: true, completion: { _ in
})
}
} else {
@ -10381,10 +10316,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let itemNode = latestNode, let statusNode = itemNode.getStatusNode() {
let bounds = statusNode.view.convert(statusNode.view.bounds, to: self.chatDisplayNode.view)
let location = CGPoint(x: bounds.midX, y: bounds.minY - 11.0)
let location = CGPoint(x: bounds.midX, y: bounds.minY - 8.0)
//TODO:localize
let tooltipController = TooltipController(content: .text("Processing video may take a few minutes."), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: true, timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
let tooltipController = TooltipController(content: .text("Processing video may take a few minutes."), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: true, isBlurred: true, timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
self.checksTooltipController = tooltipController
tooltipController.dismissed = { [weak self, weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.checksTooltipController === tooltipController {

View File

@ -257,24 +257,6 @@ extension ChatControllerImpl {
})
if displayConvertingTooltip {
//TODO:localize
strongSelf.present(
UndoOverlayController(
presentationData: strongSelf.presentationData,
content: .info(
title: "Improving video...",
text: "The video will be published after it's optimized for the bese viewing experience.",
timeout: 8.0,
customUndoText: nil
),
elevatedLayout: false,
position: .top,
action: { _ in
return true
}
),
in: .current
)
}
})
}

View File

@ -3467,6 +3467,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
}
for id in self.context.engine.messages.synchronouslyIsMessageDeletedInteractively(ids: testIds) {
if id.namespace == Namespaces.Message.ScheduledCloud {
continue
}
inner: for (stableId, listId) in maybeRemovedInteractivelyMessageIds {
if listId == id {
expiredMessageStableIds.insert(stableId)

View File

@ -1137,9 +1137,29 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
if data.messageActions.options.contains(.sendScheduledNow) {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_SendNow, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
f(.dismissWithoutContent)
}, action: { c, _ in
if messages.contains(where: { $0.pendingProcessingAttribute != nil }) {
c?.dismiss(completion: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
controllerInteraction.presentController(standardTextAlertController(
theme: AlertControllerTheme(presentationData: presentationData),
title: "Wait!",
text: "This video hasn't been converted and optimized yet. If you send it now, the viewers of the video may experience slow download speed.",
actions: [
TextAlertAction(type: .defaultAction, title: "Send Anyway", action: {
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
}),
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
],
actionLayout: .vertical
), nil)
})
} else {
c?.dismiss(result: .dismissWithoutContent, completion: nil)
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
}
})))
}
@ -2210,8 +2230,10 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
}
if id.namespace == Namespaces.Message.ScheduledCloud {
optionsMap[id]!.insert(.sendScheduledNow)
if canEditMessage(accountPeerId: accountPeerId, limitsConfiguration: limitsConfiguration, message: message, reschedule: true) {
optionsMap[id]!.insert(.editScheduledTime)
if message.pendingProcessingAttribute == nil {
if canEditMessage(accountPeerId: accountPeerId, limitsConfiguration: limitsConfiguration, message: message, reschedule: true) {
optionsMap[id]!.insert(.editScheduledTime)
}
}
if let peer = getPeer(id.peerId), let channel = peer as? TelegramChannel {
if !message.flags.contains(.Incoming) {

View File

@ -1572,6 +1572,15 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod
}
func videoQualityState() -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? {
if self.playerAvailableLevels.isEmpty {
if let qualitySet = HLSQualitySet(baseFile: self.fileReference), let minQualityFile = HLSVideoContent.minimizedHLSQuality(file: self.fileReference)?.file {
let sortedFiles = qualitySet.qualityFiles.sorted(by: { $0.key > $1.key })
if let minQuality = sortedFiles.first(where: { $0.value.media.fileId == minQualityFile.media.fileId }) {
return (minQuality.key, .auto, sortedFiles.map(\.key))
}
}
}
guard let playerCurrentLevelIndex = self.playerCurrentLevelIndex else {
return nil
}

View File

@ -33,6 +33,7 @@ swift_library(
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/PhotoResources",
],
visibility = [
"//visibility:public",

View File

@ -49,6 +49,7 @@ public enum UndoOverlayContent {
case premiumPaywall(title: String?, text: String, customUndoText: String?, timeout: Double?, linkAction: ((String) -> Void)?)
case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?)
case messageTagged(context: AccountContext, isSingleMessage: Bool, customEmoji: TelegramMediaFile, isBuiltinReaction: Bool, customUndoText: String?)
case media(context: AccountContext, file: FileMediaReference, title: String?, text: String, undoText: String?, customAction: (() -> Void)?)
}
public enum UndoOverlayAction {

View File

@ -23,6 +23,7 @@ import TextNodeWithEntities
import BundleIconComponent
import AnimatedTextComponent
import ComponentDisplayAdapters
import PhotoResources
final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let presentationData: PresentationData
@ -42,6 +43,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private var slotMachineNode: SlotMachineAnimationNode?
private var stillStickerNode: TransformImageNode?
private var stickerImageSize: CGSize?
private var stickerSourceSize: CGSize?
private var stickerOffset: CGPoint?
private var emojiStatus: ComponentView<Empty>?
private let titleNode: ImmediateTextNode
@ -1297,6 +1299,58 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} else {
displayUndo = false
}
case let .media(context, file, title, text, customUndoText, _):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
self.animationNode = nil
let stillStickerNode = TransformImageNode()
self.stillStickerNode = stillStickerNode
var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
var updatedFetchSignal: Signal<Never, EngineMediaResource.Fetch.Error>?
updatedImageSignal = mediaGridMessageVideo(postbox: context.account.postbox, userLocation: .other, videoReference: file, onlyFullSize: false, useLargeThumbnail: false, autoFetchFullSizeThumbnail: false)
updatedFetchSignal = nil
self.stickerImageSize = CGSize(width: 30.0, height: 30.0)
self.stickerSourceSize = file.media.dimensions?.cgSize
if let title = title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
} else {
self.titleNode.attributedText = nil
}
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
return ("URL", contents)
}), textAlignment: .natural)
self.textNode.attributedText = attributedText
self.textNode.maximumNumberOfLines = 5
if text.contains("](") {
isUserInteractionEnabled = true
}
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
self.originalRemainingSeconds = isUserInteractionEnabled ? 5 : 3
if let updatedFetchSignal = updatedFetchSignal {
self.fetchResourceDisposable = updatedFetchSignal.start()
}
if let updatedImageSignal = updatedImageSignal {
stillStickerNode.setSignal(updatedImageSignal)
}
}
self.remainingSeconds = self.originalRemainingSeconds
@ -1340,7 +1394,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} else {
self.isUserInteractionEnabled = false
}
case .sticker, .customEmoji:
case .sticker, .customEmoji, .media:
self.isUserInteractionEnabled = displayUndo
case .dice:
self.panelWrapperNode.clipsToBounds = true
@ -1468,6 +1522,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} else {
let _ = self.action(.undo)
}
case let .media(_, _, _, _, _, customAction):
if let customAction = customAction {
customAction()
} else {
let _ = self.action(.undo)
}
default:
let _ = self.action(.undo)
}
@ -1775,7 +1835,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
if let stillStickerNode = self.stillStickerNode {
let makeImageLayout = stillStickerNode.asyncLayout()
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: stickerImageSize, boundingSize: stickerImageSize, intrinsicInsets: UIEdgeInsets()))
var radius: CGFloat = 0.0
if case .media = self.content {
radius = 6.0
}
var stickerImageSourceSize = stickerImageSize
if let stickerSourceSize = self.stickerSourceSize {
stickerImageSourceSize = stickerSourceSize.aspectFilled(stickerImageSourceSize)
}
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: stickerImageSourceSize, boundingSize: stickerImageSize, intrinsicInsets: UIEdgeInsets()))
let _ = imageApply()
transition.updateFrame(node: stillStickerNode, frame: iconFrame)
}