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 final class FilledRoundedRectangleComponent: Component {
public enum CornerRadius: Equatable {
case value(CGFloat)
case minEdge
}
public let color: UIColor public let color: UIColor
public let cornerRadius: CGFloat public let cornerRadius: CornerRadius
public let smoothCorners: Bool public let smoothCorners: Bool
public init( public init(
color: UIColor, color: UIColor,
cornerRadius: CGFloat, cornerRadius: CornerRadius,
smoothCorners: Bool smoothCorners: Bool
) { ) {
self.color = color self.color = color
@ -216,9 +221,17 @@ public final class FilledRoundedRectangleComponent: Component {
transition.setTintColor(view: self, color: component.color) 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 let previousCornerRadius = self.currentCornerRadius
self.currentCornerRadius = component.cornerRadius self.currentCornerRadius = cornerRadius
if transition.animation.isImmediate { if transition.animation.isImmediate {
self.applyStaticCornerRadius() self.applyStaticCornerRadius()
} else { } 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 { guard let self, completed else {
return return
} }

View File

@ -57,6 +57,9 @@ swift_library(
"//submodules/TelegramUI/Components/SaveProgressScreen", "//submodules/TelegramUI/Components/SaveProgressScreen",
"//submodules/TelegramUI/Components/RasterizedCompositionComponent", "//submodules/TelegramUI/Components/RasterizedCompositionComponent",
"//submodules/TelegramUI/Components/BadgeComponent", "//submodules/TelegramUI/Components/BadgeComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/ComponentFlow", "//submodules/ComponentFlow",
], ],
visibility = [ 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.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) { open func setControlsHidden(_ hidden: Bool, animated: Bool) {

View File

@ -11,6 +11,11 @@ public enum GalleryItemNodeNavigationStyle {
} }
open class GalleryItemNode: ASDisplayNode { open class GalleryItemNode: ASDisplayNode {
public enum ActiveEdge {
case left
case right
}
private var _index: Int? private var _index: Int?
public var index: Int { public var index: Int {
get { get {
@ -111,4 +116,14 @@ open class GalleryItemNode: ASDisplayNode {
open var keyShortcuts: [KeyShortcut] { open var keyShortcuts: [KeyShortcut] {
return [] 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)) return min(44.0, floor(width / 6.0))
} }
private func activeEdgeWidth(width: CGFloat) -> CGFloat {
return floor(width * 0.4)
}
let fadeWidth: CGFloat = 70.0 let fadeWidth: CGFloat = 70.0
private let leftFadeImage = generateImage(CGSize(width: fadeWidth, height: 32.0), opaque: false, rotatedContext: { size, context in private let leftFadeImage = generateImage(CGSize(width: fadeWidth, height: 32.0), opaque: false, rotatedContext: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)
@ -85,6 +89,9 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
private let leftFadeNode: ASDisplayNode private let leftFadeNode: ASDisplayNode
private let rightFadeNode: ASDisplayNode private let rightFadeNode: ASDisplayNode
private var highlightedSide: Bool? private var highlightedSide: Bool?
private var activeSide: Bool?
private var canPerformSideNavigationAction: Bool = false
private var sideActionInitialPosition: CGPoint?
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
@ -118,6 +125,8 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
public var pagingEnabledPromise = Promise<Bool>(true) public var pagingEnabledPromise = Promise<Bool>(true)
private var pagingEnabledDisposable: Disposable? private var pagingEnabledDisposable: Disposable?
private var edgeLongTapTimer: Foundation.Timer?
public init(pageGap: CGFloat, disableTapNavigation: Bool) { public init(pageGap: CGFloat, disableTapNavigation: Bool) {
self.pageGap = pageGap self.pageGap = pageGap
self.disableTapNavigation = disableTapNavigation self.disableTapNavigation = disableTapNavigation
@ -170,57 +179,99 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
recognizer.delegate = self.wrappedGestureRecognizerDelegate recognizer.delegate = self.wrappedGestureRecognizerDelegate
self.tapRecognizer = recognizer self.tapRecognizer = recognizer
recognizer.tapActionAtPoint = { [weak self] point in recognizer.tapActionAtPoint = { [weak self] point in
guard let strongSelf = self, strongSelf.pagingEnabled else { guard let strongSelf = self else {
return .fail return .fail
} }
let size = strongSelf.bounds let size = strongSelf.bounds
var highlightedSide: Bool? var highlightedSide: Bool?
if point.x < edgeWidth(width: size.width) && strongSelf.canGoToPreviousItem() { var activeSide: Bool?
if point.x < edgeWidth(width: size.width) {
if strongSelf.canGoToPreviousItem() {
if strongSelf.items.count > 1 { if strongSelf.items.count > 1 {
highlightedSide = false highlightedSide = false
} }
} else if point.x > size.width - edgeWidth(width: size.width) && strongSelf.canGoToNextItem() { }
} else if point.x > size.width - edgeWidth(width: size.width) {
if strongSelf.canGoToNextItem() {
if strongSelf.items.count > 1 { if strongSelf.items.count > 1 {
if point.y < 80.0 {
highlightedSide = nil
} else {
highlightedSide = true 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 return .fail
} }
if let result = strongSelf.hitTest(point, with: nil), let _ = result.asyncdisplaykit_node as? ASButtonNode { if let result = strongSelf.hitTest(point, with: nil), let _ = result.asyncdisplaykit_node as? ASButtonNode {
return .fail return .fail
} }
if activeSide != nil {
return .waitForHold(timeout: 0.3, acceptTap: true)
} else {
return .keepWithSingleTap return .keepWithSingleTap
} }
}
recognizer.highlight = { [weak self] point in recognizer.highlight = { [weak self] point in
guard let strongSelf = self, strongSelf.pagingEnabled else { guard let strongSelf = self else {
return return
} }
let size = strongSelf.bounds let size = strongSelf.bounds
var highlightedSide: Bool? var highlightedSide: Bool?
if let point = point { var activeSide: Bool?
if point.x < edgeWidth(width: size.width) && strongSelf.canGoToPreviousItem() { if let point {
if point.x < edgeWidth(width: size.width) {
if strongSelf.canGoToPreviousItem() {
if strongSelf.items.count > 1 { if strongSelf.items.count > 1 {
highlightedSide = false highlightedSide = false
} }
} else if point.x > size.width - edgeWidth(width: size.width) && strongSelf.canGoToNextItem() { }
} else if point.x > size.width - edgeWidth(width: size.width) {
if strongSelf.canGoToNextItem() {
if strongSelf.items.count > 1 { if strongSelf.items.count > 1 {
highlightedSide = true 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 { if strongSelf.highlightedSide != highlightedSide {
strongSelf.highlightedSide = highlightedSide strongSelf.highlightedSide = highlightedSide
if highlightedSide != nil {
strongSelf.canPerformSideNavigationAction = true
}
let leftAlpha: CGFloat let leftAlpha: CGFloat
let rightAlpha: CGFloat let rightAlpha: CGFloat
if let highlightedSide = highlightedSide { 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) self.view.addGestureRecognizer(recognizer)
} }
@ -258,8 +350,9 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { @objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
switch recognizer.state { switch recognizer.state {
case .ended: case .ended:
self.sideActionInitialPosition = nil
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
if case .tap = gesture { if case .tap = gesture, self.canPerformSideNavigationAction {
let size = self.bounds.size let size = self.bounds.size
if location.x < edgeWidth(width: size.width) && self.canGoToPreviousItem() { if location.x < edgeWidth(width: size.width) && self.canGoToPreviousItem() {
self.goToPreviousItem() 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: default:
break 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 RasterizedCompositionComponent
import BadgeComponent import BadgeComponent
import ComponentFlow import ComponentFlow
import ComponentDisplayAdapters
public enum UniversalVideoGalleryItemContentInfo { public enum UniversalVideoGalleryItemContentInfo {
case message(Message, Int?) case message(Message, Int?)
@ -1318,7 +1319,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private var playOnContentOwnership = false private var playOnContentOwnership = false
private var skipInitialPause = false private var skipInitialPause = false
private var ignorePauseStatus = false private var ignorePauseStatus = false
private var validLayout: (ContainerViewLayout, CGFloat)? private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat)?
private var didPause = false private var didPause = false
private var isPaused = true private var isPaused = true
private var dismissOnOrientationChange = false private var dismissOnOrientationChange = false
@ -1368,6 +1369,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private var activePictureInPictureNavigationController: NavigationController? private var activePictureInPictureNavigationController: NavigationController?
private var activePictureInPictureController: ViewController? 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) { init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
@ -1604,6 +1608,46 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
} }
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 { if dismiss {
self.dismiss() self.dismiss()
} }
@ -3938,6 +3982,59 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
} }
return keyShortcuts 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 { 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[1401868056] = { return Api.StarsSubscription.parse_starsSubscription($0) }
dict[88173912] = { return Api.StarsSubscriptionPricing.parse_starsSubscriptionPricing($0) } dict[88173912] = { return Api.StarsSubscriptionPricing.parse_starsSubscriptionPricing($0) }
dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($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[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) }
dict[-110658899] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAPI($0) } dict[-110658899] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAPI($0) }
dict[1617438738] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAds($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[-1576161051] = { return Api.Update.parse_updateDeleteMessages($0) }
dict[1407644140] = { return Api.Update.parse_updateDeleteQuickReply($0) } dict[1407644140] = { return Api.Update.parse_updateDeleteQuickReply($0) }
dict[1450174413] = { return Api.Update.parse_updateDeleteQuickReplyMessages($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[654302845] = { return Api.Update.parse_updateDialogFilter($0) }
dict[-1512627963] = { return Api.Update.parse_updateDialogFilterOrder($0) } dict[-1512627963] = { return Api.Update.parse_updateDialogFilterOrder($0) }
dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) } dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) }

View File

@ -1010,13 +1010,13 @@ public extension Api {
} }
public extension Api { public extension Api {
enum StarsTransaction: TypeConstructorDescription { 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) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { 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 { if boxed {
buffer.appendInt32(-1216644148) buffer.appendInt32(903148150)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(id, 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 << 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 << 13) != 0 {serializeInt32(giveawayPostId!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 14) != 0 {stargift!.serialize(buffer, true)} 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)} if Int(flags) & Int(1 << 15) != 0 {serializeInt32(floodskipNumber!, buffer: buffer, boxed: false)}
break break
} }
@ -1046,8 +1045,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { 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):
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)]) 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? var _17: Int32?
if Int(_1!) & Int(1 << 15) != 0 {_17 = reader.readInt32() } 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 _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -1113,9 +1110,8 @@ public extension Api {
let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil
let _c16 = (Int(_1!) & Int(1 << 14) == 0) || _16 != nil let _c16 = (Int(_1!) & Int(1 << 14) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != 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 {
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, floodskipNumber: _17)
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)
} }
else { else {
return nil return nil

View File

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

View File

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

View File

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

View File

@ -212,6 +212,7 @@ struct AccountMutableState {
var namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]] var namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]]
var updatedOutgoingUniqueMessageIds: [Int64: Int32] var updatedOutgoingUniqueMessageIds: [Int64: Int32]
var storedStories: [StoryId: UpdatesStoredStory] var storedStories: [StoryId: UpdatesStoredStory]
var sentScheduledMessageIds: Set<MessageId>
var resetForumTopicLists: [PeerId: StateResetForumTopics] = [:] var resetForumTopicLists: [PeerId: StateResetForumTopics] = [:]
@ -231,7 +232,7 @@ struct AccountMutableState {
var authorizationListUpdated: Bool = false 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.initialState = initialState
self.state = initialState.state self.state = initialState.state
self.peers = initialPeers self.peers = initialPeers
@ -240,6 +241,7 @@ struct AccountMutableState {
self.referencedGeneralMessageIds = initialReferencedGeneralMessageIds self.referencedGeneralMessageIds = initialReferencedGeneralMessageIds
self.storedMessages = initialStoredMessages self.storedMessages = initialStoredMessages
self.storedStories = initialStoredStories self.storedStories = initialStoredStories
self.sentScheduledMessageIds = initialSentScheduledMessageIds
self.readInboxMaxIds = initialReadInboxMaxIds self.readInboxMaxIds = initialReadInboxMaxIds
self.channelStates = initialState.channelStates self.channelStates = initialState.channelStates
self.peerChatInfos = initialState.peerChatInfos self.peerChatInfos = initialState.peerChatInfos
@ -249,7 +251,7 @@ struct AccountMutableState {
self.updatedOutgoingUniqueMessageIds = [:] 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.initialState = initialState
self.operations = operations self.operations = operations
self.state = state self.state = state
@ -260,6 +262,7 @@ struct AccountMutableState {
self.referencedGeneralMessageIds = referencedGeneralMessageIds self.referencedGeneralMessageIds = referencedGeneralMessageIds
self.storedMessages = storedMessages self.storedMessages = storedMessages
self.storedStories = storedStories self.storedStories = storedStories
self.sentScheduledMessageIds = sentScheduledMessageIds
self.peerChatInfos = peerChatInfos self.peerChatInfos = peerChatInfos
self.readInboxMaxIds = readInboxMaxIds self.readInboxMaxIds = readInboxMaxIds
self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp
@ -271,7 +274,7 @@ struct AccountMutableState {
} }
func branch() -> 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) { mutating func merge(_ other: AccountMutableState) {
@ -282,6 +285,8 @@ struct AccountMutableState {
self.storedStories[id] = story self.storedStories[id] = story
} }
self.sentScheduledMessageIds.formUnion(other.sentScheduledMessageIds)
for i in other.branchOperationIndex ..< other.operations.count { for i in other.branchOperationIndex ..< other.operations.count {
self.addOperation(other.operations[i]) self.addOperation(other.operations[i])
} }
@ -357,6 +362,10 @@ struct AccountMutableState {
self.addOperation(.DeleteMessages(messageIds)) self.addOperation(.DeleteMessages(messageIds))
} }
mutating func addSentScheduledMessageIds(_ messageIds: [MessageId]) {
self.sentScheduledMessageIds.formUnion(messageIds)
}
mutating func editMessage(_ id: MessageId, message: StoreMessage) { mutating func editMessage(_ id: MessageId, message: StoreMessage) {
self.addOperation(.EditMessage(id, message)) self.addOperation(.EditMessage(id, message))
} }
@ -842,6 +851,7 @@ struct AccountReplayedFinalState {
let updatedRevenueBalances: [PeerId: RevenueStats.Balances] let updatedRevenueBalances: [PeerId: RevenueStats.Balances]
let updatedStarsBalance: [PeerId: Int64] let updatedStarsBalance: [PeerId: Int64]
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
let sentScheduledMessageIds: Set<MessageId>
} }
struct AccountFinalStateEvents { struct AccountFinalStateEvents {
@ -849,6 +859,7 @@ struct AccountFinalStateEvents {
let addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] let addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)]
let wasScheduledMessageIds: [MessageId] let wasScheduledMessageIds: [MessageId]
let deletedMessageIds: [DeletedMessageId] let deletedMessageIds: [DeletedMessageId]
let sentScheduledMessageIds: Set<MessageId>
let updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] let updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]]
let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedWebpages: [MediaId: TelegramMediaWebpage]
let updatedCalls: [Api.PhoneCall] let updatedCalls: [Api.PhoneCall]
@ -873,10 +884,10 @@ struct AccountFinalStateEvents {
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
var isEmpty: Bool { 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.addedIncomingMessageIds = addedIncomingMessageIds
self.addedReactionEvents = addedReactionEvents self.addedReactionEvents = addedReactionEvents
self.wasScheduledMessageIds = wasScheduledMessageIds self.wasScheduledMessageIds = wasScheduledMessageIds
@ -903,6 +914,7 @@ struct AccountFinalStateEvents {
self.updatedRevenueBalances = updatedRevenueBalances self.updatedRevenueBalances = updatedRevenueBalances
self.updatedStarsBalance = updatedStarsBalance self.updatedStarsBalance = updatedStarsBalance
self.updatedStarsRevenueStatus = updatedStarsRevenueStatus self.updatedStarsRevenueStatus = updatedStarsRevenueStatus
self.sentScheduledMessageIds = sentScheduledMessageIds
} }
init(state: AccountReplayedFinalState) { init(state: AccountReplayedFinalState) {
@ -932,6 +944,7 @@ struct AccountFinalStateEvents {
self.updatedRevenueBalances = state.updatedRevenueBalances self.updatedRevenueBalances = state.updatedRevenueBalances
self.updatedStarsBalance = state.updatedStarsBalance self.updatedStarsBalance = state.updatedStarsBalance
self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus
self.sentScheduledMessageIds = state.sentScheduledMessageIds
} }
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents { func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
@ -961,6 +974,9 @@ struct AccountFinalStateEvents {
let isPremiumUpdated = self.isPremiumUpdated || other.isPremiumUpdated 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 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) { if let message = StoreMessage(apiMessage: apiMessage, accountPeerId: accountPeerId, peerIsForum: peerIsForum, namespace: Namespaces.Message.QuickReplyCloud) {
updatedState.addQuickReplyMessages([message]) updatedState.addQuickReplyMessages([message])
} }
case let .updateDeleteScheduledMessages(peer, messages): case let .updateDeleteScheduledMessages(_, peer, messages, sentMessages):
var messageIds: [MessageId] = [] var messageIds: [MessageId] = []
var sentMessageIds: [MessageId] = []
for message in messages { for message in messages {
messageIds.append(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.ScheduledCloud, id: message)) 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.deleteMessages(messageIds)
updatedState.addSentScheduledMessageIds(sentMessageIds)
case let .updateDeleteQuickReplyMessages(_, messages): case let .updateDeleteQuickReplyMessages(_, messages):
var messageIds: [MessageId] = [] var messageIds: [MessageId] = []
for message in messages { for message in messages {
@ -2628,7 +2635,7 @@ func pollChannelOnce(accountPeerId: PeerId, postbox: Postbox, network: Network,
peerChatInfos[peerId] = PeerChatInfo(notificationSettings: notificationSettings) 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) return pollChannel(accountPeerId: accountPeerId, postbox: postbox, network: network, peer: peer, state: initialState)
|> mapToSignal { (finalState, _, timeout) -> Signal<Int32, NoError> in |> mapToSignal { (finalState, _, timeout) -> Signal<Int32, NoError> in
return resolveAssociatedMessages(accountPeerId: accountPeerId, postbox: postbox, network: network, state: finalState) 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) 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) return pollChannel(accountPeerId: accountPeerId, postbox: postbox, network: network, peer: peer, state: initialState)
|> mapToSignal { (finalState, _, timeout) -> Signal<Never, NoError> in |> mapToSignal { (finalState, _, timeout) -> Signal<Never, NoError> in
return resolveAssociatedMessages(accountPeerId: accountPeerId, postbox: postbox, network: network, state: finalState) return resolveAssociatedMessages(accountPeerId: accountPeerId, postbox: postbox, network: network, state: finalState)
@ -5342,5 +5349,29 @@ func replayFinalState(
_internal_setStarsReactionDefaultToPrivate(isPrivate: updatedStarsReactionsAreAnonymousByDefault, transaction: transaction) _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 { final class MessagesRemovedContext {
private var messagesRemovedInteractively = Set<DeletedMessageId>() private var messagesRemovedInteractively = Set<DeletedMessageId>()
private var messagesRemovedRemotely = Set<DeletedMessageId>()
private var messagesRemovedInteractivelyLock = NSLock() private var messagesRemovedInteractivelyLock = NSLock()
func synchronouslyIsMessageDeletedInteractively(ids: [MessageId]) -> [EngineMessage.Id] { func synchronouslyIsMessageDeletedInteractively(ids: [MessageId]) -> [EngineMessage.Id] {
@ -85,6 +86,26 @@ final class MessagesRemovedContext {
return result 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]) { func addIsMessagesDeletedInteractively(ids: [DeletedMessageId]) {
if ids.isEmpty { if ids.isEmpty {
return return
@ -94,6 +115,16 @@ final class MessagesRemovedContext {
self.messagesRemovedInteractively.formUnion(ids) self.messagesRemovedInteractively.formUnion(ids)
self.messagesRemovedInteractivelyLock.unlock() 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 { public final class AccountStateManager {
@ -297,6 +328,11 @@ public final class AccountStateManager {
return self.forceSendPendingStarsReactionPipe.signal() 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 updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:]
private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext() private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext()
private var updatedRevenueBalancesContext = UpdatedRevenueBalancesSubscriberContext() private var updatedRevenueBalancesContext = UpdatedRevenueBalancesSubscriberContext()
@ -690,6 +726,7 @@ public final class AccountStateManager {
if let result = result, !result.deletedMessageIds.isEmpty { if let result = result, !result.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds) messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
} }
return result return result
@ -835,6 +872,7 @@ public final class AccountStateManager {
if let replayedState = replayedState { if let replayedState = replayedState {
if !replayedState.deletedMessageIds.isEmpty { if !replayedState.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: replayedState.deletedMessageIds) messagesRemovedContext.addIsMessagesDeletedInteractively(ids: replayedState.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: replayedState.deletedMessageIds)
} }
return (difference, replayedState, false, false) return (difference, replayedState, false, false)
@ -973,6 +1011,7 @@ public final class AccountStateManager {
if let result = result, !result.deletedMessageIds.isEmpty { if let result = result, !result.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds) messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
} }
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
@ -1080,6 +1119,9 @@ public final class AccountStateManager {
if !events.updatedIncomingThreadReadStates.isEmpty || !events.updatedOutgoingThreadReadStates.isEmpty { if !events.updatedIncomingThreadReadStates.isEmpty || !events.updatedOutgoingThreadReadStates.isEmpty {
strongSelf.threadReadStateUpdatesPipe.putNext((events.updatedIncomingThreadReadStates, events.updatedOutgoingThreadReadStates)) strongSelf.threadReadStateUpdatesPipe.putNext((events.updatedIncomingThreadReadStates, events.updatedOutgoingThreadReadStates))
} }
if !events.sentScheduledMessageIds.isEmpty {
strongSelf.sentScheduledMessageIdsPipe.putNext(events.sentScheduledMessageIds)
}
if !events.isContactUpdates.isEmpty { if !events.isContactUpdates.isEmpty {
strongSelf.addIsContactUpdates(events.isContactUpdates) strongSelf.addIsContactUpdates(events.isContactUpdates)
} }
@ -1248,6 +1290,7 @@ public final class AccountStateManager {
if let result = result, !result.deletedMessageIds.isEmpty { if let result = result, !result.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds) messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
} }
return result return result
@ -1296,6 +1339,7 @@ public final class AccountStateManager {
if let result = result, !result.deletedMessageIds.isEmpty { if let result = result, !result.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds) messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
} }
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
@ -1388,6 +1432,7 @@ public final class AccountStateManager {
if let replayedState = replayedState, !replayedState.deletedMessageIds.isEmpty { if let replayedState = replayedState, !replayedState.deletedMessageIds.isEmpty {
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: replayedState.deletedMessageIds) messagesRemovedContext.addIsMessagesDeletedInteractively(ids: replayedState.deletedMessageIds)
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: replayedState.deletedMessageIds)
} }
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime 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) { func forceSendPendingStarsReaction(messageId: MessageId) {
self.impl.with { impl in self.impl.with { impl in
impl.forceSendPendingStarsReactionPipe.putNext(messageId) impl.forceSendPendingStarsReactionPipe.putNext(messageId)
@ -2128,6 +2179,10 @@ public final class AccountStateManager {
public func synchronouslyIsMessageDeletedInteractively(ids: [EngineMessage.Id]) -> [EngineMessage.Id] { public func synchronouslyIsMessageDeletedInteractively(ids: [EngineMessage.Id]) -> [EngineMessage.Id] {
return self.messagesRemovedContext.synchronouslyIsMessageDeletedInteractively(ids: ids) 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) { func resolveNotificationSettings(list: [TelegramPeerNotificationSettings], defaultSettings: MessageNotificationSettings) -> (sound: PeerMessageSound, notify: Bool, displayContents: Bool) {

View File

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

View File

@ -1401,6 +1401,10 @@ public extension TelegramEngine {
return self.account.stateManager.synchronouslyIsMessageDeletedInteractively(ids: ids) 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? { public func synchronouslyLookupCorrelationId(correlationId: Int64) -> EngineMessage.Id? {
return self.account.pendingMessageManager.synchronouslyLookupCorrelationId(correlationId: correlationId) return self.account.pendingMessageManager.synchronouslyLookupCorrelationId(correlationId: correlationId)
} }

View File

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

View File

@ -6118,14 +6118,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
} }
override public func getStatusNode() -> ASDisplayNode? { override public func getStatusNode() -> ASDisplayNode? {
if let statusNode = self.mosaicStatusNode {
return statusNode
}
for contentNode in self.contentNodes { for contentNode in self.contentNodes {
if let statusNode = contentNode.getStatusNode() { if let statusNode = contentNode.getStatusNode() {
return statusNode return statusNode
} }
} }
if let statusNode = self.mosaicStatusNode {
return statusNode
}
return nil 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) let _ = (strongSelf.shouldDivertMessagesToScheduled(messages: transformedMessages)
|> deliverOnMainQueue).start(next: { shouldDivert in |> deliverOnMainQueue).start(next: { shouldDivert in
let signal: Signal<[MessageId?], NoError> let signal: Signal<[MessageId?], NoError>
var stayInThisChat = false
var shouldOpenScheduledMessages = false var shouldOpenScheduledMessages = false
if forwardSourcePeerIds.count > 1 { if forwardSourcePeerIds.count > 1 {
var forwardedMessages = forwardedMessages var forwardedMessages = forwardedMessages
@ -1302,7 +1301,6 @@ extension ChatControllerImpl {
} }
return ids return ids
} }
stayInThisChat = true
} else { } else {
var transformedMessages = transformedMessages var transformedMessages = transformedMessages
if shouldDivert { if shouldDivert {
@ -1334,56 +1332,6 @@ extension ChatControllerImpl {
strongSelf.layoutActionOnViewTransitionAction = nil strongSelf.layoutActionOnViewTransitionAction = nil
layoutActionOnViewTransitionAction() 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...", title: "Improving video...",
text: "The video will be published after it's optimized for the bese viewing experience.", text: "The video will be published after it's optimized for the bese viewing experience.",
customUndoText: nil, customUndoText: nil,
timeout: 6.0 timeout: 3.5
), ),
elevatedLayout: false, elevatedLayout: false,
position: .top, position: .top,
@ -4993,6 +4941,20 @@ extension ChatControllerImpl {
}), in: .current) }), 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() self.displayNodeDidLoad()
} }
} }

View File

@ -106,14 +106,9 @@ extension ChatControllerImpl {
} }
} }
//TODO:localize if messages.contains(where: { $0.pendingProcessingAttribute != nil }) {
/*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 tip = .videoProcessing
} }
}
}*/
if actions.tip == nil { if actions.tip == nil {
actions.tip = tip 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 layoutActionOnViewTransitionAction: (() -> Void)?
var lastPostedScheduledMessagesToastTimestamp: Double = 0.0
var postedScheduledMessagesEventsDisposable: Disposable?
public init( public init(
context: AccountContext, context: AccountContext,
chatLocation: ChatLocation, chatLocation: ChatLocation,
@ -7194,6 +7197,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.recorderDataDisposable.dispose() self.recorderDataDisposable.dispose()
self.displaySendWhenOnlineTipDisposable.dispose() self.displaySendWhenOnlineTipDisposable.dispose()
self.networkSpeedEventsDisposable?.dispose() self.networkSpeedEventsDisposable?.dispose()
self.postedScheduledMessagesEventsDisposable?.dispose()
} }
deallocate() deallocate()
} }
@ -9136,52 +9140,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
func shouldDivertMessagesToScheduled(targetPeer: EnginePeer? = nil, messages: [EnqueueMessage]) -> Signal<Bool, NoError> { func shouldDivertMessagesToScheduled(targetPeer: EnginePeer? = nil, messages: [EnqueueMessage]) -> Signal<Bool, NoError> {
return .single(false) 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) { func sendMessages(_ messages: [EnqueueMessage], media: Bool = false, commit: Bool = false) {
@ -9240,30 +9198,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
layoutActionOnViewTransitionAction() layoutActionOnViewTransitionAction()
} }
self.openScheduledMessages(force: true, completion: { [weak self] c in self.openScheduledMessages(force: true, completion: { _ 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
)
}) })
} }
} else { } else {
@ -10381,10 +10316,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let itemNode = latestNode, let statusNode = itemNode.getStatusNode() { if let itemNode = latestNode, let statusNode = itemNode.getStatusNode() {
let bounds = statusNode.view.convert(statusNode.view.bounds, to: self.chatDisplayNode.view) 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 //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 self.checksTooltipController = tooltipController
tooltipController.dismissed = { [weak self, weak tooltipController] _ in tooltipController.dismissed = { [weak self, weak tooltipController] _ in
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.checksTooltipController === tooltipController { if let strongSelf = self, let tooltipController = tooltipController, strongSelf.checksTooltipController === tooltipController {

View File

@ -257,24 +257,6 @@ extension ChatControllerImpl {
}) })
if displayConvertingTooltip { 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) { for id in self.context.engine.messages.synchronouslyIsMessageDeletedInteractively(ids: testIds) {
if id.namespace == Namespaces.Message.ScheduledCloud {
continue
}
inner: for (stableId, listId) in maybeRemovedInteractivelyMessageIds { inner: for (stableId, listId) in maybeRemovedInteractivelyMessageIds {
if listId == id { if listId == id {
expiredMessageStableIds.insert(stableId) expiredMessageStableIds.insert(stableId)

View File

@ -1137,9 +1137,29 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
if data.messageActions.options.contains(.sendScheduledNow) { if data.messageActions.options.contains(.sendScheduledNow) {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_SendNow, icon: { theme in 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) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, 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]) controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
f(.dismissWithoutContent) }),
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,9 +2230,11 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
} }
if id.namespace == Namespaces.Message.ScheduledCloud { if id.namespace == Namespaces.Message.ScheduledCloud {
optionsMap[id]!.insert(.sendScheduledNow) optionsMap[id]!.insert(.sendScheduledNow)
if message.pendingProcessingAttribute == nil {
if canEditMessage(accountPeerId: accountPeerId, limitsConfiguration: limitsConfiguration, message: message, reschedule: true) { if canEditMessage(accountPeerId: accountPeerId, limitsConfiguration: limitsConfiguration, message: message, reschedule: true) {
optionsMap[id]!.insert(.editScheduledTime) optionsMap[id]!.insert(.editScheduledTime)
} }
}
if let peer = getPeer(id.peerId), let channel = peer as? TelegramChannel { if let peer = getPeer(id.peerId), let channel = peer as? TelegramChannel {
if !message.flags.contains(.Incoming) { if !message.flags.contains(.Incoming) {
optionsMap[id]!.insert(.deleteLocally) optionsMap[id]!.insert(.deleteLocally)

View File

@ -1572,6 +1572,15 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod
} }
func videoQualityState() -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? { 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 { guard let playerCurrentLevelIndex = self.playerCurrentLevelIndex else {
return nil return nil
} }

View File

@ -33,6 +33,7 @@ swift_library(
"//submodules/Components/BundleIconComponent", "//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/AnimatedTextComponent", "//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/Components/ComponentDisplayAdapters", "//submodules/Components/ComponentDisplayAdapters",
"//submodules/PhotoResources",
], ],
visibility = [ visibility = [
"//visibility:public", "//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 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 peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?)
case messageTagged(context: AccountContext, isSingleMessage: Bool, customEmoji: TelegramMediaFile, isBuiltinReaction: Bool, 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 { public enum UndoOverlayAction {

View File

@ -23,6 +23,7 @@ import TextNodeWithEntities
import BundleIconComponent import BundleIconComponent
import AnimatedTextComponent import AnimatedTextComponent
import ComponentDisplayAdapters import ComponentDisplayAdapters
import PhotoResources
final class UndoOverlayControllerNode: ViewControllerTracingNode { final class UndoOverlayControllerNode: ViewControllerTracingNode {
private let presentationData: PresentationData private let presentationData: PresentationData
@ -42,6 +43,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
private var slotMachineNode: SlotMachineAnimationNode? private var slotMachineNode: SlotMachineAnimationNode?
private var stillStickerNode: TransformImageNode? private var stillStickerNode: TransformImageNode?
private var stickerImageSize: CGSize? private var stickerImageSize: CGSize?
private var stickerSourceSize: CGSize?
private var stickerOffset: CGPoint? private var stickerOffset: CGPoint?
private var emojiStatus: ComponentView<Empty>? private var emojiStatus: ComponentView<Empty>?
private let titleNode: ImmediateTextNode private let titleNode: ImmediateTextNode
@ -1297,6 +1299,58 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} else { } else {
displayUndo = false 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 self.remainingSeconds = self.originalRemainingSeconds
@ -1340,7 +1394,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} else { } else {
self.isUserInteractionEnabled = false self.isUserInteractionEnabled = false
} }
case .sticker, .customEmoji: case .sticker, .customEmoji, .media:
self.isUserInteractionEnabled = displayUndo self.isUserInteractionEnabled = displayUndo
case .dice: case .dice:
self.panelWrapperNode.clipsToBounds = true self.panelWrapperNode.clipsToBounds = true
@ -1468,6 +1522,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
} else { } else {
let _ = self.action(.undo) let _ = self.action(.undo)
} }
case let .media(_, _, _, _, _, customAction):
if let customAction = customAction {
customAction()
} else {
let _ = self.action(.undo)
}
default: default:
let _ = self.action(.undo) let _ = self.action(.undo)
} }
@ -1775,7 +1835,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
if let stillStickerNode = self.stillStickerNode { if let stillStickerNode = self.stillStickerNode {
let makeImageLayout = stillStickerNode.asyncLayout() 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() let _ = imageApply()
transition.updateFrame(node: stillStickerNode, frame: iconFrame) transition.updateFrame(node: stillStickerNode, frame: iconFrame)
} }