mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-06 17:00:13 +00:00
Video improvements
This commit is contained in:
parent
b3d94d8092
commit
b3e0d64b37
@ -126,13 +126,18 @@ public final class RoundedRectangle: Component {
|
||||
}
|
||||
|
||||
public final class FilledRoundedRectangleComponent: Component {
|
||||
public enum CornerRadius: Equatable {
|
||||
case value(CGFloat)
|
||||
case minEdge
|
||||
}
|
||||
|
||||
public let color: UIColor
|
||||
public let cornerRadius: CGFloat
|
||||
public let cornerRadius: CornerRadius
|
||||
public let smoothCorners: Bool
|
||||
|
||||
public init(
|
||||
color: UIColor,
|
||||
cornerRadius: CGFloat,
|
||||
cornerRadius: CornerRadius,
|
||||
smoothCorners: Bool
|
||||
) {
|
||||
self.color = color
|
||||
@ -216,9 +221,17 @@ public final class FilledRoundedRectangleComponent: Component {
|
||||
|
||||
transition.setTintColor(view: self, color: component.color)
|
||||
|
||||
if self.currentCornerRadius != component.cornerRadius {
|
||||
let cornerRadius: CGFloat
|
||||
switch component.cornerRadius {
|
||||
case let .value(value):
|
||||
cornerRadius = value
|
||||
case .minEdge:
|
||||
cornerRadius = min(availableSize.width, availableSize.height) * 0.5
|
||||
}
|
||||
|
||||
if self.currentCornerRadius != cornerRadius {
|
||||
let previousCornerRadius = self.currentCornerRadius
|
||||
self.currentCornerRadius = component.cornerRadius
|
||||
self.currentCornerRadius = cornerRadius
|
||||
if transition.animation.isImmediate {
|
||||
self.applyStaticCornerRadius()
|
||||
} else {
|
||||
@ -236,7 +249,7 @@ public final class FilledRoundedRectangleComponent: Component {
|
||||
}
|
||||
|
||||
}
|
||||
transition.setCornerRadius(layer: self.layer, cornerRadius: component.cornerRadius, completion: { [weak self] completed in
|
||||
transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius, completion: { [weak self] completed in
|
||||
guard let self, completed else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -57,6 +57,9 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
||||
"//submodules/TelegramUI/Components/RasterizedCompositionComponent",
|
||||
"//submodules/TelegramUI/Components/BadgeComponent",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/ComponentFlow",
|
||||
],
|
||||
visibility = [
|
||||
|
||||
@ -303,7 +303,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
||||
|
||||
self.pager.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height), size: layout.size)
|
||||
|
||||
self.pager.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition)
|
||||
self.pager.containerLayoutUpdated(layout, navigationBarHeight: self.areControlsHidden ? 0.0 : navigationBarHeight, transition: transition)
|
||||
}
|
||||
|
||||
open func setControlsHidden(_ hidden: Bool, animated: Bool) {
|
||||
|
||||
@ -11,6 +11,11 @@ public enum GalleryItemNodeNavigationStyle {
|
||||
}
|
||||
|
||||
open class GalleryItemNode: ASDisplayNode {
|
||||
public enum ActiveEdge {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
private var _index: Int?
|
||||
public var index: Int {
|
||||
get {
|
||||
@ -111,4 +116,14 @@ open class GalleryItemNode: ASDisplayNode {
|
||||
open var keyShortcuts: [KeyShortcut] {
|
||||
return []
|
||||
}
|
||||
|
||||
open func hasActiveEdgeAction(edge: ActiveEdge) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
open func setActiveEdgeAction(edge: ActiveEdge?) {
|
||||
}
|
||||
|
||||
open func adjustActiveEdgeAction(distance: CGFloat) {
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,10 @@ private func edgeWidth(width: CGFloat) -> CGFloat {
|
||||
return min(44.0, floor(width / 6.0))
|
||||
}
|
||||
|
||||
private func activeEdgeWidth(width: CGFloat) -> CGFloat {
|
||||
return floor(width * 0.4)
|
||||
}
|
||||
|
||||
let fadeWidth: CGFloat = 70.0
|
||||
private let leftFadeImage = generateImage(CGSize(width: fadeWidth, height: 32.0), opaque: false, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
@ -85,6 +89,9 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
|
||||
private let leftFadeNode: ASDisplayNode
|
||||
private let rightFadeNode: ASDisplayNode
|
||||
private var highlightedSide: Bool?
|
||||
private var activeSide: Bool?
|
||||
private var canPerformSideNavigationAction: Bool = false
|
||||
private var sideActionInitialPosition: CGPoint?
|
||||
|
||||
private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer?
|
||||
|
||||
@ -118,6 +125,8 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
|
||||
public var pagingEnabledPromise = Promise<Bool>(true)
|
||||
private var pagingEnabledDisposable: Disposable?
|
||||
|
||||
private var edgeLongTapTimer: Foundation.Timer?
|
||||
|
||||
public init(pageGap: CGFloat, disableTapNavigation: Bool) {
|
||||
self.pageGap = pageGap
|
||||
self.disableTapNavigation = disableTapNavigation
|
||||
@ -170,57 +179,99 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
|
||||
recognizer.delegate = self.wrappedGestureRecognizerDelegate
|
||||
self.tapRecognizer = recognizer
|
||||
recognizer.tapActionAtPoint = { [weak self] point in
|
||||
guard let strongSelf = self, strongSelf.pagingEnabled else {
|
||||
guard let strongSelf = self else {
|
||||
return .fail
|
||||
}
|
||||
|
||||
let size = strongSelf.bounds
|
||||
|
||||
var highlightedSide: Bool?
|
||||
if point.x < edgeWidth(width: size.width) && strongSelf.canGoToPreviousItem() {
|
||||
if strongSelf.items.count > 1 {
|
||||
highlightedSide = false
|
||||
var activeSide: Bool?
|
||||
if point.x < edgeWidth(width: size.width) {
|
||||
if strongSelf.canGoToPreviousItem() {
|
||||
if strongSelf.items.count > 1 {
|
||||
highlightedSide = false
|
||||
}
|
||||
}
|
||||
} else if point.x > size.width - edgeWidth(width: size.width) && strongSelf.canGoToNextItem() {
|
||||
if strongSelf.items.count > 1 {
|
||||
if point.y < 80.0 {
|
||||
highlightedSide = nil
|
||||
} else {
|
||||
} else if point.x > size.width - edgeWidth(width: size.width) {
|
||||
if strongSelf.canGoToNextItem() {
|
||||
if strongSelf.items.count > 1 {
|
||||
highlightedSide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if point.x < activeEdgeWidth(width: size.width) {
|
||||
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .left) {
|
||||
activeSide = false
|
||||
}
|
||||
} else if point.x > size.width - activeEdgeWidth(width: size.width) {
|
||||
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .right) {
|
||||
activeSide = true
|
||||
}
|
||||
}
|
||||
|
||||
if highlightedSide == nil {
|
||||
if !strongSelf.pagingEnabled {
|
||||
highlightedSide = nil
|
||||
}
|
||||
|
||||
if highlightedSide == nil && activeSide == nil {
|
||||
return .fail
|
||||
}
|
||||
|
||||
if let result = strongSelf.hitTest(point, with: nil), let _ = result.asyncdisplaykit_node as? ASButtonNode {
|
||||
return .fail
|
||||
}
|
||||
return .keepWithSingleTap
|
||||
|
||||
if activeSide != nil {
|
||||
return .waitForHold(timeout: 0.3, acceptTap: true)
|
||||
} else {
|
||||
return .keepWithSingleTap
|
||||
}
|
||||
}
|
||||
recognizer.highlight = { [weak self] point in
|
||||
guard let strongSelf = self, strongSelf.pagingEnabled else {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let size = strongSelf.bounds
|
||||
|
||||
var highlightedSide: Bool?
|
||||
if let point = point {
|
||||
if point.x < edgeWidth(width: size.width) && strongSelf.canGoToPreviousItem() {
|
||||
if strongSelf.items.count > 1 {
|
||||
highlightedSide = false
|
||||
var activeSide: Bool?
|
||||
if let point {
|
||||
if point.x < edgeWidth(width: size.width) {
|
||||
if strongSelf.canGoToPreviousItem() {
|
||||
if strongSelf.items.count > 1 {
|
||||
highlightedSide = false
|
||||
}
|
||||
}
|
||||
} else if point.x > size.width - edgeWidth(width: size.width) && strongSelf.canGoToNextItem() {
|
||||
if strongSelf.items.count > 1 {
|
||||
highlightedSide = true
|
||||
} else if point.x > size.width - edgeWidth(width: size.width) {
|
||||
if strongSelf.canGoToNextItem() {
|
||||
if strongSelf.items.count > 1 {
|
||||
highlightedSide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if point.x < activeEdgeWidth(width: size.width) {
|
||||
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .left) {
|
||||
activeSide = false
|
||||
}
|
||||
} else if point.x > size.width - activeEdgeWidth(width: size.width) {
|
||||
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex), itemNode.hasActiveEdgeAction(edge: .right) {
|
||||
activeSide = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !strongSelf.pagingEnabled {
|
||||
highlightedSide = nil
|
||||
}
|
||||
|
||||
if strongSelf.highlightedSide != highlightedSide {
|
||||
strongSelf.highlightedSide = highlightedSide
|
||||
|
||||
if highlightedSide != nil {
|
||||
strongSelf.canPerformSideNavigationAction = true
|
||||
}
|
||||
|
||||
let leftAlpha: CGFloat
|
||||
let rightAlpha: CGFloat
|
||||
if let highlightedSide = highlightedSide {
|
||||
@ -247,6 +298,47 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if strongSelf.activeSide != activeSide {
|
||||
strongSelf.activeSide = activeSide
|
||||
|
||||
if let activeSide, let centralIndex = strongSelf.centralItemIndex, let _ = strongSelf.visibleItemNode(at: centralIndex) {
|
||||
if strongSelf.edgeLongTapTimer == nil {
|
||||
strongSelf.edgeLongTapTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 0.3, repeats: false, block: { _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let centralIndex = self.centralItemIndex, let itemNode = self.visibleItemNode(at: centralIndex) {
|
||||
itemNode.setActiveEdgeAction(edge: activeSide ? .right : .left)
|
||||
}
|
||||
|
||||
self.canPerformSideNavigationAction = false
|
||||
|
||||
let leftAlpha: CGFloat
|
||||
let rightAlpha: CGFloat
|
||||
|
||||
leftAlpha = 0.0
|
||||
rightAlpha = 0.0
|
||||
|
||||
if self.leftFadeNode.alpha != leftAlpha {
|
||||
self.leftFadeNode.alpha = leftAlpha
|
||||
self.leftFadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
if self.rightFadeNode.alpha != rightAlpha {
|
||||
self.rightFadeNode.alpha = rightAlpha
|
||||
self.rightFadeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.16, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if let edgeLongTapTimer = strongSelf.edgeLongTapTimer {
|
||||
edgeLongTapTimer.invalidate()
|
||||
strongSelf.edgeLongTapTimer = nil
|
||||
|
||||
if let centralIndex = strongSelf.centralItemIndex, let itemNode = strongSelf.visibleItemNode(at: centralIndex) {
|
||||
itemNode.setActiveEdgeAction(edge: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
@ -258,8 +350,9 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
|
||||
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
self.sideActionInitialPosition = nil
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
if case .tap = gesture {
|
||||
if case .tap = gesture, self.canPerformSideNavigationAction {
|
||||
let size = self.bounds.size
|
||||
if location.x < edgeWidth(width: size.width) && self.canGoToPreviousItem() {
|
||||
self.goToPreviousItem()
|
||||
@ -268,6 +361,21 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
|
||||
}
|
||||
}
|
||||
}
|
||||
case .cancelled:
|
||||
self.sideActionInitialPosition = nil
|
||||
case .began:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, case .hold = gesture {
|
||||
self.sideActionInitialPosition = location
|
||||
}
|
||||
case .changed:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, case .hold = gesture {
|
||||
if let sideActionInitialPosition = self.sideActionInitialPosition {
|
||||
let distance = location.x - sideActionInitialPosition.x
|
||||
if let centralIndex = self.centralItemIndex, let itemNode = self.visibleItemNode(at: centralIndex) {
|
||||
itemNode.adjustActiveEdgeAction(distance: distance)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
122
submodules/GalleryUI/Sources/GalleryRateToastComponent.swift
Normal file
122
submodules/GalleryUI/Sources/GalleryRateToastComponent.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -32,6 +32,7 @@ import SectionTitleContextItem
|
||||
import RasterizedCompositionComponent
|
||||
import BadgeComponent
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
|
||||
public enum UniversalVideoGalleryItemContentInfo {
|
||||
case message(Message, Int?)
|
||||
@ -1318,7 +1319,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private var playOnContentOwnership = false
|
||||
private var skipInitialPause = false
|
||||
private var ignorePauseStatus = false
|
||||
private var validLayout: (ContainerViewLayout, CGFloat)?
|
||||
private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat)?
|
||||
private var didPause = false
|
||||
private var isPaused = true
|
||||
private var dismissOnOrientationChange = false
|
||||
@ -1368,6 +1369,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private var activePictureInPictureNavigationController: NavigationController?
|
||||
private var activePictureInPictureController: ViewController?
|
||||
|
||||
private var activeEdgeRateState: (initialRate: Double, currentRate: Double)?
|
||||
private var activeEdgeRateIndicator: ComponentView<Empty>?
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
@ -1603,6 +1607,46 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
pictureInPictureNode.updateLayout(placeholderSize, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
if let activeEdgeRateState = self.activeEdgeRateState {
|
||||
var activeEdgeRateIndicatorTransition = transition
|
||||
let activeEdgeRateIndicator: ComponentView<Empty>
|
||||
if let current = self.activeEdgeRateIndicator {
|
||||
activeEdgeRateIndicator = current
|
||||
} else {
|
||||
activeEdgeRateIndicator = ComponentView()
|
||||
self.activeEdgeRateIndicator = activeEdgeRateIndicator
|
||||
activeEdgeRateIndicatorTransition = .immediate
|
||||
}
|
||||
|
||||
let activeEdgeRateIndicatorSize = activeEdgeRateIndicator.update(
|
||||
transition: ComponentTransition(activeEdgeRateIndicatorTransition),
|
||||
component: AnyComponent(GalleryRateToastComponent(
|
||||
rate: activeEdgeRateState.currentRate
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 100.0)
|
||||
)
|
||||
let activeEdgeRateIndicatorFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - activeEdgeRateIndicatorSize.width) * 0.5), y: max(navigationBarHeight, layout.statusBarHeight ?? 0.0) + 8.0), size: activeEdgeRateIndicatorSize)
|
||||
if let activeEdgeRateIndicatorView = activeEdgeRateIndicator.view {
|
||||
if activeEdgeRateIndicatorView.superview == nil {
|
||||
self.view.addSubview(activeEdgeRateIndicatorView)
|
||||
transition.animateTransformScale(view: activeEdgeRateIndicatorView, from: 0.001)
|
||||
if transition.isAnimated {
|
||||
activeEdgeRateIndicatorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
activeEdgeRateIndicatorTransition.updateFrame(view: activeEdgeRateIndicatorView, frame: activeEdgeRateIndicatorFrame)
|
||||
}
|
||||
} else if let activeEdgeRateIndicator = self.activeEdgeRateIndicator {
|
||||
self.activeEdgeRateIndicator = nil
|
||||
if let activeEdgeRateIndicatorView = activeEdgeRateIndicator.view {
|
||||
transition.updateAlpha(layer: activeEdgeRateIndicatorView.layer, alpha: 0.0, completion: { [weak activeEdgeRateIndicatorView] _ in
|
||||
activeEdgeRateIndicatorView?.removeFromSuperview()
|
||||
})
|
||||
transition.updateTransformScale(layer: activeEdgeRateIndicatorView.layer, scale: 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
if dismiss {
|
||||
self.dismiss()
|
||||
@ -3938,6 +3982,59 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
return keyShortcuts
|
||||
}
|
||||
|
||||
override func hasActiveEdgeAction(edge: ActiveEdge) -> Bool {
|
||||
if case .right = edge {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override func setActiveEdgeAction(edge: ActiveEdge?) {
|
||||
guard let videoNode = self.videoNode else {
|
||||
return
|
||||
}
|
||||
if let edge, case .right = edge {
|
||||
let effectiveRate: Double
|
||||
if let current = self.activeEdgeRateState {
|
||||
effectiveRate = min(2.5, current.initialRate + 0.5)
|
||||
self.activeEdgeRateState = (current.initialRate, effectiveRate)
|
||||
} else {
|
||||
guard let playbackRate = self.playbackRate else {
|
||||
return
|
||||
}
|
||||
effectiveRate = min(2.5, playbackRate + 0.5)
|
||||
self.activeEdgeRateState = (playbackRate, effectiveRate)
|
||||
}
|
||||
videoNode.setBaseRate(effectiveRate)
|
||||
} else if let (initialRate, _) = self.activeEdgeRateState {
|
||||
self.activeEdgeRateState = nil
|
||||
videoNode.setBaseRate(initialRate)
|
||||
}
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: .animated(duration: 0.35, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
override func adjustActiveEdgeAction(distance: CGFloat) {
|
||||
guard let videoNode = self.videoNode else {
|
||||
return
|
||||
}
|
||||
if let current = self.activeEdgeRateState {
|
||||
var rateFraction = Double(distance) / 100.0
|
||||
rateFraction = max(0.0, min(1.0, rateFraction))
|
||||
let rateDistance = (current.initialRate + 0.5) * (1.0 - rateFraction) + 2.5 * rateFraction
|
||||
let effectiveRate = max(1.0, min(2.5, rateDistance))
|
||||
self.activeEdgeRateState = (current.initialRate, effectiveRate)
|
||||
videoNode.setBaseRate(effectiveRate)
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: .animated(duration: 0.35, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
|
||||
@ -902,7 +902,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1401868056] = { return Api.StarsSubscription.parse_starsSubscription($0) }
|
||||
dict[88173912] = { return Api.StarsSubscriptionPricing.parse_starsSubscriptionPricing($0) }
|
||||
dict[198776256] = { return Api.StarsTopupOption.parse_starsTopupOption($0) }
|
||||
dict[-1216644148] = { return Api.StarsTransaction.parse_starsTransaction($0) }
|
||||
dict[903148150] = { return Api.StarsTransaction.parse_starsTransaction($0) }
|
||||
dict[-670195363] = { return Api.StarsTransactionPeer.parse_starsTransactionPeer($0) }
|
||||
dict[-110658899] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAPI($0) }
|
||||
dict[1617438738] = { return Api.StarsTransactionPeer.parse_starsTransactionPeerAds($0) }
|
||||
@ -1005,7 +1005,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1576161051] = { return Api.Update.parse_updateDeleteMessages($0) }
|
||||
dict[1407644140] = { return Api.Update.parse_updateDeleteQuickReply($0) }
|
||||
dict[1450174413] = { return Api.Update.parse_updateDeleteQuickReplyMessages($0) }
|
||||
dict[-1870238482] = { return Api.Update.parse_updateDeleteScheduledMessages($0) }
|
||||
dict[-223929981] = { return Api.Update.parse_updateDeleteScheduledMessages($0) }
|
||||
dict[654302845] = { return Api.Update.parse_updateDialogFilter($0) }
|
||||
dict[-1512627963] = { return Api.Update.parse_updateDialogFilterOrder($0) }
|
||||
dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) }
|
||||
|
||||
@ -1010,13 +1010,13 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
enum StarsTransaction: TypeConstructorDescription {
|
||||
case starsTransaction(flags: Int32, id: String, stars: Int64, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipDate: Int32?, floodskipNumber: Int32?)
|
||||
case starsTransaction(flags: Int32, id: String, stars: Int64, date: Int32, peer: Api.StarsTransactionPeer, title: String?, description: String?, photo: Api.WebDocument?, transactionDate: Int32?, transactionUrl: String?, botPayload: Buffer?, msgId: Int32?, extendedMedia: [Api.MessageMedia]?, subscriptionPeriod: Int32?, giveawayPostId: Int32?, stargift: Api.StarGift?, floodskipNumber: Int32?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipDate, let floodskipNumber):
|
||||
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1216644148)
|
||||
buffer.appendInt32(903148150)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeString(id, buffer: buffer, boxed: false)
|
||||
@ -1038,7 +1038,6 @@ public extension Api {
|
||||
if Int(flags) & Int(1 << 12) != 0 {serializeInt32(subscriptionPeriod!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 13) != 0 {serializeInt32(giveawayPostId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 14) != 0 {stargift!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(floodskipDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 15) != 0 {serializeInt32(floodskipNumber!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
@ -1046,8 +1045,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipDate, let floodskipNumber):
|
||||
return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any), ("subscriptionPeriod", subscriptionPeriod as Any), ("giveawayPostId", giveawayPostId as Any), ("stargift", stargift as Any), ("floodskipDate", floodskipDate as Any), ("floodskipNumber", floodskipNumber as Any)])
|
||||
case .starsTransaction(let flags, let id, let stars, let date, let peer, let title, let description, let photo, let transactionDate, let transactionUrl, let botPayload, let msgId, let extendedMedia, let subscriptionPeriod, let giveawayPostId, let stargift, let floodskipNumber):
|
||||
return ("starsTransaction", [("flags", flags as Any), ("id", id as Any), ("stars", stars as Any), ("date", date as Any), ("peer", peer as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("transactionDate", transactionDate as Any), ("transactionUrl", transactionUrl as Any), ("botPayload", botPayload as Any), ("msgId", msgId as Any), ("extendedMedia", extendedMedia as Any), ("subscriptionPeriod", subscriptionPeriod as Any), ("giveawayPostId", giveawayPostId as Any), ("stargift", stargift as Any), ("floodskipNumber", floodskipNumber as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -1094,8 +1093,6 @@ public extension Api {
|
||||
} }
|
||||
var _17: Int32?
|
||||
if Int(_1!) & Int(1 << 15) != 0 {_17 = reader.readInt32() }
|
||||
var _18: Int32?
|
||||
if Int(_1!) & Int(1 << 15) != 0 {_18 = reader.readInt32() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -1113,9 +1110,8 @@ public extension Api {
|
||||
let _c15 = (Int(_1!) & Int(1 << 13) == 0) || _15 != nil
|
||||
let _c16 = (Int(_1!) & Int(1 << 14) == 0) || _16 != nil
|
||||
let _c17 = (Int(_1!) & Int(1 << 15) == 0) || _17 != nil
|
||||
let _c18 = (Int(_1!) & Int(1 << 15) == 0) || _18 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 {
|
||||
return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipDate: _17, floodskipNumber: _18)
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 {
|
||||
return Api.StarsTransaction.starsTransaction(flags: _1!, id: _2!, stars: _3!, date: _4!, peer: _5!, title: _6, description: _7, photo: _8, transactionDate: _9, transactionUrl: _10, botPayload: _11, msgId: _12, extendedMedia: _13, subscriptionPeriod: _14, giveawayPostId: _15, stargift: _16, floodskipNumber: _17)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
||||
@ -676,7 +676,7 @@ public extension Api {
|
||||
case updateDeleteMessages(messages: [Int32], pts: Int32, ptsCount: Int32)
|
||||
case updateDeleteQuickReply(shortcutId: Int32)
|
||||
case updateDeleteQuickReplyMessages(shortcutId: Int32, messages: [Int32])
|
||||
case updateDeleteScheduledMessages(peer: Api.Peer, messages: [Int32])
|
||||
case updateDeleteScheduledMessages(flags: Int32, peer: Api.Peer, messages: [Int32], sentMessages: [Int32]?)
|
||||
case updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?)
|
||||
case updateDialogFilterOrder(order: [Int32])
|
||||
case updateDialogFilters
|
||||
@ -1246,16 +1246,22 @@ public extension Api {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
break
|
||||
case .updateDeleteScheduledMessages(let peer, let messages):
|
||||
case .updateDeleteScheduledMessages(let flags, let peer, let messages, let sentMessages):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1870238482)
|
||||
buffer.appendInt32(-223929981)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
peer.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(messages.count))
|
||||
for item in messages {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(sentMessages!.count))
|
||||
for item in sentMessages! {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}}
|
||||
break
|
||||
case .updateDialogFilter(let flags, let id, let filter):
|
||||
if boxed {
|
||||
@ -2097,8 +2103,8 @@ public extension Api {
|
||||
return ("updateDeleteQuickReply", [("shortcutId", shortcutId as Any)])
|
||||
case .updateDeleteQuickReplyMessages(let shortcutId, let messages):
|
||||
return ("updateDeleteQuickReplyMessages", [("shortcutId", shortcutId as Any), ("messages", messages as Any)])
|
||||
case .updateDeleteScheduledMessages(let peer, let messages):
|
||||
return ("updateDeleteScheduledMessages", [("peer", peer as Any), ("messages", messages as Any)])
|
||||
case .updateDeleteScheduledMessages(let flags, let peer, let messages, let sentMessages):
|
||||
return ("updateDeleteScheduledMessages", [("flags", flags as Any), ("peer", peer as Any), ("messages", messages as Any), ("sentMessages", sentMessages as Any)])
|
||||
case .updateDialogFilter(let flags, let id, let filter):
|
||||
return ("updateDialogFilter", [("flags", flags as Any), ("id", id as Any), ("filter", filter as Any)])
|
||||
case .updateDialogFilterOrder(let order):
|
||||
@ -3309,18 +3315,26 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
public static func parse_updateDeleteScheduledMessages(_ reader: BufferReader) -> Update? {
|
||||
var _1: Api.Peer?
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Api.Peer?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||
}
|
||||
var _2: [Int32]?
|
||||
var _3: [Int32]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||
_3 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||
}
|
||||
var _4: [Int32]?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.Update.updateDeleteScheduledMessages(peer: _1!, messages: _2!)
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.Update.updateDeleteScheduledMessages(flags: _1!, peer: _2!, messages: _3!, sentMessages: _4)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
||||
@ -105,7 +105,7 @@ final class VideoChatExpandedSpeakingToastComponent: Component {
|
||||
transition: transition,
|
||||
component: AnyComponent(FilledRoundedRectangleComponent(
|
||||
color: UIColor(white: 0.0, alpha: 0.9),
|
||||
cornerRadius: size.height * 0.5,
|
||||
cornerRadius: .value(size.height * 0.5),
|
||||
smoothCorners: false
|
||||
)),
|
||||
environment: {},
|
||||
|
||||
@ -1224,7 +1224,7 @@ final class VideoChatScreenComponent: Component {
|
||||
)),
|
||||
background: AnyComponent(FilledRoundedRectangleComponent(
|
||||
color: UIColor(white: 1.0, alpha: 0.1),
|
||||
cornerRadius: navigationButtonDiameter * 0.5,
|
||||
cornerRadius: .value(navigationButtonDiameter * 0.5),
|
||||
smoothCorners: false
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
|
||||
@ -212,6 +212,7 @@ struct AccountMutableState {
|
||||
var namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]]
|
||||
var updatedOutgoingUniqueMessageIds: [Int64: Int32]
|
||||
var storedStories: [StoryId: UpdatesStoredStory]
|
||||
var sentScheduledMessageIds: Set<MessageId>
|
||||
|
||||
var resetForumTopicLists: [PeerId: StateResetForumTopics] = [:]
|
||||
|
||||
@ -231,7 +232,7 @@ struct AccountMutableState {
|
||||
|
||||
var authorizationListUpdated: Bool = false
|
||||
|
||||
init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialReferencedReplyMessageIds: ReferencedReplyMessageIds, initialReferencedGeneralMessageIds: Set<MessageId>, initialStoredMessages: Set<MessageId>, initialStoredStories: [StoryId: UpdatesStoredStory], initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>]) {
|
||||
init(initialState: AccountInitialState, initialPeers: [PeerId: Peer], initialReferencedReplyMessageIds: ReferencedReplyMessageIds, initialReferencedGeneralMessageIds: Set<MessageId>, initialStoredMessages: Set<MessageId>, initialStoredStories: [StoryId: UpdatesStoredStory], initialReadInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], initialSentScheduledMessageIds: Set<MessageId>) {
|
||||
self.initialState = initialState
|
||||
self.state = initialState.state
|
||||
self.peers = initialPeers
|
||||
@ -240,6 +241,7 @@ struct AccountMutableState {
|
||||
self.referencedGeneralMessageIds = initialReferencedGeneralMessageIds
|
||||
self.storedMessages = initialStoredMessages
|
||||
self.storedStories = initialStoredStories
|
||||
self.sentScheduledMessageIds = initialSentScheduledMessageIds
|
||||
self.readInboxMaxIds = initialReadInboxMaxIds
|
||||
self.channelStates = initialState.channelStates
|
||||
self.peerChatInfos = initialState.peerChatInfos
|
||||
@ -249,7 +251,7 @@ struct AccountMutableState {
|
||||
self.updatedOutgoingUniqueMessageIds = [:]
|
||||
}
|
||||
|
||||
init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], apiChats: [PeerId: Api.Chat], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedReplyMessageIds: ReferencedReplyMessageIds, referencedGeneralMessageIds: Set<MessageId>, storedMessages: Set<MessageId>, storedStories: [StoryId: UpdatesStoredStory], readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], dismissBotWebViews: [Int64], branchOperationIndex: Int) {
|
||||
init(initialState: AccountInitialState, operations: [AccountStateMutationOperation], state: AuthorizedAccountState.State, peers: [PeerId: Peer], apiChats: [PeerId: Api.Chat], channelStates: [PeerId: AccountStateChannelState], peerChatInfos: [PeerId: PeerChatInfo], referencedReplyMessageIds: ReferencedReplyMessageIds, referencedGeneralMessageIds: Set<MessageId>, storedMessages: Set<MessageId>, storedStories: [StoryId: UpdatesStoredStory], sentScheduledMessageIds: Set<MessageId>, readInboxMaxIds: [PeerId: MessageId], storedMessagesByPeerIdAndTimestamp: [PeerId: Set<MessageIndex>], namespacesWithHolesFromPreviousState: [PeerId: [MessageId.Namespace: HoleFromPreviousState]], updatedOutgoingUniqueMessageIds: [Int64: Int32], displayAlerts: [(text: String, isDropAuth: Bool)], dismissBotWebViews: [Int64], branchOperationIndex: Int) {
|
||||
self.initialState = initialState
|
||||
self.operations = operations
|
||||
self.state = state
|
||||
@ -260,6 +262,7 @@ struct AccountMutableState {
|
||||
self.referencedGeneralMessageIds = referencedGeneralMessageIds
|
||||
self.storedMessages = storedMessages
|
||||
self.storedStories = storedStories
|
||||
self.sentScheduledMessageIds = sentScheduledMessageIds
|
||||
self.peerChatInfos = peerChatInfos
|
||||
self.readInboxMaxIds = readInboxMaxIds
|
||||
self.storedMessagesByPeerIdAndTimestamp = storedMessagesByPeerIdAndTimestamp
|
||||
@ -271,7 +274,7 @@ struct AccountMutableState {
|
||||
}
|
||||
|
||||
func branch() -> AccountMutableState {
|
||||
return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, apiChats: self.apiChats, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedReplyMessageIds: self.referencedReplyMessageIds, referencedGeneralMessageIds: self.referencedGeneralMessageIds, storedMessages: self.storedMessages, storedStories: self.storedStories, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, dismissBotWebViews: self.dismissBotWebViews, branchOperationIndex: self.operations.count)
|
||||
return AccountMutableState(initialState: self.initialState, operations: self.operations, state: self.state, peers: self.peers, apiChats: self.apiChats, channelStates: self.channelStates, peerChatInfos: self.peerChatInfos, referencedReplyMessageIds: self.referencedReplyMessageIds, referencedGeneralMessageIds: self.referencedGeneralMessageIds, storedMessages: self.storedMessages, storedStories: self.storedStories, sentScheduledMessageIds: self.sentScheduledMessageIds, readInboxMaxIds: self.readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: self.storedMessagesByPeerIdAndTimestamp, namespacesWithHolesFromPreviousState: self.namespacesWithHolesFromPreviousState, updatedOutgoingUniqueMessageIds: self.updatedOutgoingUniqueMessageIds, displayAlerts: self.displayAlerts, dismissBotWebViews: self.dismissBotWebViews, branchOperationIndex: self.operations.count)
|
||||
}
|
||||
|
||||
mutating func merge(_ other: AccountMutableState) {
|
||||
@ -282,6 +285,8 @@ struct AccountMutableState {
|
||||
self.storedStories[id] = story
|
||||
}
|
||||
|
||||
self.sentScheduledMessageIds.formUnion(other.sentScheduledMessageIds)
|
||||
|
||||
for i in other.branchOperationIndex ..< other.operations.count {
|
||||
self.addOperation(other.operations[i])
|
||||
}
|
||||
@ -357,6 +362,10 @@ struct AccountMutableState {
|
||||
self.addOperation(.DeleteMessages(messageIds))
|
||||
}
|
||||
|
||||
mutating func addSentScheduledMessageIds(_ messageIds: [MessageId]) {
|
||||
self.sentScheduledMessageIds.formUnion(messageIds)
|
||||
}
|
||||
|
||||
mutating func editMessage(_ id: MessageId, message: StoreMessage) {
|
||||
self.addOperation(.EditMessage(id, message))
|
||||
}
|
||||
@ -842,13 +851,15 @@ struct AccountReplayedFinalState {
|
||||
let updatedRevenueBalances: [PeerId: RevenueStats.Balances]
|
||||
let updatedStarsBalance: [PeerId: Int64]
|
||||
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
|
||||
let sentScheduledMessageIds: Set<MessageId>
|
||||
}
|
||||
|
||||
struct AccountFinalStateEvents {
|
||||
let addedIncomingMessageIds: [MessageId]
|
||||
let addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)]
|
||||
let wasScheduledMessageIds:[MessageId]
|
||||
let wasScheduledMessageIds: [MessageId]
|
||||
let deletedMessageIds: [DeletedMessageId]
|
||||
let sentScheduledMessageIds: Set<MessageId>
|
||||
let updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]]
|
||||
let updatedWebpages: [MediaId: TelegramMediaWebpage]
|
||||
let updatedCalls: [Api.PhoneCall]
|
||||
@ -873,10 +884,10 @@ struct AccountFinalStateEvents {
|
||||
let updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances]
|
||||
|
||||
var isEmpty: Bool {
|
||||
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty
|
||||
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedRevenueBalances.isEmpty && self.updatedStarsBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty
|
||||
}
|
||||
|
||||
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: Int64] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:]) {
|
||||
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedRevenueBalances: [PeerId: RevenueStats.Balances] = [:], updatedStarsBalance: [PeerId: Int64] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set()) {
|
||||
self.addedIncomingMessageIds = addedIncomingMessageIds
|
||||
self.addedReactionEvents = addedReactionEvents
|
||||
self.wasScheduledMessageIds = wasScheduledMessageIds
|
||||
@ -903,6 +914,7 @@ struct AccountFinalStateEvents {
|
||||
self.updatedRevenueBalances = updatedRevenueBalances
|
||||
self.updatedStarsBalance = updatedStarsBalance
|
||||
self.updatedStarsRevenueStatus = updatedStarsRevenueStatus
|
||||
self.sentScheduledMessageIds = sentScheduledMessageIds
|
||||
}
|
||||
|
||||
init(state: AccountReplayedFinalState) {
|
||||
@ -932,6 +944,7 @@ struct AccountFinalStateEvents {
|
||||
self.updatedRevenueBalances = state.updatedRevenueBalances
|
||||
self.updatedStarsBalance = state.updatedStarsBalance
|
||||
self.updatedStarsRevenueStatus = state.updatedStarsRevenueStatus
|
||||
self.sentScheduledMessageIds = state.sentScheduledMessageIds
|
||||
}
|
||||
|
||||
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
|
||||
@ -961,6 +974,9 @@ struct AccountFinalStateEvents {
|
||||
|
||||
let isPremiumUpdated = self.isPremiumUpdated || other.isPremiumUpdated
|
||||
|
||||
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }))
|
||||
var sentScheduledMessageIds = self.sentScheduledMessageIds
|
||||
sentScheduledMessageIds.formUnion(other.sentScheduledMessageIds)
|
||||
|
||||
return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, addedReactionEvents: self.addedReactionEvents + other.addedReactionEvents, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, storyUpdates: self.storyUpdates + other.storyUpdates, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, dismissBotWebViews: self.dismissBotWebViews + other.dismissBotWebViews, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs }), updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: self.updatedRevenueBalances.merging(other.updatedRevenueBalances, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsBalance: self.updatedStarsBalance.merging(other.updatedStarsBalance, uniquingKeysWith: { lhs, _ in lhs }), updatedStarsRevenueStatus: self.updatedStarsRevenueStatus.merging(other.updatedStarsRevenueStatus, uniquingKeysWith: { lhs, _ in lhs }), sentScheduledMessageIds: sentScheduledMessageIds)
|
||||
}
|
||||
}
|
||||
|
||||
@ -529,7 +529,7 @@ func initialStateWithPeerIds(_ transaction: Transaction, peerIds: Set<PeerId>, a
|
||||
}
|
||||
}
|
||||
|
||||
let state = AccountMutableState(initialState: AccountInitialState(state: (transaction.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates, channelsToPollExplicitely: channelsToPollExplicitely), initialPeers: peers, initialReferencedReplyMessageIds: referencedReplyMessageIds, initialReferencedGeneralMessageIds: referencedGeneralMessageIds, initialStoredMessages: storedMessages, initialStoredStories: storedStories, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp)
|
||||
let state = AccountMutableState(initialState: AccountInitialState(state: (transaction.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, peerIdsRequiringLocalChatState: peerIdsRequiringLocalChatState, channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates, channelsToPollExplicitely: channelsToPollExplicitely), initialPeers: peers, initialReferencedReplyMessageIds: referencedReplyMessageIds, initialReferencedGeneralMessageIds: referencedGeneralMessageIds, initialStoredMessages: storedMessages, initialStoredStories: storedStories, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp, initialSentScheduledMessageIds: Set())
|
||||
return state
|
||||
}
|
||||
|
||||
@ -1666,12 +1666,19 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
|
||||
if let message = StoreMessage(apiMessage: apiMessage, accountPeerId: accountPeerId, peerIsForum: peerIsForum, namespace: Namespaces.Message.QuickReplyCloud) {
|
||||
updatedState.addQuickReplyMessages([message])
|
||||
}
|
||||
case let .updateDeleteScheduledMessages(peer, messages):
|
||||
case let .updateDeleteScheduledMessages(_, peer, messages, sentMessages):
|
||||
var messageIds: [MessageId] = []
|
||||
var sentMessageIds: [MessageId] = []
|
||||
for message in messages {
|
||||
messageIds.append(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.ScheduledCloud, id: message))
|
||||
}
|
||||
if let sentMessages {
|
||||
for message in sentMessages {
|
||||
sentMessageIds.append(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: message))
|
||||
}
|
||||
}
|
||||
updatedState.deleteMessages(messageIds)
|
||||
updatedState.addSentScheduledMessageIds(sentMessageIds)
|
||||
case let .updateDeleteQuickReplyMessages(_, messages):
|
||||
var messageIds: [MessageId] = []
|
||||
for message in messages {
|
||||
@ -2628,7 +2635,7 @@ func pollChannelOnce(accountPeerId: PeerId, postbox: Postbox, network: Network,
|
||||
peerChatInfos[peerId] = PeerChatInfo(notificationSettings: notificationSettings)
|
||||
}
|
||||
}
|
||||
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedReplyMessageIds: ReferencedReplyMessageIds(), initialReferencedGeneralMessageIds: Set(), initialStoredMessages: Set(), initialStoredStories: [:], initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
|
||||
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedReplyMessageIds: ReferencedReplyMessageIds(), initialReferencedGeneralMessageIds: Set(), initialStoredMessages: Set(), initialStoredStories: [:], initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:], initialSentScheduledMessageIds: Set())
|
||||
return pollChannel(accountPeerId: accountPeerId, postbox: postbox, network: network, peer: peer, state: initialState)
|
||||
|> mapToSignal { (finalState, _, timeout) -> Signal<Int32, NoError> in
|
||||
return resolveAssociatedMessages(accountPeerId: accountPeerId, postbox: postbox, network: network, state: finalState)
|
||||
@ -2685,7 +2692,7 @@ public func standalonePollChannelOnce(accountPeerId: PeerId, postbox: Postbox, n
|
||||
peerChatInfos[peerId] = PeerChatInfo(notificationSettings: notificationSettings)
|
||||
}
|
||||
}
|
||||
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedReplyMessageIds: ReferencedReplyMessageIds(), initialReferencedGeneralMessageIds: Set(), initialStoredMessages: Set(), initialStoredStories: [:], initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
|
||||
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), peerIdsRequiringLocalChatState: Set(), channelStates: channelStates, peerChatInfos: peerChatInfos, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:], channelsToPollExplicitely: Set()), initialPeers: initialPeers, initialReferencedReplyMessageIds: ReferencedReplyMessageIds(), initialReferencedGeneralMessageIds: Set(), initialStoredMessages: Set(), initialStoredStories: [:], initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:], initialSentScheduledMessageIds: Set())
|
||||
return pollChannel(accountPeerId: accountPeerId, postbox: postbox, network: network, peer: peer, state: initialState)
|
||||
|> mapToSignal { (finalState, _, timeout) -> Signal<Never, NoError> in
|
||||
return resolveAssociatedMessages(accountPeerId: accountPeerId, postbox: postbox, network: network, state: finalState)
|
||||
@ -5342,5 +5349,29 @@ func replayFinalState(
|
||||
_internal_setStarsReactionDefaultToPrivate(isPrivate: updatedStarsReactionsAreAnonymousByDefault, transaction: transaction)
|
||||
}
|
||||
|
||||
return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, addedReactionEvents: addedReactionEvents, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, deletedMessageIds: deletedMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedGroupCallParticipants: updatedGroupCallParticipants, storyUpdates: storyUpdates, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates, updateConfig: updateConfig, isPremiumUpdated: isPremiumUpdated, updatedRevenueBalances: updatedRevenueBalances, updatedStarsBalance: updatedStarsBalance, updatedStarsRevenueStatus: updatedStarsRevenueStatus)
|
||||
return AccountReplayedFinalState(
|
||||
state: finalState,
|
||||
addedIncomingMessageIds: addedIncomingMessageIds,
|
||||
addedReactionEvents: addedReactionEvents,
|
||||
wasScheduledMessageIds: wasScheduledMessageIds,
|
||||
addedSecretMessageIds: addedSecretMessageIds,
|
||||
deletedMessageIds: deletedMessageIds,
|
||||
updatedTypingActivities: updatedTypingActivities,
|
||||
updatedWebpages: updatedWebpages,
|
||||
updatedCalls: updatedCalls,
|
||||
addedCallSignalingData: addedCallSignalingData,
|
||||
updatedGroupCallParticipants: updatedGroupCallParticipants,
|
||||
storyUpdates: storyUpdates,
|
||||
updatedPeersNearby: updatedPeersNearby,
|
||||
isContactUpdates: isContactUpdates,
|
||||
delayNotificatonsUntil: delayNotificatonsUntil,
|
||||
updatedIncomingThreadReadStates: updatedIncomingThreadReadStates,
|
||||
updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates,
|
||||
updateConfig: updateConfig,
|
||||
isPremiumUpdated: isPremiumUpdated,
|
||||
updatedRevenueBalances: updatedRevenueBalances,
|
||||
updatedStarsBalance: updatedStarsBalance,
|
||||
updatedStarsRevenueStatus: updatedStarsRevenueStatus,
|
||||
sentScheduledMessageIds: finalState.state.sentScheduledMessageIds
|
||||
)
|
||||
}
|
||||
|
||||
@ -63,6 +63,7 @@ public enum DeletedMessageId: Hashable {
|
||||
|
||||
final class MessagesRemovedContext {
|
||||
private var messagesRemovedInteractively = Set<DeletedMessageId>()
|
||||
private var messagesRemovedRemotely = Set<DeletedMessageId>()
|
||||
private var messagesRemovedInteractivelyLock = NSLock()
|
||||
|
||||
func synchronouslyIsMessageDeletedInteractively(ids: [MessageId]) -> [EngineMessage.Id] {
|
||||
@ -85,6 +86,26 @@ final class MessagesRemovedContext {
|
||||
return result
|
||||
}
|
||||
|
||||
func synchronouslyIsMessageDeletedRemotely(ids: [MessageId]) -> [EngineMessage.Id] {
|
||||
var result: [EngineMessage.Id] = []
|
||||
|
||||
self.messagesRemovedInteractivelyLock.lock()
|
||||
for id in ids {
|
||||
let mappedId: DeletedMessageId
|
||||
if id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
mappedId = .global(id.id)
|
||||
} else {
|
||||
mappedId = .messageId(id)
|
||||
}
|
||||
if self.messagesRemovedRemotely.contains(mappedId) {
|
||||
result.append(id)
|
||||
}
|
||||
}
|
||||
self.messagesRemovedInteractivelyLock.unlock()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func addIsMessagesDeletedInteractively(ids: [DeletedMessageId]) {
|
||||
if ids.isEmpty {
|
||||
return
|
||||
@ -94,6 +115,16 @@ final class MessagesRemovedContext {
|
||||
self.messagesRemovedInteractively.formUnion(ids)
|
||||
self.messagesRemovedInteractivelyLock.unlock()
|
||||
}
|
||||
|
||||
func addIsMessagesDeletedRemotely(ids: [DeletedMessageId]) {
|
||||
if ids.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
self.messagesRemovedInteractivelyLock.lock()
|
||||
self.messagesRemovedRemotely.formUnion(ids)
|
||||
self.messagesRemovedInteractivelyLock.unlock()
|
||||
}
|
||||
}
|
||||
|
||||
public final class AccountStateManager {
|
||||
@ -297,6 +328,11 @@ public final class AccountStateManager {
|
||||
return self.forceSendPendingStarsReactionPipe.signal()
|
||||
}
|
||||
|
||||
fileprivate let sentScheduledMessageIdsPipe = ValuePipe<Set<MessageId>>()
|
||||
public var sentScheduledMessageIds: Signal<Set<MessageId>, NoError> {
|
||||
return self.sentScheduledMessageIdsPipe.signal()
|
||||
}
|
||||
|
||||
private var updatedWebpageContexts: [MediaId: UpdatedWebpageSubscriberContext] = [:]
|
||||
private var updatedPeersNearbyContext = UpdatedPeersNearbySubscriberContext()
|
||||
private var updatedRevenueBalancesContext = UpdatedRevenueBalancesSubscriberContext()
|
||||
@ -690,6 +726,7 @@ public final class AccountStateManager {
|
||||
|
||||
if let result = result, !result.deletedMessageIds.isEmpty {
|
||||
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
|
||||
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
|
||||
}
|
||||
|
||||
return result
|
||||
@ -835,6 +872,7 @@ public final class AccountStateManager {
|
||||
if let replayedState = replayedState {
|
||||
if !replayedState.deletedMessageIds.isEmpty {
|
||||
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: replayedState.deletedMessageIds)
|
||||
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: replayedState.deletedMessageIds)
|
||||
}
|
||||
|
||||
return (difference, replayedState, false, false)
|
||||
@ -973,6 +1011,7 @@ public final class AccountStateManager {
|
||||
|
||||
if let result = result, !result.deletedMessageIds.isEmpty {
|
||||
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
|
||||
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
|
||||
}
|
||||
|
||||
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||
@ -1080,6 +1119,9 @@ public final class AccountStateManager {
|
||||
if !events.updatedIncomingThreadReadStates.isEmpty || !events.updatedOutgoingThreadReadStates.isEmpty {
|
||||
strongSelf.threadReadStateUpdatesPipe.putNext((events.updatedIncomingThreadReadStates, events.updatedOutgoingThreadReadStates))
|
||||
}
|
||||
if !events.sentScheduledMessageIds.isEmpty {
|
||||
strongSelf.sentScheduledMessageIdsPipe.putNext(events.sentScheduledMessageIds)
|
||||
}
|
||||
if !events.isContactUpdates.isEmpty {
|
||||
strongSelf.addIsContactUpdates(events.isContactUpdates)
|
||||
}
|
||||
@ -1248,6 +1290,7 @@ public final class AccountStateManager {
|
||||
|
||||
if let result = result, !result.deletedMessageIds.isEmpty {
|
||||
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
|
||||
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
|
||||
}
|
||||
|
||||
return result
|
||||
@ -1296,6 +1339,7 @@ public final class AccountStateManager {
|
||||
|
||||
if let result = result, !result.deletedMessageIds.isEmpty {
|
||||
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: result.deletedMessageIds)
|
||||
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: result.deletedMessageIds)
|
||||
}
|
||||
|
||||
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||
@ -1388,6 +1432,7 @@ public final class AccountStateManager {
|
||||
|
||||
if let replayedState = replayedState, !replayedState.deletedMessageIds.isEmpty {
|
||||
messagesRemovedContext.addIsMessagesDeletedInteractively(ids: replayedState.deletedMessageIds)
|
||||
messagesRemovedContext.addIsMessagesDeletedRemotely(ids: replayedState.deletedMessageIds)
|
||||
}
|
||||
|
||||
let deltaTime = CFAbsoluteTimeGetCurrent() - startTime
|
||||
@ -1889,6 +1934,12 @@ public final class AccountStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
public var sentScheduledMessageIds: Signal<Set<MessageId>, NoError> {
|
||||
return self.impl.signalWith { impl, subscriber in
|
||||
return impl.sentScheduledMessageIds.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
|
||||
}
|
||||
}
|
||||
|
||||
func forceSendPendingStarsReaction(messageId: MessageId) {
|
||||
self.impl.with { impl in
|
||||
impl.forceSendPendingStarsReactionPipe.putNext(messageId)
|
||||
@ -2128,6 +2179,10 @@ public final class AccountStateManager {
|
||||
public func synchronouslyIsMessageDeletedInteractively(ids: [EngineMessage.Id]) -> [EngineMessage.Id] {
|
||||
return self.messagesRemovedContext.synchronouslyIsMessageDeletedInteractively(ids: ids)
|
||||
}
|
||||
|
||||
public func synchronouslyIsMessageDeletedRemotely(ids: [EngineMessage.Id]) -> [EngineMessage.Id] {
|
||||
return self.messagesRemovedContext.synchronouslyIsMessageDeletedRemotely(ids: ids)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveNotificationSettings(list: [TelegramPeerNotificationSettings], defaultSettings: MessageNotificationSettings) -> (sound: PeerMessageSound, notify: Bool, displayContents: Bool) {
|
||||
|
||||
@ -178,7 +178,9 @@ private func validatePeerReadState(network: Network, postbox: Postbox, stateMana
|
||||
if case let .idBased(updatedMaxIncomingReadId, _, _, updatedCount, updatedMarkedUnread) = readState {
|
||||
if updatedCount != 0 || updatedMarkedUnread {
|
||||
if localMaxIncomingReadId > updatedMaxIncomingReadId {
|
||||
return .retry
|
||||
if !"".isEmpty {
|
||||
return .retry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1401,6 +1401,10 @@ public extension TelegramEngine {
|
||||
return self.account.stateManager.synchronouslyIsMessageDeletedInteractively(ids: ids)
|
||||
}
|
||||
|
||||
public func synchronouslyIsMessageDeletedRemotely(ids: [EngineMessage.Id]) -> [EngineMessage.Id] {
|
||||
return self.account.stateManager.synchronouslyIsMessageDeletedRemotely(ids: ids)
|
||||
}
|
||||
|
||||
public func synchronouslyLookupCorrelationId(correlationId: Int64) -> EngineMessage.Id? {
|
||||
return self.account.pendingMessageManager.synchronouslyLookupCorrelationId(correlationId: correlationId)
|
||||
}
|
||||
|
||||
@ -490,8 +490,7 @@ private final class StarsContextImpl {
|
||||
private extension StarsContext.State.Transaction {
|
||||
init?(apiTransaction: Api.StarsTransaction, peerId: EnginePeer.Id?, transaction: Transaction) {
|
||||
switch apiTransaction {
|
||||
case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod, giveawayPostId, starGift, floodskipDate, floodskipNumber):
|
||||
let _ = floodskipDate
|
||||
case let .starsTransaction(apiFlags, id, stars, date, transactionPeer, title, description, photo, transactionDate, transactionUrl, _, messageId, extendedMedia, subscriptionPeriod, giveawayPostId, starGift, floodskipNumber):
|
||||
let _ = floodskipNumber
|
||||
|
||||
let parsedPeer: StarsContext.State.Transaction.Peer
|
||||
|
||||
@ -6118,14 +6118,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
|
||||
override public func getStatusNode() -> ASDisplayNode? {
|
||||
if let statusNode = self.mosaicStatusNode {
|
||||
return statusNode
|
||||
}
|
||||
for contentNode in self.contentNodes {
|
||||
if let statusNode = contentNode.getStatusNode() {
|
||||
return statusNode
|
||||
}
|
||||
}
|
||||
if let statusNode = self.mosaicStatusNode {
|
||||
return statusNode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
12
submodules/TelegramUI/Images.xcassets/Media Gallery/VideoRateToast.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Gallery/VideoRateToast.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
73
submodules/TelegramUI/Images.xcassets/Media Gallery/VideoRateToast.imageset/play.pdf
vendored
Normal file
73
submodules/TelegramUI/Images.xcassets/Media Gallery/VideoRateToast.imageset/play.pdf
vendored
Normal 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
|
||||
@ -1272,7 +1272,6 @@ extension ChatControllerImpl {
|
||||
let _ = (strongSelf.shouldDivertMessagesToScheduled(messages: transformedMessages)
|
||||
|> deliverOnMainQueue).start(next: { shouldDivert in
|
||||
let signal: Signal<[MessageId?], NoError>
|
||||
var stayInThisChat = false
|
||||
var shouldOpenScheduledMessages = false
|
||||
if forwardSourcePeerIds.count > 1 {
|
||||
var forwardedMessages = forwardedMessages
|
||||
@ -1302,7 +1301,6 @@ extension ChatControllerImpl {
|
||||
}
|
||||
return ids
|
||||
}
|
||||
stayInThisChat = true
|
||||
} else {
|
||||
var transformedMessages = transformedMessages
|
||||
if shouldDivert {
|
||||
@ -1334,56 +1332,6 @@ extension ChatControllerImpl {
|
||||
strongSelf.layoutActionOnViewTransitionAction = nil
|
||||
layoutActionOnViewTransitionAction()
|
||||
}
|
||||
|
||||
if stayInThisChat {
|
||||
strongSelf.dismissAllUndoControllers()
|
||||
|
||||
//TODO:localize
|
||||
strongSelf.present(
|
||||
UndoOverlayController(
|
||||
presentationData: strongSelf.presentationData,
|
||||
content: .info(
|
||||
title: "Improving video...",
|
||||
text: "The video will be published after it's optimized for the bese viewing experience.",
|
||||
timeout: 8.0,
|
||||
customUndoText: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
} else {
|
||||
strongSelf.openScheduledMessages(force: true, completion: { c in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
c.dismissAllUndoControllers()
|
||||
|
||||
//TODO:localize
|
||||
c.present(
|
||||
UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .info(
|
||||
title: "Improving video...",
|
||||
text: "The video will be published after it's optimized for the bese viewing experience.",
|
||||
timeout: 8.0,
|
||||
customUndoText: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -4659,7 +4607,7 @@ extension ChatControllerImpl {
|
||||
title: "Improving video...",
|
||||
text: "The video will be published after it's optimized for the bese viewing experience.",
|
||||
customUndoText: nil,
|
||||
timeout: 6.0
|
||||
timeout: 3.5
|
||||
),
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
@ -4993,6 +4941,20 @@ extension ChatControllerImpl {
|
||||
}), in: .current)
|
||||
})
|
||||
|
||||
if case .scheduledMessages = self.subject {
|
||||
self.postedScheduledMessagesEventsDisposable = (self.context.account.stateManager.sentScheduledMessageIds
|
||||
|> deliverOnMainQueue).start(next: { [weak self] ids in
|
||||
guard let self, let peerId = self.chatLocation.peerId else {
|
||||
return
|
||||
}
|
||||
let filteredIds = Array(ids).filter({ $0.peerId == peerId })
|
||||
if filteredIds.isEmpty {
|
||||
return
|
||||
}
|
||||
self.displayPostedScheduledMessagesToast(ids: filteredIds)
|
||||
})
|
||||
}
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,14 +106,9 @@ extension ChatControllerImpl {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
/*if "".isEmpty, let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
|
||||
for media in message.media {
|
||||
if let file = media as? TelegramMediaFile, file.isVideo, !file.isInstantVideo, !file.isAnimated {
|
||||
tip = .videoProcessing
|
||||
}
|
||||
}
|
||||
}*/
|
||||
if messages.contains(where: { $0.pendingProcessingAttribute != nil }) {
|
||||
tip = .videoProcessing
|
||||
}
|
||||
|
||||
if actions.tip == nil {
|
||||
actions.tip = tip
|
||||
|
||||
101
submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift
Normal file
101
submodules/TelegramUI/Sources/Chat/ChatControllerToasts.swift
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -654,6 +654,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
var layoutActionOnViewTransitionAction: (() -> Void)?
|
||||
|
||||
var lastPostedScheduledMessagesToastTimestamp: Double = 0.0
|
||||
var postedScheduledMessagesEventsDisposable: Disposable?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
chatLocation: ChatLocation,
|
||||
@ -7194,6 +7197,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.recorderDataDisposable.dispose()
|
||||
self.displaySendWhenOnlineTipDisposable.dispose()
|
||||
self.networkSpeedEventsDisposable?.dispose()
|
||||
self.postedScheduledMessagesEventsDisposable?.dispose()
|
||||
}
|
||||
deallocate()
|
||||
}
|
||||
@ -9136,52 +9140,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
func shouldDivertMessagesToScheduled(targetPeer: EnginePeer? = nil, messages: [EnqueueMessage]) -> Signal<Bool, NoError> {
|
||||
return .single(false)
|
||||
/*guard let peer = targetPeer?._asPeer() ?? self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
|
||||
} else {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
//TODO:release
|
||||
if !"".isEmpty {
|
||||
return .single(false)
|
||||
}
|
||||
|
||||
var forwardMessageIds: [EngineMessage.Id] = []
|
||||
|
||||
for message in messages {
|
||||
if case let .message(_, _, _, mediaReference, _, _, _, _, _, _) = message, let media = mediaReference?.media {
|
||||
if let file = media as? TelegramMediaFile, file.isVideo && !file.isInstantVideo && !file.isAnimated {
|
||||
return .single(true)
|
||||
}
|
||||
} else if case let .forward(sourceId, _, _, _, _) = message {
|
||||
forwardMessageIds.append(sourceId)
|
||||
}
|
||||
}
|
||||
|
||||
if forwardMessageIds.isEmpty {
|
||||
return .single(false)
|
||||
} else {
|
||||
return self.context.engine.data.get(
|
||||
EngineDataList(forwardMessageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:)))
|
||||
)
|
||||
|> map { messages -> Bool in
|
||||
for message in messages {
|
||||
guard let message else {
|
||||
continue
|
||||
}
|
||||
for media in message.media {
|
||||
if let file = media as? TelegramMediaFile, file.isVideo && !file.isInstantVideo && !file.isAnimated {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
func sendMessages(_ messages: [EnqueueMessage], media: Bool = false, commit: Bool = false) {
|
||||
@ -9240,30 +9198,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
layoutActionOnViewTransitionAction()
|
||||
}
|
||||
|
||||
self.openScheduledMessages(force: true, completion: { [weak self] c in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
c.dismissAllUndoControllers()
|
||||
|
||||
//TODO:localize
|
||||
c.present(
|
||||
UndoOverlayController(
|
||||
presentationData: self.presentationData,
|
||||
content: .info(
|
||||
title: "Improving video...",
|
||||
text: "The video will be published after it's optimized for the bese viewing experience.",
|
||||
timeout: 8.0,
|
||||
customUndoText: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
self.openScheduledMessages(force: true, completion: { _ in
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@ -10381,10 +10316,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
if let itemNode = latestNode, let statusNode = itemNode.getStatusNode() {
|
||||
let bounds = statusNode.view.convert(statusNode.view.bounds, to: self.chatDisplayNode.view)
|
||||
let location = CGPoint(x: bounds.midX, y: bounds.minY - 11.0)
|
||||
let location = CGPoint(x: bounds.midX, y: bounds.minY - 8.0)
|
||||
|
||||
//TODO:localize
|
||||
let tooltipController = TooltipController(content: .text("Processing video may take a few minutes."), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: true, timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
|
||||
let tooltipController = TooltipController(content: .text("Processing video may take a few minutes."), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, balancedTextLayout: true, isBlurred: true, timeout: 3.5, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true)
|
||||
self.checksTooltipController = tooltipController
|
||||
tooltipController.dismissed = { [weak self, weak tooltipController] _ in
|
||||
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.checksTooltipController === tooltipController {
|
||||
|
||||
@ -257,24 +257,6 @@ extension ChatControllerImpl {
|
||||
})
|
||||
|
||||
if displayConvertingTooltip {
|
||||
//TODO:localize
|
||||
strongSelf.present(
|
||||
UndoOverlayController(
|
||||
presentationData: strongSelf.presentationData,
|
||||
content: .info(
|
||||
title: "Improving video...",
|
||||
text: "The video will be published after it's optimized for the bese viewing experience.",
|
||||
timeout: 8.0,
|
||||
customUndoText: nil
|
||||
),
|
||||
elevatedLayout: false,
|
||||
position: .top,
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
),
|
||||
in: .current
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -3467,6 +3467,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
}
|
||||
}
|
||||
for id in self.context.engine.messages.synchronouslyIsMessageDeletedInteractively(ids: testIds) {
|
||||
if id.namespace == Namespaces.Message.ScheduledCloud {
|
||||
continue
|
||||
}
|
||||
inner: for (stableId, listId) in maybeRemovedInteractivelyMessageIds {
|
||||
if listId == id {
|
||||
expiredMessageStableIds.insert(stableId)
|
||||
|
||||
@ -1137,9 +1137,29 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
if data.messageActions.options.contains(.sendScheduledNow) {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.ScheduledMessages_SendNow, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
|
||||
f(.dismissWithoutContent)
|
||||
}, action: { c, _ in
|
||||
if messages.contains(where: { $0.pendingProcessingAttribute != nil }) {
|
||||
c?.dismiss(completion: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
//TODO:localize
|
||||
controllerInteraction.presentController(standardTextAlertController(
|
||||
theme: AlertControllerTheme(presentationData: presentationData),
|
||||
title: "Wait!",
|
||||
text: "This video hasn't been converted and optimized yet. If you send it now, the viewers of the video may experience slow download speed.",
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultAction, title: "Send Anyway", action: {
|
||||
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
|
||||
}),
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
|
||||
],
|
||||
actionLayout: .vertical
|
||||
), nil)
|
||||
})
|
||||
} else {
|
||||
c?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||
controllerInteraction.sendScheduledMessagesNow(selectAll ? messages.map { $0.id } : [message.id])
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
@ -2210,8 +2230,10 @@ func chatAvailableMessageActionsImpl(engine: TelegramEngine, accountPeerId: Peer
|
||||
}
|
||||
if id.namespace == Namespaces.Message.ScheduledCloud {
|
||||
optionsMap[id]!.insert(.sendScheduledNow)
|
||||
if canEditMessage(accountPeerId: accountPeerId, limitsConfiguration: limitsConfiguration, message: message, reschedule: true) {
|
||||
optionsMap[id]!.insert(.editScheduledTime)
|
||||
if message.pendingProcessingAttribute == nil {
|
||||
if canEditMessage(accountPeerId: accountPeerId, limitsConfiguration: limitsConfiguration, message: message, reschedule: true) {
|
||||
optionsMap[id]!.insert(.editScheduledTime)
|
||||
}
|
||||
}
|
||||
if let peer = getPeer(id.peerId), let channel = peer as? TelegramChannel {
|
||||
if !message.flags.contains(.Incoming) {
|
||||
|
||||
@ -1572,6 +1572,15 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod
|
||||
}
|
||||
|
||||
func videoQualityState() -> (current: Int, preferred: UniversalVideoContentVideoQuality, available: [Int])? {
|
||||
if self.playerAvailableLevels.isEmpty {
|
||||
if let qualitySet = HLSQualitySet(baseFile: self.fileReference), let minQualityFile = HLSVideoContent.minimizedHLSQuality(file: self.fileReference)?.file {
|
||||
let sortedFiles = qualitySet.qualityFiles.sorted(by: { $0.key > $1.key })
|
||||
if let minQuality = sortedFiles.first(where: { $0.value.media.fileId == minQualityFile.media.fileId }) {
|
||||
return (minQuality.key, .auto, sortedFiles.map(\.key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard let playerCurrentLevelIndex = self.playerCurrentLevelIndex else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ swift_library(
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/PhotoResources",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -49,6 +49,7 @@ public enum UndoOverlayContent {
|
||||
case premiumPaywall(title: String?, text: String, customUndoText: String?, timeout: Double?, linkAction: ((String) -> Void)?)
|
||||
case peers(context: AccountContext, peers: [EnginePeer], title: String?, text: String, customUndoText: String?)
|
||||
case messageTagged(context: AccountContext, isSingleMessage: Bool, customEmoji: TelegramMediaFile, isBuiltinReaction: Bool, customUndoText: String?)
|
||||
case media(context: AccountContext, file: FileMediaReference, title: String?, text: String, undoText: String?, customAction: (() -> Void)?)
|
||||
}
|
||||
|
||||
public enum UndoOverlayAction {
|
||||
|
||||
@ -23,6 +23,7 @@ import TextNodeWithEntities
|
||||
import BundleIconComponent
|
||||
import AnimatedTextComponent
|
||||
import ComponentDisplayAdapters
|
||||
import PhotoResources
|
||||
|
||||
final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
private let presentationData: PresentationData
|
||||
@ -42,6 +43,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
private var slotMachineNode: SlotMachineAnimationNode?
|
||||
private var stillStickerNode: TransformImageNode?
|
||||
private var stickerImageSize: CGSize?
|
||||
private var stickerSourceSize: CGSize?
|
||||
private var stickerOffset: CGPoint?
|
||||
private var emojiStatus: ComponentView<Empty>?
|
||||
private let titleNode: ImmediateTextNode
|
||||
@ -1297,6 +1299,58 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
} else {
|
||||
displayUndo = false
|
||||
}
|
||||
case let .media(context, file, title, text, customUndoText, _):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = nil
|
||||
|
||||
let stillStickerNode = TransformImageNode()
|
||||
|
||||
self.stillStickerNode = stillStickerNode
|
||||
|
||||
var updatedImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||
var updatedFetchSignal: Signal<Never, EngineMediaResource.Fetch.Error>?
|
||||
|
||||
updatedImageSignal = mediaGridMessageVideo(postbox: context.account.postbox, userLocation: .other, videoReference: file, onlyFullSize: false, useLargeThumbnail: false, autoFetchFullSizeThumbnail: false)
|
||||
updatedFetchSignal = nil
|
||||
self.stickerImageSize = CGSize(width: 30.0, height: 30.0)
|
||||
self.stickerSourceSize = file.media.dimensions?.cgSize
|
||||
|
||||
if let title = title {
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(14.0), textColor: .white)
|
||||
} else {
|
||||
self.titleNode.attributedText = nil
|
||||
}
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let link = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: undoTextColor)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
|
||||
return ("URL", contents)
|
||||
}), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 5
|
||||
|
||||
if text.contains("](") {
|
||||
isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
if let customUndoText = customUndoText {
|
||||
undoText = customUndoText
|
||||
displayUndo = true
|
||||
} else {
|
||||
displayUndo = false
|
||||
}
|
||||
self.originalRemainingSeconds = isUserInteractionEnabled ? 5 : 3
|
||||
|
||||
if let updatedFetchSignal = updatedFetchSignal {
|
||||
self.fetchResourceDisposable = updatedFetchSignal.start()
|
||||
}
|
||||
|
||||
if let updatedImageSignal = updatedImageSignal {
|
||||
stillStickerNode.setSignal(updatedImageSignal)
|
||||
}
|
||||
}
|
||||
|
||||
self.remainingSeconds = self.originalRemainingSeconds
|
||||
@ -1340,7 +1394,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
} else {
|
||||
self.isUserInteractionEnabled = false
|
||||
}
|
||||
case .sticker, .customEmoji:
|
||||
case .sticker, .customEmoji, .media:
|
||||
self.isUserInteractionEnabled = displayUndo
|
||||
case .dice:
|
||||
self.panelWrapperNode.clipsToBounds = true
|
||||
@ -1468,6 +1522,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
} else {
|
||||
let _ = self.action(.undo)
|
||||
}
|
||||
case let .media(_, _, _, _, _, customAction):
|
||||
if let customAction = customAction {
|
||||
customAction()
|
||||
} else {
|
||||
let _ = self.action(.undo)
|
||||
}
|
||||
default:
|
||||
let _ = self.action(.undo)
|
||||
}
|
||||
@ -1775,7 +1835,16 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
|
||||
if let stillStickerNode = self.stillStickerNode {
|
||||
let makeImageLayout = stillStickerNode.asyncLayout()
|
||||
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: stickerImageSize, boundingSize: stickerImageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
|
||||
var radius: CGFloat = 0.0
|
||||
if case .media = self.content {
|
||||
radius = 6.0
|
||||
}
|
||||
var stickerImageSourceSize = stickerImageSize
|
||||
if let stickerSourceSize = self.stickerSourceSize {
|
||||
stickerImageSourceSize = stickerSourceSize.aspectFilled(stickerImageSourceSize)
|
||||
}
|
||||
let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: stickerImageSourceSize, boundingSize: stickerImageSize, intrinsicInsets: UIEdgeInsets()))
|
||||
let _ = imageApply()
|
||||
transition.updateFrame(node: stillStickerNode, frame: iconFrame)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user