mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Voice Chat Improvements
This commit is contained in:
parent
1fbceb81d1
commit
74d1896e04
@ -5824,6 +5824,8 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.StopRecordingTitle" = "Stop Recording?";
|
||||
"VoiceChat.StopRecordingStop" = "Stop";
|
||||
|
||||
"VoiceChat.RecordingSaved" = "Audio stream saved to Saved Messages.";
|
||||
|
||||
"VoiceChat.StatusMutedForYou" = "muted for you";
|
||||
"VoiceChat.StatusMutedYou" = "put you on mute";
|
||||
|
||||
@ -6187,14 +6189,13 @@ Sorry for the inconvenience.";
|
||||
|
||||
"VoiceChat.DisplayAs" = "Display Me As...";
|
||||
"VoiceChat.DisplayAsInfo" = "Choose whether you want to be displayed as your personal account or as your channel.";
|
||||
"VoiceChat.DisplayAsSuccess" = "Members of this voice chat will now see your as **%@**.";
|
||||
"VoiceChat.PersonalAccount" = "personal account";
|
||||
"VoiceChat.EditTitle" = "Edit Voice Chat Title";
|
||||
"VoiceChat.EditPermissions" = "Edit Permissions";
|
||||
|
||||
"VoiceChat.OpenChannel" = "Open Channel";
|
||||
|
||||
"VoiceChat.DisplayAsSuccess" = "Members of this voice chat will now see your as **%@**.";
|
||||
|
||||
"VoiceChat.EditTitleTitle" = "Voice Chat Title";
|
||||
"VoiceChat.EditTitleText" = "Edit a title of this voice chat.";
|
||||
"VoiceChat.EditTitleSuccess" = "Voice chat title changed to **%@**.";
|
||||
|
@ -223,7 +223,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0)
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
transition.updateFrameAdditive(node: statusNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin + verticalSpacing + textSize.height), size: textSize))
|
||||
transition.updateFrameAdditive(node: statusNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin + verticalSpacing + textSize.height), size: statusSize))
|
||||
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: textFrame.maxX + badgeSpacing, y: floor((size.height - badgeSize.height) / 2.0)), size: badgeSize)
|
||||
transition.updateFrame(node: self.badgeBackgroundNode, frame: badgeFrame)
|
||||
|
@ -212,7 +212,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
var feedbackTap: (() -> Void)?
|
||||
|
||||
var blurBackground = true
|
||||
if case let .extracted(extractedSource) = source, !extractedSource.blurBackground {
|
||||
if case .reference = source {
|
||||
blurBackground = false
|
||||
} else if case let .extracted(extractedSource) = source, !extractedSource.blurBackground {
|
||||
blurBackground = false
|
||||
}
|
||||
self.blurBackground = blurBackground
|
||||
@ -441,7 +443,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}))
|
||||
|
||||
switch source {
|
||||
case .extracted:
|
||||
case .reference, .extracted:
|
||||
self.contentReady.set(.single(true))
|
||||
case let .controller(source):
|
||||
self.contentReady.set(source.controller.ready.get())
|
||||
@ -478,6 +480,15 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
private func initializeContent() {
|
||||
switch self.source {
|
||||
case let .reference(source):
|
||||
let transitionInfo = source.transitionInfo()
|
||||
if let transitionInfo = transitionInfo {
|
||||
let referenceNode = transitionInfo.referenceNode
|
||||
self.contentContainerNode.contentNode = .reference(node: referenceNode)
|
||||
self.contentAreaInScreenSpace = transitionInfo.contentAreaInScreenSpace
|
||||
let projectedFrame = convertFrame(referenceNode.view.bounds, from: referenceNode.view, to: self.view)
|
||||
self.originalProjectedContentViewFrame = (projectedFrame, projectedFrame)
|
||||
}
|
||||
case let .extracted(source):
|
||||
let takenViewInfo = source.takeView()
|
||||
|
||||
@ -553,6 +564,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
self.hapticFeedback.impact()
|
||||
|
||||
switch self.source {
|
||||
case .reference:
|
||||
break
|
||||
case .extracted:
|
||||
if let contentAreaInScreenSpace = self.contentAreaInScreenSpace, let maybeContentNode = self.contentContainerNode.contentNode, case .extracted = maybeContentNode {
|
||||
var updatedContentAreaInScreenSpace = contentAreaInScreenSpace
|
||||
@ -619,6 +632,22 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
if let contentNode = self.contentContainerNode.contentNode {
|
||||
switch contentNode {
|
||||
case let .reference(referenceNode):
|
||||
let springDuration: Double = 0.42 * animationDurationFactor
|
||||
let springDamping: CGFloat = 104.0
|
||||
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
|
||||
self.actionsContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||
|
||||
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
||||
let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view)
|
||||
|
||||
let localContentSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.contentContainerNode.view.superview)
|
||||
|
||||
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
let contentContainerOffset = CGPoint(x: localContentSourceFrame.center.x - self.contentContainerNode.frame.center.x, y: localContentSourceFrame.center.y - self.contentContainerNode.frame.center.y)
|
||||
self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
}
|
||||
case let .extracted(extracted, keepInPlace):
|
||||
let springDuration: Double = 0.42 * animationDurationFactor
|
||||
let springDamping: CGFloat = 104.0
|
||||
@ -701,6 +730,74 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
var result = initialResult
|
||||
|
||||
switch self.source {
|
||||
case let .reference(source):
|
||||
guard let maybeContentNode = self.contentContainerNode.contentNode, case let .reference(referenceNode) = maybeContentNode else {
|
||||
return
|
||||
}
|
||||
|
||||
let transitionInfo = source.transitionInfo()
|
||||
if transitionInfo == nil {
|
||||
result = .dismissWithoutContent
|
||||
}
|
||||
|
||||
switch result {
|
||||
case let .custom(value):
|
||||
switch value {
|
||||
case let .animated(duration, curve):
|
||||
transitionDuration = duration
|
||||
transitionCurve = curve
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
self.isAnimatingOut = true
|
||||
|
||||
self.scrollNode.view.setContentOffset(self.scrollNode.view.contentOffset, animated: false)
|
||||
|
||||
var completedEffect = false
|
||||
var completedContentNode = false
|
||||
var completedActionsNode = false
|
||||
|
||||
if let transitionInfo = transitionInfo, let parentSupernode = referenceNode.supernode {
|
||||
self.originalProjectedContentViewFrame = (convertFrame(referenceNode.frame, from: parentSupernode.view, to: self.view), convertFrame(referenceNode.bounds, from: referenceNode.view, to: self.view))
|
||||
|
||||
var updatedContentAreaInScreenSpace = transitionInfo.contentAreaInScreenSpace
|
||||
updatedContentAreaInScreenSpace.origin.x = 0.0
|
||||
updatedContentAreaInScreenSpace.size.width = self.bounds.width
|
||||
|
||||
self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: updatedContentAreaInScreenSpace, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
|
||||
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
if !self.dimNode.isHidden {
|
||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
|
||||
} else {
|
||||
self.withoutBlurDimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: transitionDuration * animationDurationFactor, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15 * animationDurationFactor, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.actionsContainerNode.layer.animateScale(from: 1.0, to: 0.1, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
|
||||
|
||||
let animateOutToItem: Bool
|
||||
switch result {
|
||||
case .default, .custom:
|
||||
animateOutToItem = true
|
||||
case .dismissWithoutContent:
|
||||
animateOutToItem = false
|
||||
}
|
||||
|
||||
if animateOutToItem, let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
||||
let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view)
|
||||
let localContentSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.contentContainerNode.view.superview)
|
||||
|
||||
self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y), duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true)
|
||||
}
|
||||
case let .extracted(source):
|
||||
guard let maybeContentNode = self.contentContainerNode.contentNode, case let .extracted(contentParentNode, keepInPlace) = maybeContentNode else {
|
||||
return
|
||||
@ -1111,7 +1208,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
switch layout.metrics.widthClass {
|
||||
case .compact:
|
||||
if case let .extracted(extractedSource) = self.source, !extractedSource.blurBackground {
|
||||
if case .reference = self.source {
|
||||
} else if case let .extracted(extractedSource) = self.source, !extractedSource.blurBackground {
|
||||
} else if self.effectView.superview == nil {
|
||||
self.view.insertSubview(self.effectView, at: 0)
|
||||
if #available(iOS 10.0, *) {
|
||||
@ -1126,7 +1224,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
self.dimNode.isHidden = false
|
||||
self.withoutBlurDimNode.isHidden = true
|
||||
case .regular:
|
||||
if case let .extracted(extractedSource) = self.source, !extractedSource.blurBackground {
|
||||
if case .reference = self.source {
|
||||
} else if case let .extracted(extractedSource) = self.source, !extractedSource.blurBackground {
|
||||
} else if self.effectView.superview != nil {
|
||||
self.effectView.removeFromSuperview()
|
||||
self.withoutBlurDimNode.alpha = 1.0
|
||||
@ -1147,6 +1246,83 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
if let contentNode = self.contentContainerNode.contentNode {
|
||||
switch contentNode {
|
||||
case let .reference(referenceNode):
|
||||
let contentActionsSpacing: CGFloat = 16.0
|
||||
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
||||
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
|
||||
let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
|
||||
|
||||
let actionsSize = self.actionsContainerNode.updateLayout(widthClass: layout.metrics.widthClass, constrainedWidth: layout.size.width - actionsSideInset * 2.0, transition: actionsContainerTransition)
|
||||
self.actionsContainerNode.updateSize(containerSize: actionsSize, contentSize: actionsSize)
|
||||
let contentSize = originalProjectedContentViewFrame.1.size
|
||||
self.contentContainerNode.updateLayout(size: contentSize, scaledSize: contentSize, transition: transition)
|
||||
|
||||
let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height)
|
||||
|
||||
let originalActionsY = min(originalProjectedContentViewFrame.1.maxY + contentActionsSpacing, maximumActionsFrameOrigin)
|
||||
let preferredActionsX = originalProjectedContentViewFrame.1.minX
|
||||
|
||||
var originalActionsFrame = CGRect(origin: CGPoint(x: max(actionsSideInset, min(layout.size.width - actionsSize.width - actionsSideInset, preferredActionsX)), y: originalActionsY), size: actionsSize)
|
||||
let originalContentX: CGFloat = originalProjectedContentViewFrame.1.minX
|
||||
let originalContentY = originalProjectedContentViewFrame.1.minY
|
||||
|
||||
var originalContentFrame = CGRect(origin: CGPoint(x: originalContentX, y: originalContentY), size: originalProjectedContentViewFrame.1.size)
|
||||
let topEdge = max(contentTopInset, self.contentAreaInScreenSpace?.minY ?? 0.0)
|
||||
if originalContentFrame.minY < topEdge {
|
||||
let requiredOffset = topEdge - originalContentFrame.minY
|
||||
let availableOffset = max(0.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - originalActionsFrame.maxY)
|
||||
let offset = min(requiredOffset, availableOffset)
|
||||
originalActionsFrame = originalActionsFrame.offsetBy(dx: 0.0, dy: offset)
|
||||
originalContentFrame = originalContentFrame.offsetBy(dx: 0.0, dy: offset)
|
||||
}
|
||||
|
||||
var contentHeight = max(layout.size.height, max(layout.size.height, originalActionsFrame.maxY + actionsBottomInset) - originalContentFrame.minY + contentTopInset)
|
||||
|
||||
var overflowOffset: CGFloat
|
||||
var contentContainerFrame: CGRect
|
||||
// if keepInPlace {
|
||||
overflowOffset = min(0.0, originalActionsFrame.minY - contentTopInset)
|
||||
let contentParentNode = referenceNode
|
||||
contentContainerFrame = originalContentFrame
|
||||
if !overflowOffset.isZero {
|
||||
let offsetDelta = contentParentNode.frame.height + 4.0
|
||||
overflowOffset += offsetDelta
|
||||
overflowOffset = min(0.0, overflowOffset)
|
||||
|
||||
originalActionsFrame.origin.x -= contentParentNode.frame.width + 14.0
|
||||
originalActionsFrame.origin.x = max(actionsSideInset, originalActionsFrame.origin.x)
|
||||
//originalActionsFrame.origin.y += contentParentNode.contentRect.height
|
||||
if originalActionsFrame.minX < contentContainerFrame.minX {
|
||||
contentContainerFrame.origin.x = min(originalActionsFrame.maxX + 14.0, layout.size.width - actionsSideInset)
|
||||
}
|
||||
originalActionsFrame.origin.y += offsetDelta
|
||||
if originalActionsFrame.maxY < originalContentFrame.maxY {
|
||||
originalActionsFrame.origin.y += contentParentNode.frame.height
|
||||
originalActionsFrame.origin.y = min(originalActionsFrame.origin.y, layout.size.height - originalActionsFrame.height - actionsBottomInset)
|
||||
}
|
||||
contentHeight -= offsetDelta
|
||||
}
|
||||
// } else {
|
||||
// overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset)
|
||||
// contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -overflowOffset - contentParentNode.contentRect.minY)
|
||||
// }
|
||||
|
||||
let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight)
|
||||
if self.scrollNode.view.contentSize != scrollContentSize {
|
||||
self.scrollNode.view.contentSize = scrollContentSize
|
||||
}
|
||||
self.actionsContainerNode.panSelectionGestureEnabled = scrollContentSize.height <= layout.size.height
|
||||
|
||||
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
|
||||
actionsContainerTransition.updateFrame(node: self.actionsContainerNode, frame: originalActionsFrame.offsetBy(dx: 0.0, dy: -overflowOffset))
|
||||
|
||||
if isInitialLayout {
|
||||
let currentContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view)
|
||||
if overflowOffset < 0.0 {
|
||||
transition.animateOffsetAdditive(node: self.scrollNode, offset: currentContainerFrame.minY - previousContainerFrame.minY)
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .extracted(contentParentNode, keepInPlace):
|
||||
let contentActionsSpacing: CGFloat = keepInPlace ? 16.0 : 8.0
|
||||
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
||||
@ -1198,7 +1374,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
if keepInPlace {
|
||||
overflowOffset = min(0.0, originalActionsFrame.minY - contentTopInset)
|
||||
contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -contentParentNode.contentRect.minY)
|
||||
if keepInPlace && !overflowOffset.isZero {
|
||||
if !overflowOffset.isZero {
|
||||
let offsetDelta = contentParentNode.contentRect.height + 4.0
|
||||
overflowOffset += offsetDelta
|
||||
overflowOffset = min(0.0, overflowOffset)
|
||||
@ -1452,6 +1628,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
let mappedPoint = self.view.convert(point, to: self.scrollNode.view)
|
||||
if let maybeContentNode = self.contentContainerNode.contentNode {
|
||||
switch maybeContentNode {
|
||||
case .reference:
|
||||
break
|
||||
case let .extracted(contentParentNode, _):
|
||||
if case let .extracted(source) = self.source {
|
||||
if !source.ignoreContentTouches {
|
||||
@ -1493,6 +1671,20 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}
|
||||
}
|
||||
|
||||
public final class ContextControllerReferenceViewInfo {
|
||||
public let referenceNode: ContextReferenceContentNode
|
||||
public let contentAreaInScreenSpace: CGRect
|
||||
|
||||
public init(referenceNode: ContextReferenceContentNode, contentAreaInScreenSpace: CGRect) {
|
||||
self.referenceNode = referenceNode
|
||||
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ContextReferenceContentSource: class {
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo?
|
||||
}
|
||||
|
||||
public final class ContextControllerTakeViewInfo {
|
||||
public let contentContainingNode: ContextExtractedContentContainingNode
|
||||
public let contentAreaInScreenSpace: CGRect
|
||||
@ -1503,16 +1695,6 @@ public final class ContextControllerTakeViewInfo {
|
||||
}
|
||||
}
|
||||
|
||||
public final class ContextControllerTakeControllerInfo {
|
||||
public let contentAreaInScreenSpace: CGRect
|
||||
public let sourceNode: () -> (ASDisplayNode, CGRect)?
|
||||
|
||||
public init(contentAreaInScreenSpace: CGRect, sourceNode: @escaping () -> (ASDisplayNode, CGRect)?) {
|
||||
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
}
|
||||
|
||||
public final class ContextControllerPutBackViewInfo {
|
||||
public let contentAreaInScreenSpace: CGRect
|
||||
|
||||
@ -1537,6 +1719,16 @@ public extension ContextExtractedContentSource {
|
||||
}
|
||||
}
|
||||
|
||||
public final class ContextControllerTakeControllerInfo {
|
||||
public let contentAreaInScreenSpace: CGRect
|
||||
public let sourceNode: () -> (ASDisplayNode, CGRect)?
|
||||
|
||||
public init(contentAreaInScreenSpace: CGRect, sourceNode: @escaping () -> (ASDisplayNode, CGRect)?) {
|
||||
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ContextControllerContentSource: class {
|
||||
var controller: ViewController { get }
|
||||
var navigationController: NavigationController? { get }
|
||||
@ -1548,6 +1740,7 @@ public protocol ContextControllerContentSource: class {
|
||||
}
|
||||
|
||||
public enum ContextContentSource {
|
||||
case reference(ContextReferenceContentSource)
|
||||
case extracted(ContextExtractedContentSource)
|
||||
case controller(ContextControllerContentSource)
|
||||
}
|
||||
@ -1576,6 +1769,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
}
|
||||
|
||||
public var reactionSelected: ((ReactionContextItem.Reaction) -> Void)?
|
||||
public var dismissed: (() -> Void)?
|
||||
|
||||
private var shouldBeDismissedDisposable: Disposable?
|
||||
|
||||
@ -1591,8 +1785,13 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
if case let .extracted(extractedSource) = source {
|
||||
if !extractedSource.blurBackground {
|
||||
switch source {
|
||||
case .reference:
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
case let .extracted(extractedSource):
|
||||
if extractedSource.blurBackground {
|
||||
self.statusBar.statusBarStyle = .Hide
|
||||
} else {
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
}
|
||||
self.shouldBeDismissedDisposable = (extractedSource.shouldBeDismissed
|
||||
@ -1604,9 +1803,10 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
}
|
||||
strongSelf.dismiss(result: .default, completion: {})
|
||||
})
|
||||
} else {
|
||||
case .controller:
|
||||
self.statusBar.statusBarStyle = .Hide
|
||||
}
|
||||
|
||||
self.lockOrientation = true
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
}
|
||||
@ -1691,6 +1891,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
completion?()
|
||||
})
|
||||
self.dismissed?()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1705,6 +1906,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
completion?()
|
||||
})
|
||||
self.dismissed?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ public final class ContextContentContainerNode: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
switch contentNode {
|
||||
case .reference:
|
||||
break
|
||||
case .extracted:
|
||||
break
|
||||
case let .controller(controller):
|
||||
|
@ -1,6 +1,9 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
public final class ContextReferenceContentNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public final class ContextExtractedContentContainingNode: ASDisplayNode {
|
||||
public let contentNode: ContextExtractedContentNode
|
||||
public var contentRect: CGRect = CGRect()
|
||||
@ -58,6 +61,7 @@ public final class ContextControllerContentNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public enum ContextContentNode {
|
||||
case reference(node: ContextReferenceContentNode)
|
||||
case extracted(node: ContextExtractedContentContainingNode, keepInPlace: Bool)
|
||||
case controller(ContextControllerContentNode)
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ private struct ChannelBannedMemberControllerState: Equatable {
|
||||
var updating: Bool = false
|
||||
}
|
||||
|
||||
private func completeRights(_ flags: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags {
|
||||
func completeRights(_ flags: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags {
|
||||
var result = flags
|
||||
result.remove(.banReadMessages)
|
||||
if result.contains(.banSendGifs) {
|
||||
|
@ -417,26 +417,6 @@ func groupPermissionDependencies(_ right: TelegramChatBannedRightsFlags) -> Tele
|
||||
}
|
||||
}
|
||||
|
||||
private func completeRights(_ flags: TelegramChatBannedRightsFlags) -> TelegramChatBannedRightsFlags {
|
||||
var result = flags
|
||||
result.remove(.banReadMessages)
|
||||
if result.contains(.banSendGifs) {
|
||||
result.insert(.banSendStickers)
|
||||
result.insert(.banSendGifs)
|
||||
result.insert(.banSendGames)
|
||||
} else {
|
||||
result.remove(.banSendStickers)
|
||||
result.remove(.banSendGifs)
|
||||
result.remove(.banSendGames)
|
||||
}
|
||||
if result.contains(.banEmbedLinks) {
|
||||
result.insert(.banSendInline)
|
||||
} else {
|
||||
result.remove(.banSendInline)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func channelPermissionsControllerEntries(context: AccountContext, presentationData: PresentationData, view: PeerView, state: ChannelPermissionsControllerState, participants: [RenderedChannelParticipant]?) -> [ChannelPermissionsEntry] {
|
||||
var entries: [ChannelPermissionsEntry] = []
|
||||
|
||||
|
@ -33,7 +33,7 @@ public enum PeerReportOption {
|
||||
case other
|
||||
}
|
||||
|
||||
public func presentPeerReportOptions(context: AccountContext, parent: ViewController, contextController: ContextController?, subject: PeerReportSubject, options: [PeerReportOption] = [.spam, .violence, .pornography, .childAbuse, .copyright, .other], completion: @escaping (ReportReason?, Bool) -> Void) {
|
||||
public func presentPeerReportOptions(context: AccountContext, parent: ViewController, contextController: ContextController?, backAction: ((ContextController) -> Void)? = nil, subject: PeerReportSubject, options: [PeerReportOption] = [.spam, .violence, .pornography, .childAbuse, .copyright, .other], passthrough: Bool = false, completion: @escaping (ReportReason?, Bool) -> Void) {
|
||||
if let contextController = contextController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
var items: [ContextMenuItem] = []
|
||||
@ -86,27 +86,31 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
||||
}
|
||||
|
||||
let action: (String) -> Void = { message in
|
||||
if passthrough {
|
||||
completion(reportReason, true)
|
||||
} else {
|
||||
switch subject {
|
||||
case let .peer(peerId):
|
||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: "")
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
displaySuccess()
|
||||
completion(reportReason, true)
|
||||
completion(nil, false)
|
||||
})
|
||||
case let .messages(messageIds):
|
||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: "")
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
displaySuccess()
|
||||
completion(reportReason, true)
|
||||
completion(nil, false)
|
||||
})
|
||||
case let .profilePhoto(peerId, photoId):
|
||||
let _ = (reportPeerPhoto(account: context.account, peerId: peerId, reason: reportReason, message: "")
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
displaySuccess()
|
||||
completion(reportReason, true)
|
||||
completion(nil, false)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let controller = ActionSheetController(presentationData: presentationData, allowInputInset: true)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
@ -133,11 +137,19 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro
|
||||
f(.dismissWithoutContent)
|
||||
})))
|
||||
}
|
||||
if let backAction = backAction {
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { (c, _) in
|
||||
backAction(c)
|
||||
})))
|
||||
}
|
||||
contextController.setItems(.single(items))
|
||||
} else {
|
||||
contextController?.dismiss()
|
||||
parent.view.endEditing(true)
|
||||
parent.present(peerReportOptionsController(context: context, subject: subject, passthrough: false, present: { [weak parent] c, a in
|
||||
parent.present(peerReportOptionsController(context: context, subject: subject, passthrough: passthrough, present: { [weak parent] c, a in
|
||||
parent?.present(c, in: .window(.root), with: a)
|
||||
}, push: { [weak parent] c in
|
||||
parent?.push(c)
|
||||
@ -200,31 +212,23 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
}
|
||||
|
||||
let action: (String) -> Void = { message in
|
||||
switch subject {
|
||||
case let .peer(peerId):
|
||||
if passthrough {
|
||||
completion(reportReason, true)
|
||||
} else {
|
||||
switch subject {
|
||||
case let .peer(peerId):
|
||||
let _ = (reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: message)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
displaySuccess()
|
||||
completion(nil, false)
|
||||
})
|
||||
}
|
||||
case let .messages(messageIds):
|
||||
if passthrough {
|
||||
completion(reportReason, true)
|
||||
} else {
|
||||
let _ = (reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: message)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
displaySuccess()
|
||||
completion(nil, false)
|
||||
})
|
||||
}
|
||||
case let .profilePhoto(peerId, photoId):
|
||||
if passthrough {
|
||||
completion(reportReason, true)
|
||||
} else {
|
||||
let _ = (reportPeerPhoto(account: context.account, peerId: peerId, reason: reportReason, message: message)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
displaySuccess()
|
||||
@ -259,8 +263,6 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
} else {
|
||||
action("")
|
||||
}
|
||||
} else {
|
||||
push(peerReportController(context: context, subject: subject, completion: completion))
|
||||
}
|
||||
|
||||
controller?.dismissAnimated()
|
||||
@ -278,180 +280,3 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
])
|
||||
return controller
|
||||
}
|
||||
|
||||
private final class PeerReportControllerArguments {
|
||||
let updateText: (String) -> Void
|
||||
|
||||
init(updateText: @escaping (String) -> Void) {
|
||||
self.updateText = updateText
|
||||
}
|
||||
}
|
||||
|
||||
private enum PeerReportControllerSection: Int32 {
|
||||
case text
|
||||
}
|
||||
|
||||
private enum PeerReportControllerEntryTag: ItemListItemTag {
|
||||
case text
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? PeerReportControllerEntryTag {
|
||||
switch self {
|
||||
case .text:
|
||||
if case .text = other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum PeerReportControllerEntry: ItemListNodeEntry {
|
||||
case text(PresentationTheme, String, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .text:
|
||||
return PeerReportControllerSection.text.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .text:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerReportControllerEntry, rhs: PeerReportControllerEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .text(lhsTheme, lhsText, lhsValue):
|
||||
if case let .text(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: PeerReportControllerEntry, rhs: PeerReportControllerEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! PeerReportControllerArguments
|
||||
switch self {
|
||||
case let .text(theme, title, value):
|
||||
return ItemListMultilineInputItem(presentationData: presentationData, text: value, placeholder: title, maxLength: nil, sectionId: self.section, style: .blocks, textUpdated: { text in
|
||||
arguments.updateText(text)
|
||||
}, tag: PeerReportControllerEntryTag.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct PeerReportControllerState: Equatable {
|
||||
var isReporting: Bool = false
|
||||
var text: String = ""
|
||||
}
|
||||
|
||||
private func peerReportControllerEntries(presentationData: PresentationData, state: PeerReportControllerState) -> [PeerReportControllerEntry] {
|
||||
var entries: [PeerReportControllerEntry] = []
|
||||
|
||||
entries.append(.text(presentationData.theme, presentationData.strings.ReportPeer_ReasonOther_Placeholder, state.text))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
private func peerReportController(context: AccountContext, subject: PeerReportSubject, completion: @escaping (ReportReason?, Bool) -> Void) -> ViewController {
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
|
||||
let statePromise = ValuePromise(PeerReportControllerState(), ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: PeerReportControllerState())
|
||||
let updateState: ((PeerReportControllerState) -> PeerReportControllerState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
let arguments = PeerReportControllerArguments(updateText: { text in
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.text = text
|
||||
return state
|
||||
}
|
||||
})
|
||||
|
||||
let reportDisposable = MetaDisposable()
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get())
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let rightButton: ItemListNavigationButton
|
||||
if state.isReporting {
|
||||
rightButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||
} else {
|
||||
rightButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: !state.text.isEmpty, action: {
|
||||
var text: String = ""
|
||||
updateState { state in
|
||||
var state = state
|
||||
if !state.isReporting && !state.text.isEmpty {
|
||||
text = state.text
|
||||
state.isReporting = true
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
if !text.isEmpty {
|
||||
let reportReason: ReportReason = .custom
|
||||
let completed: () -> Void = {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
presentControllerImpl?(textAlertController(context: context, title: nil, text: presentationData.strings.ReportPeer_AlertSuccess, actions: [TextAlertAction.init(type: TextAlertActionType.defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||
completion(reportReason, true)
|
||||
dismissImpl?()
|
||||
}
|
||||
switch subject {
|
||||
case let .peer(peerId):
|
||||
reportDisposable.set((reportPeer(account: context.account, peerId: peerId, reason: reportReason, message: text)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
completed()
|
||||
}))
|
||||
case let .messages(messageIds):
|
||||
reportDisposable.set((reportPeerMessages(account: context.account, messageIds: messageIds, reason: reportReason, message: text)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
completed()
|
||||
}))
|
||||
case let .profilePhoto(peerId, photoId):
|
||||
reportDisposable.set((reportPeerPhoto(account: context.account, peerId: peerId, reason: reportReason, message: text)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
completed()
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.ReportPeer_ReasonOther_Title), leftNavigationButton: ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||
dismissImpl?()
|
||||
completion(nil, false)
|
||||
}), rightNavigationButton: rightButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: peerReportControllerEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: PeerReportControllerEntryTag.text)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|> afterDisposed {
|
||||
reportDisposable.dispose()
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
controller.navigationPresentation = .modal
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
controller?.dismiss()
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
@ -230,7 +230,22 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
case .end:
|
||||
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallDeclineButton"), color: imageColor)
|
||||
case .cancel:
|
||||
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallCancelButton"), color: imageColor)
|
||||
image = generateImage(CGSize(width: 28.0, height: 28.0), opaque: false, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setLineWidth(4.0 - UIScreenPixel)
|
||||
context.setLineCap(.round)
|
||||
context.setStrokeColor(imageColor.cgColor)
|
||||
|
||||
context.move(to: CGPoint(x: 2.0 + UIScreenPixel, y: 2.0 + UIScreenPixel))
|
||||
context.addLine(to: CGPoint(x: 26.0 - UIScreenPixel, y: 26.0 - UIScreenPixel))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: 26.0 - UIScreenPixel, y: 2.0 + UIScreenPixel))
|
||||
context.addLine(to: CGPoint(x: 2.0 + UIScreenPixel, y: 26.0 - UIScreenPixel))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
||||
if let image = image {
|
||||
|
@ -694,14 +694,18 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}) {
|
||||
if let participantsContext = temporaryParticipantsContext.context.participantsContext {
|
||||
let myPeerId = self.joinAsPeerId
|
||||
let myPeer = self.accountContext.account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(myPeerId)
|
||||
let myPeer = self.accountContext.account.postbox.transaction { transaction -> (Peer, CachedPeerData?)? in
|
||||
if let peer = transaction.getPeer(myPeerId) {
|
||||
return (peer, transaction.getPeerCachedData(peerId: myPeerId))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self.participantsContextStateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||
myPeer,
|
||||
participantsContext.state,
|
||||
participantsContext.activeSpeakers
|
||||
).start(next: { [weak self] myPeer, state, activeSpeakers in
|
||||
).start(next: { [weak self] myPeerAndCachedData, state, activeSpeakers in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -721,7 +725,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
var participants = state.participants
|
||||
|
||||
if !participants.contains(where: { $0.peer.id == myPeerId }) {
|
||||
if let myPeer = myPeer {
|
||||
if let (myPeer, cachedData) = myPeerAndCachedData {
|
||||
let about: String?
|
||||
if let cachedData = cachedData as? CachedUserData {
|
||||
about = cachedData.about
|
||||
} else if let cachedData = cachedData as? CachedUserData {
|
||||
about = cachedData.about
|
||||
} else {
|
||||
about = nil
|
||||
}
|
||||
participants.append(GroupCallParticipantsContext.Participant(
|
||||
peer: myPeer,
|
||||
ssrc: 0,
|
||||
@ -731,7 +743,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
activityRank: nil,
|
||||
muteState: GroupCallParticipantsContext.Participant.MuteState(canUnmute: true, mutedByYou: false),
|
||||
volume: nil,
|
||||
about: nil
|
||||
about: about
|
||||
))
|
||||
participants.sort()
|
||||
}
|
||||
|
@ -1438,6 +1438,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
let myPeerId = strongSelf.call.myPeerId
|
||||
let darkTheme = strongSelf.darkTheme
|
||||
|
||||
var mainItemsImpl: (() -> Signal<[ContextMenuItem], NoError>)?
|
||||
|
||||
@ -1456,8 +1457,34 @@ public final class VoiceChatController: ViewController {
|
||||
} else if let subscribers = peer.subscribers {
|
||||
subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers)
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: peer.peer, size: avatarSize)), action: { _, f in
|
||||
|
||||
let isSelected = peer.peer.id == myPeerId
|
||||
let extendedAvatarSize = CGSize(width: 35.0, height: 35.0)
|
||||
let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: peer.peer, size: avatarSize)
|
||||
|> map { image -> UIImage? in
|
||||
if isSelected, let image = image {
|
||||
return generateImage(extendedAvatarSize, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
context.draw(image.cgImage!, in: CGRect(x: (extendedAvatarSize.width - avatarSize.width) / 2.0, y: (extendedAvatarSize.height - avatarSize.height) / 2.0, width: avatarSize.width, height: avatarSize.height))
|
||||
|
||||
let lineWidth = 1.0 + UIScreenPixel
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setStrokeColor(darkTheme.actionSheet.controlAccentColor.cgColor)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0))
|
||||
})
|
||||
} else {
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: isSelected ? extendedAvatarSize : avatarSize, signal: avatarSignal), action: { _, f in
|
||||
f(.default)
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer.peer, text: strongSelf.presentationData.strings.VoiceChat_DisplayAsSuccess(peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), action: { _ in return false })
|
||||
})))
|
||||
}
|
||||
items.append(.separator)
|
||||
@ -1537,8 +1564,12 @@ public final class VoiceChatController: ViewController {
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
let controller = voiceChatTitleEditController(sharedContext: context.sharedContext, account: context.account, forceTheme: self?.darkTheme, title: self?.callState?.title, apply: { title in
|
||||
self?.call.updateTitle(title ?? "")
|
||||
let controller = voiceChatTitleEditController(sharedContext: context.sharedContext, account: context.account, forceTheme: self?.darkTheme, title: self?.callState?.title, apply: { [weak self] title in
|
||||
if let strongSelf = self, let title = title {
|
||||
strongSelf.call.updateTitle(title)
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .succeed(text: strongSelf.presentationData.strings.VoiceChat_EditTitleSuccess(title).0), action: { _ in return false })
|
||||
}
|
||||
})
|
||||
self?.controller?.present(controller, in: .window(.root))
|
||||
})))
|
||||
@ -1597,11 +1628,15 @@ public final class VoiceChatController: ViewController {
|
||||
})))
|
||||
|
||||
if let recordingStartTimestamp = strongSelf.callState?.recordingStartTimestamp {
|
||||
items.append(.custom(VoiceChatRecordingContextItem(timestamp: Double(recordingStartTimestamp), action: { _, f in
|
||||
items.append(.custom(VoiceChatRecordingContextItem(timestamp: recordingStartTimestamp, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
let alertController = textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: nil, text: strongSelf.presentationData.strings.VoiceChat_StopRecordingTitle, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.VoiceChat_StopRecordingStop, action: {
|
||||
self?.call.setShouldBeRecording(false)
|
||||
let alertController = textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: nil, text: strongSelf.presentationData.strings.VoiceChat_StopRecordingTitle, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.VoiceChat_StopRecordingStop, action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.call.setShouldBeRecording(false)
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .forward(savedMessages: true, text: strongSelf.presentationData.strings.VoiceChat_RecordingSaved), action: { _ in return false })
|
||||
}
|
||||
})])
|
||||
self?.controller?.present(alertController, in: .window(.root))
|
||||
}), false))
|
||||
@ -1611,8 +1646,12 @@ public final class VoiceChatController: ViewController {
|
||||
}, action: { _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
let alertController = textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: strongSelf.presentationData.strings.VoiceChat_StartRecordingTitle, text: strongSelf.presentationData.strings.VoiceChat_StartRecordingText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.VoiceChat_StartRecordingStart, action: {
|
||||
self?.call.setShouldBeRecording(true)
|
||||
let alertController = textAlertController(context: strongSelf.context, forceTheme: strongSelf.darkTheme, title: strongSelf.presentationData.strings.VoiceChat_StartRecordingTitle, text: strongSelf.presentationData.strings.VoiceChat_StartRecordingText, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.VoiceChat_StartRecordingStart, action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.call.setShouldBeRecording(true)
|
||||
|
||||
strongSelf.presentUndoOverlay(content: .recording(text: strongSelf.presentationData.strings.VoiceChat_RecordingStarted), action: { _ in return false })
|
||||
}
|
||||
})])
|
||||
self?.controller?.present(alertController, in: .window(.root))
|
||||
})))
|
||||
@ -1654,7 +1693,7 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
let optionsButton: VoiceChatHeaderButton = !strongSelf.recButton.isHidden ? strongSelf.recButton : strongSelf.optionsButton
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: optionsButton.extractedContainerNode, keepInPlace: false, blurBackground: false)), items: mainItemsImpl?() ?? .single([]), reactionItems: [], gesture: gesture)
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: optionsButton.extractedContainerNode, keepInPlace: true, blurBackground: false)), items: mainItemsImpl?() ?? .single([]), reactionItems: [], gesture: gesture)
|
||||
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
@ -1668,7 +1707,7 @@ public final class VoiceChatController: ViewController {
|
||||
|> deliverOnMainQueue).start(next: { [weak self] color in
|
||||
if let strongSelf = self {
|
||||
strongSelf.currentAudioButtonColor = color
|
||||
strongSelf.updateButtons(transition: .immediate)
|
||||
strongSelf.updateButtons(animated: true)
|
||||
}
|
||||
})
|
||||
|
||||
@ -2214,7 +2253,7 @@ public final class VoiceChatController: ViewController {
|
||||
self.titleNode.update(size: CGSize(width: size.width, height: 44.0), title: title, subtitle: self.currentSubtitle, transition: transition)
|
||||
}
|
||||
|
||||
private func updateButtons(transition: ContainedViewLayoutTransition) {
|
||||
private func updateButtons(animated: Bool) {
|
||||
let audioButtonAppearance: CallControllerButtonItemNode.Content.Appearance
|
||||
if let color = self.currentAudioButtonColor {
|
||||
audioButtonAppearance = .color(.custom(color.rgb, 1.0))
|
||||
@ -2272,11 +2311,11 @@ public final class VoiceChatController: ViewController {
|
||||
soundTitle = self.presentationData.strings.Call_Audio
|
||||
}
|
||||
|
||||
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: soundTitle, transition: .animated(duration: 0.3, curve: .linear))
|
||||
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: soundTitle, transition: animated ? .animated(duration: 0.3, curve: .linear) : .immediate)
|
||||
|
||||
let cameraButtonSize = CGSize(width: 40.0, height: 40.0)
|
||||
|
||||
self.cameraButtonNode.update(size: cameraButtonSize, content: CallControllerButtonItemNode.Content(appearance: CallControllerButtonItemNode.Content.Appearance.blurred(isFilled: false), image: .camera), text: " ", transition: .animated(duration: 0.3, curve: .linear))
|
||||
self.cameraButtonNode.update(size: cameraButtonSize, content: CallControllerButtonItemNode.Content(appearance: CallControllerButtonItemNode.Content.Appearance.blurred(isFilled: false), image: .camera), text: " ", transition: animated ? .animated(duration: 0.3, curve: .linear) : .immediate)
|
||||
|
||||
self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .cancel), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
|
||||
}
|
||||
@ -2409,7 +2448,7 @@ public final class VoiceChatController: ViewController {
|
||||
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
||||
}
|
||||
|
||||
self.updateButtons(transition: transition)
|
||||
self.updateButtons(animated: !isFirstTime)
|
||||
|
||||
/*var currentVideoOrigin = CGPoint(x: 4.0, y: (layout.statusBarHeight ?? 0.0) + 4.0)
|
||||
for (_, _, videoNode) in self.videoNodes {
|
||||
|
@ -21,10 +21,10 @@ func generateStartRecordingIcon(color: UIColor) -> UIImage? {
|
||||
}
|
||||
|
||||
final class VoiceChatRecordingContextItem: ContextMenuCustomItem {
|
||||
fileprivate let timestamp: Double
|
||||
fileprivate let timestamp: Int32
|
||||
fileprivate let action: (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void
|
||||
|
||||
init(timestamp: Double, action: @escaping (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void) {
|
||||
init(timestamp: Int32, action: @escaping (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void) {
|
||||
self.timestamp = timestamp
|
||||
self.action = action
|
||||
}
|
||||
@ -187,8 +187,8 @@ private final class VoiceChatRecordingContextItemNode: ASDisplayNode, ContextMen
|
||||
return
|
||||
}
|
||||
|
||||
let currentTime = CFAbsoluteTimeGetCurrent()
|
||||
let duration = currentTime - item.timestamp
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
let duration = max(0, timestamp - item.timestamp)
|
||||
|
||||
let subtextFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
|
||||
self.statusNode.attributedText = NSAttributedString(string: stringForDuration(Int32(duration)), font: subtextFont, textColor: presentationData.theme.contextMenu.secondaryColor)
|
||||
@ -218,7 +218,12 @@ private final class VoiceChatRecordingContextItemNode: ASDisplayNode, ContextMen
|
||||
let verticalSpacing: CGFloat = 2.0
|
||||
let combinedTextHeight = textSize.height + verticalSpacing + statusSize.height
|
||||
return (CGSize(width: max(textSize.width, statusSize.width) + sideInset + rightTextInset, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in
|
||||
let hadLayout = self.validLayout != nil
|
||||
self.validLayout = size
|
||||
|
||||
if !hadLayout {
|
||||
self.updateTime(transition: .immediate)
|
||||
}
|
||||
let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0)
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
|
@ -418,6 +418,7 @@ func voiceChatTitleEditController(sharedContext: SharedAccountContext, account:
|
||||
presentationDataDisposable.dispose()
|
||||
}
|
||||
dismissImpl = { [weak controller] animated in
|
||||
contentNode.inputFieldNode.deactivateInput()
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
|
File diff suppressed because it is too large
Load Diff
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Help.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Help.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_question.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Help.imageset/ic_question.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Help.imageset/ic_question.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/VoiceChat.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/VoiceChat.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menuvoicechat.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/VoiceChat.imageset/ic_menuvoicechat.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/VoiceChat.imageset/ic_menuvoicechat.pdf
vendored
Normal file
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_blocked.pdf",
|
||||
"filename" : "ic_left.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/ic_left.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/ic_left.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Instant View/Close.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/Close.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_ivcloselarge.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Close.imageset/ic_ivcloselarge.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Close.imageset/ic_ivcloselarge.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Instant View/CloseSmall.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/CloseSmall.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_ivclosesmall.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Instant View/CloseSmall.imageset/ic_ivclosesmall.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Instant View/CloseSmall.imageset/ic_ivclosesmall.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_right.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/ic_right.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/ic_right.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Instant View/Minimize.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/Minimize.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_ivcollapse.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Minimize.imageset/ic_ivcollapse.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Minimize.imageset/ic_ivcollapse.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_ivsettings.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Settings.imageset/ic_ivsettings.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Settings.imageset/ic_ivsettings.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings/Browser.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings/Browser.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_lt_safari.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings/Check.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings/Check.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_lt_check.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Settings/Check.imageset/ic_lt_check.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Settings/Check.imageset/ic_lt_check.pdf
vendored
Normal file
Binary file not shown.
@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings/DecreaseFont.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings/DecreaseFont.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_lt_smallerfont.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings/IncreaseFont.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings/IncreaseFont.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_lt_biggerfont.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings/Search.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Instant View/Settings/Search.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_lt_search.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Settings/Search.imageset/ic_lt_search.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Instant View/Settings/Search.imageset/ic_lt_search.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -59,6 +59,21 @@ private class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
|
||||
} else {
|
||||
let touch = touches.first!
|
||||
self.initialLocation = touch.location(in: self.view)
|
||||
|
||||
let touchesArray = Array(touches)
|
||||
if touchesArray.count == 2, let firstTouch = touchesArray.first, let secondTouch = touchesArray.last {
|
||||
let firstLocation = firstTouch.location(in: self.view)
|
||||
let secondLocation = secondTouch.location(in: self.view)
|
||||
|
||||
func distance(_ v1: CGPoint, _ v2: CGPoint) -> CGFloat {
|
||||
let dx = v1.x - v2.x
|
||||
let dy = v1.y - v2.y
|
||||
return sqrt(dx * dx + dy * dy)
|
||||
}
|
||||
if distance(firstLocation, secondLocation) > 100 {
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -885,7 +885,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
})))
|
||||
} else if message.id.peerId.isReplies {
|
||||
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_ContextMenuBlock, textColor: .destructive, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Block"), color: theme.actionSheet.destructiveActionTextColor)
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.destructiveActionTextColor)
|
||||
}, action: { controller, f in
|
||||
interfaceInteraction.blockMessageAuthor(message, controller)
|
||||
})))
|
||||
|
@ -425,6 +425,7 @@ func chatTextLinkEditController(sharedContext: SharedAccountContext, account: Ac
|
||||
presentationDataDisposable.dispose()
|
||||
}
|
||||
dismissImpl = { [weak controller] animated in
|
||||
contentNode.inputFieldNode.deactivateInput()
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
} else {
|
||||
|
@ -25,19 +25,22 @@ final class PeerInfoState {
|
||||
let updatingAvatar: PeerInfoUpdatingAvatar?
|
||||
let updatingBio: String?
|
||||
let avatarUploadProgress: CGFloat?
|
||||
let highlightedButton: PeerInfoHeaderButtonKey?
|
||||
|
||||
init(
|
||||
isEditing: Bool,
|
||||
selectedMessageIds: Set<MessageId>?,
|
||||
updatingAvatar: PeerInfoUpdatingAvatar?,
|
||||
updatingBio: String?,
|
||||
avatarUploadProgress: CGFloat?
|
||||
avatarUploadProgress: CGFloat?,
|
||||
highlightedButton: PeerInfoHeaderButtonKey?
|
||||
) {
|
||||
self.isEditing = isEditing
|
||||
self.selectedMessageIds = selectedMessageIds
|
||||
self.updatingAvatar = updatingAvatar
|
||||
self.updatingBio = updatingBio
|
||||
self.avatarUploadProgress = avatarUploadProgress
|
||||
self.highlightedButton = highlightedButton
|
||||
}
|
||||
|
||||
func withIsEditing(_ isEditing: Bool) -> PeerInfoState {
|
||||
@ -46,7 +49,8 @@ final class PeerInfoState {
|
||||
selectedMessageIds: self.selectedMessageIds,
|
||||
updatingAvatar: self.updatingAvatar,
|
||||
updatingBio: self.updatingBio,
|
||||
avatarUploadProgress: self.avatarUploadProgress
|
||||
avatarUploadProgress: self.avatarUploadProgress,
|
||||
highlightedButton: self.highlightedButton
|
||||
)
|
||||
}
|
||||
|
||||
@ -56,7 +60,8 @@ final class PeerInfoState {
|
||||
selectedMessageIds: selectedMessageIds,
|
||||
updatingAvatar: self.updatingAvatar,
|
||||
updatingBio: self.updatingBio,
|
||||
avatarUploadProgress: self.avatarUploadProgress
|
||||
avatarUploadProgress: self.avatarUploadProgress,
|
||||
highlightedButton: self.highlightedButton
|
||||
)
|
||||
}
|
||||
|
||||
@ -66,7 +71,8 @@ final class PeerInfoState {
|
||||
selectedMessageIds: self.selectedMessageIds,
|
||||
updatingAvatar: updatingAvatar,
|
||||
updatingBio: self.updatingBio,
|
||||
avatarUploadProgress: self.avatarUploadProgress
|
||||
avatarUploadProgress: self.avatarUploadProgress,
|
||||
highlightedButton: self.highlightedButton
|
||||
)
|
||||
}
|
||||
|
||||
@ -76,7 +82,8 @@ final class PeerInfoState {
|
||||
selectedMessageIds: self.selectedMessageIds,
|
||||
updatingAvatar: self.updatingAvatar,
|
||||
updatingBio: updatingBio,
|
||||
avatarUploadProgress: self.avatarUploadProgress
|
||||
avatarUploadProgress: self.avatarUploadProgress,
|
||||
highlightedButton: self.highlightedButton
|
||||
)
|
||||
}
|
||||
|
||||
@ -86,7 +93,19 @@ final class PeerInfoState {
|
||||
selectedMessageIds: self.selectedMessageIds,
|
||||
updatingAvatar: self.updatingAvatar,
|
||||
updatingBio: self.updatingBio,
|
||||
avatarUploadProgress: avatarUploadProgress
|
||||
avatarUploadProgress: avatarUploadProgress,
|
||||
highlightedButton: self.highlightedButton
|
||||
)
|
||||
}
|
||||
|
||||
func withHighlightedButton(_ highlightedButton: PeerInfoHeaderButtonKey?) -> PeerInfoState {
|
||||
return PeerInfoState(
|
||||
isEditing: self.isEditing,
|
||||
selectedMessageIds: self.selectedMessageIds,
|
||||
updatingAvatar: self.updatingAvatar,
|
||||
updatingBio: self.updatingBio,
|
||||
avatarUploadProgress: self.avatarUploadProgress,
|
||||
highlightedButton: highlightedButton
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -49,18 +49,22 @@ enum PeerInfoHeaderButtonIcon {
|
||||
final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
let key: PeerInfoHeaderButtonKey
|
||||
private let action: (PeerInfoHeaderButtonNode) -> Void
|
||||
let containerNode: ASDisplayNode
|
||||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
private let backgroundNode: ASImageNode
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
private var icon: PeerInfoHeaderButtonIcon?
|
||||
private var isActive: Bool?
|
||||
|
||||
init(key: PeerInfoHeaderButtonKey, action: @escaping (PeerInfoHeaderButtonNode) -> Void) {
|
||||
self.key = key
|
||||
self.action = action
|
||||
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.referenceNode = ContextReferenceContentNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.isGestureEnabled = false
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
@ -73,9 +77,10 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
|
||||
self.accessibilityTraits = .button
|
||||
|
||||
self.containerNode.addSubnode(self.referenceNode)
|
||||
self.referenceNode.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.backgroundNode)
|
||||
self.containerNode.addSubnode(self.textNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
@ -96,13 +101,14 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
self.action(self)
|
||||
}
|
||||
|
||||
func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isExpanded: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||
if self.theme != presentationData.theme || self.icon != icon {
|
||||
func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isActive: Bool, isExpanded: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||
if self.theme != presentationData.theme || self.icon != icon || self.isActive != isActive {
|
||||
self.theme = presentationData.theme
|
||||
self.icon = icon
|
||||
self.isActive = isActive
|
||||
self.backgroundNode.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(presentationData.theme.list.itemAccentColor.cgColor)
|
||||
context.setFillColor(isActive ? presentationData.theme.list.itemAccentColor.cgColor : presentationData.theme.list.itemDisabledTextColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
context.setBlendMode(.normal)
|
||||
context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor)
|
||||
@ -137,7 +143,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
})
|
||||
}
|
||||
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(12.0), textColor: presentationData.theme.list.itemAccentColor)
|
||||
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(12.0), textColor: isActive ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemDisabledTextColor)
|
||||
self.accessibilityLabel = text
|
||||
let titleSize = self.textNode.updateLayout(CGSize(width: 120.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
@ -145,6 +151,8 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.updateFrameAdditiveToCenter(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: size.height + 6.0), size: titleSize))
|
||||
transition.updateAlpha(node: self.textNode, alpha: isExpanded ? 0.0 : 1.0)
|
||||
|
||||
self.referenceNode.frame = self.containerNode.bounds
|
||||
}
|
||||
}
|
||||
|
||||
@ -2575,7 +2583,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
let usernameNodeContainer: ASDisplayNode
|
||||
let usernameNodeRawContainer: ASDisplayNode
|
||||
let usernameNode: MultiScaleTextNode
|
||||
private var buttonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderButtonNode] = [:]
|
||||
var buttonNodes: [PeerInfoHeaderButtonKey: PeerInfoHeaderButtonNode] = [:]
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let expandedBackgroundNode: ASDisplayNode
|
||||
let separatorNode: ASDisplayNode
|
||||
@ -3349,7 +3357,13 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
buttonText = presentationData.strings.PeerInfo_ButtonLeave
|
||||
buttonIcon = .leave
|
||||
}
|
||||
buttonNode.update(size: buttonFrame.size, text: buttonText, icon: buttonIcon, isExpanded: self.isAvatarExpanded, presentationData: presentationData, transition: buttonTransition)
|
||||
|
||||
var isActive = true
|
||||
if let highlightedButton = state.highlightedButton {
|
||||
isActive = buttonKey == highlightedButton
|
||||
}
|
||||
|
||||
buttonNode.update(size: buttonFrame.size, text: buttonText, icon: buttonIcon, isActive: isActive, isExpanded: self.isAvatarExpanded, presentationData: presentationData, transition: buttonTransition)
|
||||
transition.updateSublayerTransformScaleAdditive(node: buttonNode, scale: buttonsScale)
|
||||
|
||||
if wasAdded {
|
||||
|
@ -1507,7 +1507,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
selectedMessageIds: nil,
|
||||
updatingAvatar: nil,
|
||||
updatingBio: nil,
|
||||
avatarUploadProgress: nil
|
||||
avatarUploadProgress: nil,
|
||||
highlightedButton: nil
|
||||
)
|
||||
private let nearbyPeerDistance: Int32?
|
||||
private var dataDisposable: Disposable?
|
||||
@ -1597,7 +1598,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self?.updateBlocked(block: block)
|
||||
},
|
||||
openReport: { [weak self] user in
|
||||
self?.openReport(user: user)
|
||||
self?.openReport(user: user, contextController: nil, backAction: nil)
|
||||
},
|
||||
openShareBot: { [weak self] in
|
||||
self?.openShareBot()
|
||||
@ -3403,106 +3404,148 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
guard let data = self.data, let peer = data.peer else {
|
||||
return
|
||||
}
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
let dismissAction: () -> Void = { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
let presentationData = self.presentationData
|
||||
|
||||
self.state = self.state.withHighlightedButton(.more)
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
|
||||
}
|
||||
var items: [ActionSheetItem] = []
|
||||
if !peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: self.isOpenedFromChat, videoCallsEnabled: self.videoCallsEnabled, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false).contains(.search) || (self.headerNode.isAvatarExpanded && self.peerId.namespace == Namespaces.Peer.CloudUser) {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.ChatSearch_SearchPlaceholder, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
|
||||
var mainItemsImpl: (() -> Signal<[ContextMenuItem], NoError>)?
|
||||
|
||||
let displayAsItems: () -> Signal<[ContextMenuItem], NoError> = {
|
||||
return .single([])
|
||||
}
|
||||
|
||||
mainItemsImpl = {
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
let headerButtons = peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: self.isOpenedFromChat, videoCallsEnabled: self.videoCallsEnabled, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false)
|
||||
|
||||
let hasSearch = !headerButtons.contains(.search) || (self.headerNode.isAvatarExpanded && self.peerId.namespace == Namespaces.Peer.CloudUser)
|
||||
if hasSearch {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChatSearch_SearchPlaceholder, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
self?.openChatWithMessageSearch()
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
if let user = peer as? TelegramUser {
|
||||
if let botInfo = user.botInfo {
|
||||
if botInfo.flags.contains(.worksWithGroups) {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_InviteBotToGroup, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_InviteBotToGroup, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Groups"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
self?.openAddBotToGroup()
|
||||
}))
|
||||
})))
|
||||
}
|
||||
if user.username != nil {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_ShareBot, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ShareBot, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
self?.openShareBot()
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
if let cachedData = data.cachedData as? CachedUserData, let botInfo = cachedData.botInfo {
|
||||
for command in botInfo.commands {
|
||||
if command.text == "settings" {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_BotSettings, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotSettings, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Bots"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
self?.performBotCommand(command: .settings)
|
||||
}))
|
||||
})))
|
||||
} else if command.text == "help" {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_BotHelp, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotHelp, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Help"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
self?.performBotCommand(command: .help)
|
||||
}))
|
||||
})))
|
||||
} else if command.text == "privacy" {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_BotPrivacy, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotPrivacy, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
self?.performBotCommand(command: .privacy)
|
||||
}))
|
||||
})))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if user.botInfo == nil && data.isContact {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Profile_ShareContactButton, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let peer = strongSelf.data?.peer as? TelegramUser, let phone = peer.phone {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Profile_ShareContactButton, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
if let strongSelf = self, let peer = strongSelf.data?.peer as? TelegramUser, let phone = peer.phone {
|
||||
let contact = TelegramMediaContact(firstName: peer.firstName ?? "", lastName: peer.lastName ?? "", phoneNumber: phone, peerId: peer.id, vCardData: nil)
|
||||
let shareController = ShareController(context: strongSelf.context, subject: .media(.standalone(media: contact)))
|
||||
strongSelf.controller?.present(shareController, in: .window(.root))
|
||||
}
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
if self.peerId.namespace == Namespaces.Peer.CloudUser && user.botInfo == nil && !user.flags.contains(.isSupport) {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.UserInfo_StartSecretChat, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_StartSecretChat, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Add"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
self?.openStartSecretChat()
|
||||
}))
|
||||
})))
|
||||
if data.isContact {
|
||||
if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked {
|
||||
} else {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Conversation_BlockUser, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_BlockUser, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
self?.updateBlocked(block: true)
|
||||
}))
|
||||
})))
|
||||
}
|
||||
}
|
||||
} else if self.peerId.namespace == Namespaces.Peer.SecretChat && data.isContact {
|
||||
if let cachedData = data.cachedData as? CachedUserData, cachedData.isBlocked {
|
||||
} else {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Conversation_BlockUser, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_BlockUser, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
self?.updateBlocked(block: true)
|
||||
}))
|
||||
})))
|
||||
}
|
||||
}
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if !channel.flags.contains(.hasVoiceChat) {
|
||||
if channel.flags.contains(.isCreator) || channel.hasPermission(.manageCalls) {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_CreateVoiceChat, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_CreateVoiceChat, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VoiceChat"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
self?.requestCall(isVideo: false)
|
||||
}))
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
if let cachedData = self.data?.cachedData as? CachedChannelData, cachedData.flags.contains(.canViewStats) {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_Stats, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_Stats, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Statistics"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
self?.openStats()
|
||||
}))
|
||||
})))
|
||||
}
|
||||
|
||||
var canReport = true
|
||||
@ -3516,41 +3559,62 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
canReport = false
|
||||
}
|
||||
if canReport {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.ReportPeer_Report, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
self?.openReport(user: false)
|
||||
}))
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ReportPeer_Report, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Report"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] c, f in
|
||||
self?.openReport(user: false, contextController: c, backAction: { c in
|
||||
if let mainItemsImpl = mainItemsImpl {
|
||||
c.setItems(mainItemsImpl())
|
||||
}
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
if channel.flags.contains(.isCreator) {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_DeleteChannel, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_DeleteChannel, textColor: .destructive, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
self?.openDeletePeer()
|
||||
}))
|
||||
})))
|
||||
} else {
|
||||
if !peerInfoHeaderButtons(peer: peer, cachedData: data.cachedData, isOpenedFromChat: self.isOpenedFromChat, videoCallsEnabled: self.videoCallsEnabled, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false).contains(.leave) {
|
||||
if !headerButtons.contains(.leave) {
|
||||
if case .member = channel.participationStatus {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Channel_LeaveChannel, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Channel_LeaveChannel, textColor: .destructive, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
self?.openLeavePeer()
|
||||
}))
|
||||
})))
|
||||
}
|
||||
}
|
||||
}
|
||||
case .group:
|
||||
if channel.flags.contains(.isCreator) {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_DeleteGroup, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_DeleteGroup, textColor: .destructive, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
self?.openDeletePeer()
|
||||
}))
|
||||
})))
|
||||
} else {
|
||||
if case .member = channel.participationStatus {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Group_LeaveGroup, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_LeaveGroup, textColor: .destructive, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
self?.openLeavePeer()
|
||||
}))
|
||||
})))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3564,30 +3628,44 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
}
|
||||
if canManageGroupCalls, !group.flags.contains(.hasVoiceChat) {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_CreateVoiceChat, color: .accent, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_CreateVoiceChat, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VoiceChat"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.createAndJoinGroupCall(peerId: group.id)
|
||||
}))
|
||||
self?.requestCall(isVideo: false)
|
||||
})))
|
||||
}
|
||||
|
||||
if case .Member = group.membership {
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Group_LeaveGroup, color: .destructive, action: { [weak self] in
|
||||
dismissAction()
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Group_LeaveGroup, textColor: .destructive, icon: { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)
|
||||
}, action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
|
||||
self?.openLeavePeer()
|
||||
}))
|
||||
})))
|
||||
}
|
||||
}
|
||||
actionSheet.setItemGroups([
|
||||
ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
|
||||
return .single(items)
|
||||
}
|
||||
|
||||
self.view.endEditing(true)
|
||||
controller.present(actionSheet, in: .window(.root))
|
||||
|
||||
if let sourceNode = self.headerNode.buttonNodes[.more]?.referenceNode {
|
||||
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoButtonReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: mainItemsImpl?() ?? .single([]), reactionItems: [], gesture: nil)
|
||||
contextController.dismissed = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.state = strongSelf.state.withHighlightedButton(nil)
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
case .addMember:
|
||||
self.openAddMember()
|
||||
case .search:
|
||||
@ -3623,14 +3701,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
private func openChatForReporting(_ reason: ReportReason) {
|
||||
if let navigationController = (self.controller?.navigationController as? NavigationController) {
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(self.peerId), keepStack: .default, reportReason: reason, completion: { _ in
|
||||
// var viewControllers = navigationController.viewControllers
|
||||
// viewControllers = viewControllers.filter { controller in
|
||||
// if controller is PeerInfoScreen {
|
||||
// return false
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
// navigationController.setViewControllers(viewControllers, animated: false)
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -4114,7 +4184,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
controller.push(statsController)
|
||||
}
|
||||
|
||||
private func openReport(user: Bool) {
|
||||
private func openReport(user: Bool, contextController: ContextController?, backAction: ((ContextController) -> Void)?) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
@ -4126,17 +4196,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
} else {
|
||||
options = [.spam, .fake, .violence, .pornography, .childAbuse, .copyright, .other]
|
||||
}
|
||||
controller.present(peerReportOptionsController(context: self.context, subject: .peer(self.peerId), options: options, passthrough: true, present: { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}, push: { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}, completion: { [weak self] reason, _ in
|
||||
|
||||
presentPeerReportOptions(context: self.context, parent: controller, contextController: contextController, backAction: backAction, subject: .peer(self.peerId), options: options, passthrough: true, completion: { [weak self] reason, _ in
|
||||
if let reason = reason {
|
||||
DispatchQueue.main.async {
|
||||
self?.openChatForReporting(reason)
|
||||
}
|
||||
}
|
||||
}), in: .window(.root))
|
||||
})
|
||||
}
|
||||
|
||||
private func openEncryptionKey() {
|
||||
@ -6507,6 +6574,20 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerInfoButtonReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
|
||||
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
||||
func presentAddMembers(context: AccountContext, parentController: ViewController, groupPeer: Peer, selectAddMemberDisposable: MetaDisposable, addMemberDisposable: MetaDisposable) {
|
||||
let members: Promise<[PeerId]> = Promise()
|
||||
if groupPeer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
|
@ -32,6 +32,7 @@ public enum UndoOverlayContent {
|
||||
case autoDelete(isOn: Bool, title: String?, text: String)
|
||||
case gigagroupConversion(text: String)
|
||||
case linkRevoked(text: String)
|
||||
case recording(text: String)
|
||||
}
|
||||
|
||||
public enum UndoOverlayAction {
|
||||
|
@ -549,6 +549,21 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 3
|
||||
case let .recording(text):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
self.iconCheckNode = nil
|
||||
self.animationNode = AnimationNode(animation: "anim_linkrevoked", colors: [:], scale: 0.066)
|
||||
self.animatedStickerNode = nil
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 3
|
||||
}
|
||||
@ -579,7 +594,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
switch content {
|
||||
case .removedChat:
|
||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked:
|
||||
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .recording:
|
||||
break
|
||||
case .dice:
|
||||
self.panelWrapperNode.clipsToBounds = true
|
||||
|
Loading…
x
Reference in New Issue
Block a user