Voice Chats UI improvements

This commit is contained in:
Ilya Laktyushin 2020-11-21 13:19:30 +04:00
parent e87908a532
commit a362cab636
22 changed files with 6710 additions and 4725 deletions

View File

@ -5895,3 +5895,42 @@ Sorry for the inconvenience.";
"Conversation.EditingPhotoPanelTitle" = "Edit Photo";
"Conversation.TextCopied" = "Text copied to clipboard";
"Media.LimitedAccessTitle" = "Limited Access to Media";
"Media.LimitedAccessText" = "You've given Telegram access only to select number of photos.";
"Media.LimitedAccessManage" = "Manage";
"VoiceChat.BackTitle" = "Chat";
"VoiceChat.StatusSpeaking" = "speaking";
"VoiceChat.StatusListening" = "listening";
"VoiceChat.Connecting" = "Connecting...";
"VoiceChat.Reconnecting" = "Reconnecting...";
"VoiceChat.Unmute" = "Unmute";
"VoiceChat.UnmuteHelp" = "or hold and speak";
"VoiceChat.Live" = "You're Live";
"VoiceChat.Mute" = "Tap to Mute";
"VoiceChat.Muted" = "Muted";
"VoiceChat.MutedHelp" = "You are in Listen Only mode";
"VoiceChat.Audio" = "audio";
"VoiceChat.Leave" = "leave";
"VoiceChat.SpeakPermissionEveryone" = "All Members Can Speak";
"VoiceChat.SpeakPermissionAdmin" = "Only Admins Can Speak";
"VoiceChat.Share" = "Share Invite Link";
"VoiceChat.EndVoiceChat" = "End Voice Chat";
"VoiceChat.UnmutePeer" = "Allow to Speak";
"VoiceChat.MutePeer" = "Mute";
"VoiceChat.RemovePeer" = "Remove";
"VoiceChat.RemovePeerConfirmation" = "Are you sure you want to remove %@ from the group chat?";
"VoiceChat.RemovePeerRemove" = "Remove";
"VoiceChat.UserInvited" = "You invited %@ to the voice chat";
"Notification.VoiceChatInvitation" = "%1$@ invited %2$@ to the voice chat";
"Notification.VoiceChatInvitationByYou" = "You invited %1$@ to the voice chat";
"Notification.VoiceChatInvitationForYou" = "%1$@ invited you to the voice chat";

View File

@ -14,6 +14,7 @@ public enum DeleteChatPeerAction {
case clearHistory
case clearCache
case clearCacheSuggestion
case removeFromGroup
}
private let avatarFont = avatarPlaceholderFont(size: 26.0)
@ -115,6 +116,8 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
}
case .clearHistory:
text = strings.ChatList_ClearChatConfirmation(peer.displayTitle(strings: strings, displayOrder: nameOrder))
case .removeFromGroup:
text = strings.VoiceChat_RemovePeerConfirmation(peer.displayTitle(strings: strings, displayOrder: nameOrder))
default:
break
}

View File

@ -283,6 +283,7 @@ public final class ShareController: ViewController {
private var currentAccount: Account
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private let forcedTheme: PresentationTheme?
private let externalShare: Bool
private let immediateExternalShare: Bool
@ -302,11 +303,11 @@ public final class ShareController: ViewController {
public var dismissed: ((Bool) -> Void)?
public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, openStats: (() -> Void)? = nil, shares: Int? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil) {
self.init(sharedContext: context.sharedContext, currentContext: context, subject: subject, presetText: presetText, preferredAction: preferredAction, showInChat: showInChat, openStats: openStats, shares: shares, externalShare: externalShare, immediateExternalShare: immediateExternalShare, switchableAccounts: switchableAccounts, immediatePeerId: immediatePeerId)
public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, openStats: (() -> Void)? = nil, shares: Int? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, forcedTheme: PresentationTheme? = nil) {
self.init(sharedContext: context.sharedContext, currentContext: context, subject: subject, presetText: presetText, preferredAction: preferredAction, showInChat: showInChat, openStats: openStats, shares: shares, externalShare: externalShare, immediateExternalShare: immediateExternalShare, switchableAccounts: switchableAccounts, immediatePeerId: immediatePeerId, forcedTheme: forcedTheme)
}
public init(sharedContext: SharedAccountContext, currentContext: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, openStats: (() -> Void)? = nil, shares: Int? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil) {
public init(sharedContext: SharedAccountContext, currentContext: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, openStats: (() -> Void)? = nil, shares: Int? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, forcedTheme: PresentationTheme? = nil) {
self.sharedContext = sharedContext
self.currentContext = currentContext
self.currentAccount = currentContext.account
@ -318,8 +319,12 @@ public final class ShareController: ViewController {
self.immediatePeerId = immediatePeerId
self.openStats = openStats
self.shares = shares
self.forcedTheme = forcedTheme
self.presentationData = self.sharedContext.currentPresentationData.with { $0 }
if let forcedTheme = self.forcedTheme {
self.presentationData = self.presentationData.withUpdated(theme: forcedTheme)
}
super.init(navigationBarPresentationData: nil)
@ -441,7 +446,7 @@ public final class ShareController: ViewController {
return
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, shares: self.shares)
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, shares: self.shares, forcedTheme: self.forcedTheme)
self.controllerNode.dismiss = { [weak self] shared in
self?.presentingViewController?.dismiss(animated: false, completion: nil)
self?.dismissed?(shared)

View File

@ -29,6 +29,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
private let sharedContext: SharedAccountContext
private var context: AccountContext?
private var presentationData: PresentationData
private let forcedTheme: PresentationTheme?
private let externalShare: Bool
private let immediateExternalShare: Bool
private var immediatePeerId: PeerId?
@ -80,9 +81,10 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
private let presetText: String?
init(sharedContext: SharedAccountContext, presetText: String?, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?, shares: Int?) {
init(sharedContext: SharedAccountContext, presetText: String?, defaultAction: ShareControllerAction?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?, shares: Int?, forcedTheme: PresentationTheme?) {
self.sharedContext = sharedContext
self.presentationData = sharedContext.currentPresentationData.with { $0 }
self.forcedTheme = forcedTheme
self.externalShare = externalShare
self.immediateExternalShare = immediateExternalShare
self.immediatePeerId = immediatePeerId
@ -94,6 +96,10 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
self.defaultAction = defaultAction
self.requestLayout = requestLayout
if let forcedTheme = self.forcedTheme {
self.presentationData = self.presentationData.withUpdated(theme: forcedTheme)
}
let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor)
let highlightedRoundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: self.presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor)
@ -260,6 +266,9 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
return
}
self.presentationData = presentationData
if let forcedTheme = self.forcedTheme {
self.presentationData = self.presentationData.withUpdated(theme: forcedTheme)
}
let roundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor)
let highlightedRoundedBackground = generateStretchableFilledCircleImage(radius: 16.0, color: self.presentationData.theme.actionSheet.opaqueItemHighlightedBackgroundColor)

View File

@ -27,6 +27,11 @@ swift_library(
"//submodules/ItemListPeerItem:ItemListPeerItem",
"//submodules/MergeLists:MergeLists",
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/ContextUI:ContextUI",
"//submodules/ShareController:ShareController",
"//submodules/LegacyComponents:LegacyComponents",
"//submodules/DeleteChatPeerActionSheetItem:DeleteChatPeerActionSheetItem",
"//submodules/AnimationUI:AnimationUI",
],
visibility = [
"//visibility:public",

View File

@ -62,7 +62,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
private let contentNode: ASImageNode
private let overlayHighlightNode: ASImageNode
private var statusNode: SemanticStatusNode?
private let textNode: ImmediateTextNode
let textNode: ImmediateTextNode
private let largeButtonSize: CGFloat = 72.0
@ -199,7 +199,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
case .green:
fillColor = UIColor(rgb: 0x74db58)
case .redDimmed:
fillColor = UIColor(rgb: 0xd92326).withMultipliedBrightnessBy(0.3)
fillColor = UIColor(rgb: 0x4d120e)
case .greenDimmed:
fillColor = UIColor(rgb: 0x74db58).withMultipliedBrightnessBy(0.3)
case .grayDimmed:

View File

@ -0,0 +1,744 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
private extension UIBezierPath {
static func smoothCurve(through points: [CGPoint], length: CGFloat, smoothness: CGFloat) -> UIBezierPath {
var smoothPoints = [SmoothPoint]()
for index in (0 ..< points.count) {
let prevIndex = index - 1
let prev = points[prevIndex >= 0 ? prevIndex : points.count + prevIndex]
let curr = points[index]
let next = points[(index + 1) % points.count]
let angle: CGFloat = {
let dx = next.x - prev.x
let dy = -next.y + prev.y
let angle = atan2(dy, dx)
if angle < 0 {
return abs(angle)
} else {
return 2 * .pi - angle
}
}()
smoothPoints.append(
SmoothPoint(
point: curr,
inAngle: angle + .pi,
inLength: smoothness * distance(from: curr, to: prev),
outAngle: angle,
outLength: smoothness * distance(from: curr, to: next)
)
)
}
let resultPath = UIBezierPath()
resultPath.move(to: smoothPoints[0].point)
for index in (0 ..< smoothPoints.count) {
let curr = smoothPoints[index]
let next = smoothPoints[(index + 1) % points.count]
let currSmoothOut = curr.smoothOut()
let nextSmoothIn = next.smoothIn()
resultPath.addCurve(to: next.point, controlPoint1: currSmoothOut, controlPoint2: nextSmoothIn)
}
resultPath.close()
return resultPath
}
static private func distance(from fromPoint: CGPoint, to toPoint: CGPoint) -> CGFloat {
return sqrt((fromPoint.x - toPoint.x) * (fromPoint.x - toPoint.x) + (fromPoint.y - toPoint.y) * (fromPoint.y - toPoint.y))
}
struct SmoothPoint {
let point: CGPoint
let inAngle: CGFloat
let inLength: CGFloat
let outAngle: CGFloat
let outLength: CGFloat
func smoothIn() -> CGPoint {
return smooth(angle: inAngle, length: inLength)
}
func smoothOut() -> CGPoint {
return smooth(angle: outAngle, length: outLength)
}
private func smooth(angle: CGFloat, length: CGFloat) -> CGPoint {
return CGPoint(
x: point.x + length * cos(angle),
y: point.y + length * sin(angle)
)
}
}
}
private final class BlobNodeDrawingState: NSObject {
let scale: CGFloat
let shape: CGPath?
let gradientTransition: CGFloat
let gradientMovement: CGFloat
init(scale: CGFloat, shape: CGPath?, gradientTransition: CGFloat, gradientMovement: CGFloat) {
self.scale = scale
self.shape = shape
self.gradientTransition = gradientTransition
self.gradientMovement = gradientMovement
super.init()
}
}
private class BlobNode: ASDisplayNode {
let size: CGSize
let pointsCount: Int
let smoothness: CGFloat
let minRandomness: CGFloat
let maxRandomness: CGFloat
let minSpeed: CGFloat
let maxSpeed: CGFloat
var minScale: CGFloat
var maxScale: CGFloat
let scaleSpeed: CGFloat
let isCircle: Bool
var currentScale: CGFloat = 1.0
var level: CGFloat = 0.0 {
didSet {
var effectiveMaxScale = maxScale
var effectiveMinScale = minScale
if !self.loop {
effectiveMaxScale *= self.isCircle ? 0.97 : 1.1
effectiveMinScale *= self.isCircle ? 0.97 : 1.1
}
self.currentScale = effectiveMinScale + (effectiveMaxScale - effectiveMinScale) * self.level
}
}
private var speedLevel: CGFloat = 0.0
private var lastSpeedLevel: CGFloat = 0.0
private var fromPoints: [CGPoint]?
private var toPoints: [CGPoint]?
var fromLoop: Bool?
var loop = false {
didSet {
if self.loop != oldValue {
self.fromLoop = oldValue
gradientTransitionArguments = (CACurrentMediaTime(), 0.4)
}
}
}
private var currentPoints: [CGPoint]? {
guard let fromPoints = fromPoints, let toPoints = toPoints else { return nil }
return fromPoints.enumerated().map { offset, fromPoint in
let toPoint = toPoints[offset]
return CGPoint(x: fromPoint.x + (toPoint.x - fromPoint.x) * transition, y: fromPoint.y + (toPoint.y - fromPoint.y) * transition)
}
}
private var currentShape: CGPath?
private var transition: CGFloat = 0 {
didSet {
if let currentPoints = self.currentPoints {
self.currentShape = UIBezierPath.smoothCurve(through: currentPoints, length: bounds.width, smoothness: smoothness).cgPath
}
}
}
private var gradientTransition: CGFloat = 0.0
private var gradientTransitionArguments: (startTime: Double, duration: Double)?
private var gradientMovementTransition: CGFloat = 0.0
private var gradientMovementTransitionArguments: (startTime: Double, duration: Double, reverse: Bool)?
private var animator: ConstantDisplayLinkAnimator?
private var transitionArguments: (startTime: Double, duration: Double)?
init(
size: CGSize,
pointsCount: Int,
minRandomness: CGFloat,
maxRandomness: CGFloat,
minSpeed: CGFloat,
maxSpeed: CGFloat,
minScale: CGFloat,
maxScale: CGFloat,
scaleSpeed: CGFloat,
isCircle: Bool
) {
self.size = size
self.pointsCount = pointsCount
self.minRandomness = minRandomness
self.maxRandomness = maxRandomness
self.minSpeed = minSpeed
self.maxSpeed = maxSpeed
self.minScale = minScale
self.maxScale = maxScale
self.scaleSpeed = scaleSpeed
self.isCircle = isCircle
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
super.init()
self.isOpaque = false
self.displaysAsynchronously = true
self.currentScale = minScale
}
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
speedLevel = max(speedLevel, newSpeedLevel)
if abs(lastSpeedLevel - newSpeedLevel) > 0.3 {
animateToNewShape()
}
}
func startAnimating() {
animateToNewShape()
}
func stopAnimating() {
fromPoints = currentPoints
toPoints = nil
pop_removeAnimation(forKey: "blob")
}
private func updateAnimations() {
var animate = false
let timestamp = CACurrentMediaTime()
if let (startTime, duration) = self.gradientTransitionArguments, duration > 0.0 {
if let fromLoop = self.fromLoop {
if fromLoop {
self.gradientTransition = max(0.0, min(1.0, CGFloat((timestamp - startTime) / duration)))
} else {
self.gradientTransition = max(0.0, min(1.0, 1.0 - CGFloat((timestamp - startTime) / duration)))
}
}
if self.gradientTransition < 1.0 {
animate = true
} else {
self.gradientTransitionArguments = nil
}
}
if let (startTime, duration) = self.transitionArguments, duration > 0.0 {
self.transition = max(0.0, min(1.0, CGFloat((timestamp - startTime) / duration)))
if self.transition < 1.0 {
animate = true
} else {
if self.loop {
self.animateToNewShape()
} else {
self.fromPoints = self.currentPoints
self.toPoints = nil
self.transition = 0.0
self.transitionArguments = nil
}
}
}
let gradientMovementStartTime: Double
let gradientMovementDuration: Double
let gradientMovementReverse: Bool
if let (startTime, duration, reverse) = self.gradientMovementTransitionArguments, duration > 0.0 {
gradientMovementStartTime = startTime
gradientMovementDuration = duration
gradientMovementReverse = reverse
} else {
gradientMovementStartTime = CACurrentMediaTime()
gradientMovementDuration = 1.0
gradientMovementReverse = false
self.gradientMovementTransitionArguments = (gradientMovementStartTime, gradientMovementStartTime, gradientMovementReverse)
}
let movementT = CGFloat((timestamp - gradientMovementStartTime) / gradientMovementDuration)
self.gradientMovementTransition = gradientMovementReverse ? 1.0 - movementT : movementT
if gradientMovementReverse && self.gradientMovementTransition <= 0.0 {
self.gradientMovementTransitionArguments = (CACurrentMediaTime(), 1.0, false)
} else if !gradientMovementReverse && self.gradientMovementTransition >= 1.0 {
self.gradientMovementTransitionArguments = (CACurrentMediaTime(), 1.0, true)
}
if animate {
let animator: ConstantDisplayLinkAnimator
if let current = self.animator {
animator = current
} else {
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.updateAnimations()
})
self.animator = animator
}
animator.isPaused = false
} else {
// self.animator?.isPaused = true
}
self.setNeedsDisplay()
}
private func animateToNewShape() {
if let _ = self.transitionArguments {
self.fromPoints = self.currentPoints
self.toPoints = nil
self.transition = 0.0
self.transitionArguments = nil
}
if self.fromPoints == nil {
self.fromPoints = generateNextBlob(for: self.bounds.size)
}
if self.toPoints == nil {
self.toPoints = generateNextBlob(for: self.bounds.size)
}
let duration: Double = 1.0 / Double(minSpeed + (maxSpeed - minSpeed) * speedLevel)
self.transitionArguments = (CACurrentMediaTime(), duration)
self.lastSpeedLevel = self.speedLevel
self.speedLevel = 0
self.updateAnimations()
}
private func generateNextBlob(for size: CGSize) -> [CGPoint] {
let randomness = minRandomness + (maxRandomness - minRandomness) * speedLevel
return blob(pointsCount: pointsCount, randomness: randomness).map {
return CGPoint(x: size.width / 2.0 + $0.x * CGFloat(size.width), y: size.height / 2.0 + $0.y * CGFloat(size.height))
}
}
func blob(pointsCount: Int, randomness: CGFloat) -> [CGPoint] {
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
let rgen = { () -> CGFloat in
let accuracy: UInt32 = 1000
let random = arc4random_uniform(accuracy)
return CGFloat(random) / CGFloat(accuracy)
}
let rangeStart: CGFloat = 1.0 / (1.0 + randomness / 10.0)
let startAngle = angle * CGFloat(arc4random_uniform(100)) / CGFloat(100)
let points = (0 ..< pointsCount).map { i -> CGPoint in
let randPointOffset = (rangeStart + CGFloat(rgen()) * (1 - rangeStart)) / 2
let angleRandomness: CGFloat = angle * 0.1
let randAngle = angle + angle * ((angleRandomness * CGFloat(arc4random_uniform(100)) / CGFloat(100)) - angleRandomness * 0.5)
let pointX = sin(startAngle + CGFloat(i) * randAngle)
let pointY = cos(startAngle + CGFloat(i) * randAngle)
return CGPoint(x: pointX * randPointOffset, y: pointY * randPointOffset)
}
return points
}
override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
// var transitionState: SemanticStatusNodeTransitionDrawingState?
// var transitionFraction: CGFloat = 1.0
// var appearanceBackgroundTransitionFraction: CGFloat = 1.0
// var appearanceForegroundTransitionFraction: CGFloat = 1.0
//
// if let transitionContext = self.transitionContext {
// let timestamp = CACurrentMediaTime()
// var t = CGFloat((timestamp - transitionContext.startTime) / transitionContext.duration)
// t = min(1.0, max(0.0, t))
//
// if let _ = transitionContext.previousStateContext {
// transitionFraction = t
// }
// var foregroundTransitionFraction: CGFloat = 1.0
// if let previousContext = transitionContext.previousAppearanceContext {
// if previousContext.backgroundImage != self.appearanceContext.backgroundImage {
// appearanceBackgroundTransitionFraction = t
// }
// if previousContext.cutout != self.appearanceContext.cutout {
// appearanceForegroundTransitionFraction = t
// foregroundTransitionFraction = 1.0 - t
// }
// }
// transitionState = SemanticStatusNodeTransitionDrawingState(transition: t, drawingState: transitionContext.previousStateContext?.drawingState(transitionFraction: 1.0 - t), appearanceState: transitionContext.previousAppearanceContext?.drawingState(backgroundTransitionFraction: 1.0, foregroundTransitionFraction: foregroundTransitionFraction))
// }
return BlobNodeDrawingState(scale: self.currentScale, shape: self.currentShape, gradientTransition: self.gradientTransition, gradientMovement: self.gradientMovementTransition)
}
@objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
let context = UIGraphicsGetCurrentContext()!
if !isRasterizing {
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
context.fill(bounds)
}
guard let parameters = parameters as? BlobNodeDrawingState else {
return
}
if let path = parameters.shape {
var uiPath = UIBezierPath(cgPath: path)
let toOrigin = CGAffineTransform(translationX: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
let fromOrigin = CGAffineTransform(translationX: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
uiPath.apply(toOrigin)
uiPath.apply(CGAffineTransform(scaleX: parameters.scale, y: parameters.scale))
uiPath.apply(fromOrigin)
context.addPath(uiPath.cgPath)
context.clip()
let blue = UIColor(rgb: 0x0078ff)
let lightBlue = UIColor(rgb: 0x59c7f8)
let green = UIColor(rgb: 0x33c659)
let firstColor = lightBlue.interpolateTo(blue, fraction: parameters.gradientTransition)!
let secondColor = blue.interpolateTo(green, fraction: parameters.gradientTransition)!
var locations: [CGFloat] = [0.0, 1.0]
let colors: [CGColor] = [firstColor.cgColor, secondColor.cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
var center: CGPoint = CGPoint(x: bounds.size.width - 30.0, y: 50.0)
center.x -= parameters.gradientMovement * 60.0
center.y += parameters.gradientMovement * 200.0
let startRadius: CGFloat = 0.0
let endRadius: CGFloat = 260.0
context.drawRadialGradient(gradient, startCenter: center, startRadius: startRadius, endCenter: center, endRadius: endRadius, options: .drawsAfterEndLocation)
}
// if let transitionAppearanceState = parameters.transitionState?.appearanceState {
// transitionAppearanceState.drawBackground(context: context, size: bounds.size)
// }
// parameters.appearanceState.drawBackground(context: context, size: bounds.size)
//
// if let transitionDrawingState = parameters.transitionState?.drawingState {
// transitionDrawingState.draw(context: context, size: bounds.size, foregroundColor: parameters.appearanceState.effectiveForegroundColor)
// }
// parameters.drawingState.draw(context: context, size: bounds.size, foregroundColor: parameters.appearanceState.effectiveForegroundColor)
//
// if let transitionAppearanceState = parameters.transitionState?.appearanceState {
// transitionAppearanceState.drawForeground(context: context, size: bounds.size)
// }
// parameters.appearanceState.drawForeground(context: context, size: bounds.size)
}
}
private let titleFont = Font.regular(17.0)
private let subtitleFont = Font.regular(13.0)
final class VoiceChatActionButton: HighlightTrackingButtonNode {
enum State {
enum ActiveState {
case cantSpeak
case muted
case on
}
case connecting
case active(state: ActiveState)
}
// private final class SemanticStatusNodeTransitionContext {
// let startTime: Double
// let duration: Double
// let previousStateContext: SemanticStatusNodeStateContext?
// let completion: () -> Void
//
// init(startTime: Double, duration: Double, previousStateContext: SemanticStatusNodeStateContext?, previousAppearanceContext: SemanticStatusNodeAppearanceContext?, completion: @escaping () -> Void) {
// self.startTime = startTime
// self.duration = duration
// self.previousStateContext = previousStateContext
// self.previousAppearanceContext = previousAppearanceContext
// self.completion = completion
// }
// }
private let smallBlob: BlobNode
private let mediumBlob: BlobNode
private let bigBlob: BlobNode
private let iconNode: ASImageNode
private let maxLevel: CGFloat
private let glowNode: ASImageNode
let titleLabel: ImmediateTextNode
let subtitleLabel: ImmediateTextNode
private var currentParams: (size: CGSize, state: State, title: String, subtitle: String)?
private var displayLinkAnimator: ConstantDisplayLinkAnimator?
private var audioLevel: CGFloat = 0.6
private var presentationAudioLevel: CGFloat = 0
typealias BlobRange = (min: CGFloat, max: CGFloat)
var imitateVoice = false
var previousImitateTimestamp: Double?
init(size: CGSize) {
let smallBlobRange: BlobRange = (0.6, 0.62)
let mediumBlobRange: BlobRange = (0.62, 0.87)
let bigBlobRange: BlobRange = (0.65, 1.00)
self.maxLevel = 4.0
self.glowNode = ASImageNode()
self.glowNode.alpha = 0.5
self.glowNode.image = generateImage(CGSize(width: 360.0, height: 360.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
let blue = UIColor(rgb: 0x0078ff)
var locations: [CGFloat] = [0.0, 0.4, 1.0]
let colors: [CGColor] = [blue.cgColor, blue.cgColor, blue.withAlphaComponent(0.0).cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
let center: CGPoint = CGPoint(x: 180.0, y: 180.0)
let startRadius: CGFloat = 0.0
let endRadius: CGFloat = 200.0
context.drawRadialGradient(gradient, startCenter: center, startRadius: startRadius, endCenter: center, endRadius: endRadius, options: .drawsAfterEndLocation)
})
self.glowNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 360.0, height: 360.0))
self.iconNode = ASImageNode()
self.titleLabel = ImmediateTextNode()
self.subtitleLabel = ImmediateTextNode()
self.smallBlob = BlobNode(
size: size,
pointsCount: 8,
minRandomness: 0.1,
maxRandomness: 0.1,
minSpeed: 0.2,
maxSpeed: 0.6,
minScale: smallBlobRange.min,
maxScale: smallBlobRange.max,
scaleSpeed: 0.2,
isCircle: true
)
self.smallBlob.alpha = 1.0
self.mediumBlob = BlobNode(
size: size,
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 1.5,
maxSpeed: 7,
minScale: mediumBlobRange.min,
maxScale: mediumBlobRange.max,
scaleSpeed: 0.2,
isCircle: false
)
self.mediumBlob.alpha = 0.65
self.bigBlob = BlobNode(
size: size,
pointsCount: 8,
minRandomness: 1,
maxRandomness: 1,
minSpeed: 1.5,
maxSpeed: 7,
minScale: bigBlobRange.min,
maxScale: bigBlobRange.max,
scaleSpeed: 0.2,
isCircle: false
)
self.bigBlob.alpha = 0.45
super.init()
self.addSubnode(self.glowNode)
self.addSubnode(self.titleLabel)
self.addSubnode(self.subtitleLabel)
self.addSubnode(self.bigBlob)
self.addSubnode(self.mediumBlob)
self.addSubnode(self.smallBlob)
self.iconNode.image = UIImage(bundleImageName: "Call/VoiceChatMicOff")
self.addSubnode(self.iconNode)
self.displayLinkAnimator = ConstantDisplayLinkAnimator() { [weak self] in
guard let strongSelf = self else { return }
if strongSelf.imitateVoice {
let timestamp = CACurrentMediaTime()
if let previousTimestamp = strongSelf.previousImitateTimestamp {
if timestamp - previousTimestamp > 0.05 {
strongSelf.previousImitateTimestamp = timestamp
strongSelf.updateLevel(CGFloat(Float.random(in: 0.3 ..< 4.0)))
}
} else {
strongSelf.previousImitateTimestamp = timestamp
}
} else {
strongSelf.previousImitateTimestamp = nil
strongSelf.audioLevel = 0.6
}
strongSelf.presentationAudioLevel = strongSelf.presentationAudioLevel * 0.9 + strongSelf.audioLevel * 0.1
strongSelf.smallBlob.level = strongSelf.presentationAudioLevel
strongSelf.mediumBlob.level = strongSelf.presentationAudioLevel
strongSelf.bigBlob.level = strongSelf.presentationAudioLevel
}
}
private(set) var isAnimating = false
func startAnimating() {
guard !isAnimating else { return }
isAnimating = true
mediumBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.15, removeOnCompletion: false)
bigBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.15, removeOnCompletion: false)
updateBlobsState()
displayLinkAnimator?.isPaused = false
}
func stopAnimating() {
guard isAnimating else { return }
isAnimating = false
mediumBlob.layer.animateScale(from: 1.0, to: 0.5, duration: 0.15, removeOnCompletion: false)
bigBlob.layer.animateScale(from: 1.0, to: 0.5, duration: 0.15, removeOnCompletion: false)
updateBlobsState()
displayLinkAnimator?.isPaused = true
}
private func updateBlobsState() {
if self.isAnimating {
if smallBlob.frame.size != .zero {
smallBlob.startAnimating()
mediumBlob.startAnimating()
bigBlob.startAnimating()
}
} else {
smallBlob.stopAnimating()
mediumBlob.stopAnimating()
bigBlob.stopAnimating()
}
}
func updateLevel(_ level: CGFloat) {
let normalizedLevel = min(1, max(level / maxLevel, 0))
smallBlob.updateSpeedLevel(to: normalizedLevel)
mediumBlob.updateSpeedLevel(to: normalizedLevel)
bigBlob.updateSpeedLevel(to: normalizedLevel)
audioLevel = normalizedLevel
}
func update(size: CGSize, state: State, title: String, subtitle: String, animated: Bool = false) {
let updatedTitle = self.currentParams?.title != title
let updatedSubtitle = self.currentParams?.subtitle != subtitle
self.currentParams = (size, state, title, subtitle)
self.smallBlob.frame = CGRect(origin: CGPoint(), size: size)
self.mediumBlob.frame = CGRect(origin: CGPoint(), size: size)
self.bigBlob.frame = CGRect(origin: CGPoint(), size: size)
self.updateBlobsState()
self.titleLabel.attributedText = NSAttributedString(string: title, font: titleFont, textColor: .white)
self.subtitleLabel.attributedText = NSAttributedString(string: subtitle, font: subtitleFont, textColor: .white)
if case let .active(state) = state {
if state == .muted {
self.smallBlob.loop = true
self.mediumBlob.loop = true
self.bigBlob.loop = true
self.imitateVoice = false
} else {
self.smallBlob.loop = false
self.mediumBlob.loop = false
self.bigBlob.loop = false
self.imitateVoice = true
}
}
if animated {
if let snapshotView = self.titleLabel.view.snapshotContentTree(), updatedTitle {
self.titleLabel.view.insertSubview(snapshotView, belowSubview: self.titleLabel.view)
snapshotView.frame = self.titleLabel.frame
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
self.titleLabel.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
if let snapshotView = self.subtitleLabel.view.snapshotContentTree(), updatedSubtitle {
self.subtitleLabel.view.insertSubview(snapshotView, belowSubview: self.subtitleLabel.view)
snapshotView.frame = self.subtitleLabel.frame
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
self.subtitleLabel.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
}
let titleSize = self.titleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
let totalHeight = titleSize.height + subtitleSize.height + 1.0
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height + 16.0 - totalHeight / 2.0) - 20.0), size: titleSize)
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
self.glowNode.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
if let image = self.iconNode.image {
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
}
}
// func updateLayout(size: CGSize, isOn: Bool) {
// if self.validSize != size {
// self.validSize = size
//
// self.backgroundNode.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1C1C1E))
// }
// if self.isOn != isOn {
// self.isOn = isOn
// self.foregroundNode.image = UIImage(bundleImageName: isOn ? "Call/VoiceChatMicOn" : "Call/VoiceChatMicOff")
// }
// self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
//
// if let image = self.foregroundNode.image {
// self.foregroundNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
// }
// }
}

View File

@ -11,11 +11,12 @@ import AccountContext
import Postbox
import TelegramCore
import SyncCore
import ItemListPeerItem
import MergeLists
import ItemListUI
import AppBundle
import RadialStatusNode
import ContextUI
import ShareController
import DeleteChatPeerActionSheetItem
private final class VoiceChatControllerTitleView: UIView {
private var theme: PresentationTheme
@ -80,41 +81,6 @@ private final class VoiceChatControllerTitleView: UIView {
}
}
private final class VoiceChatActionButton: HighlightTrackingButtonNode {
private let backgroundNode: ASImageNode
private let foregroundNode: ASImageNode
private var validSize: CGSize?
private var isOn: Bool?
init() {
self.backgroundNode = ASImageNode()
self.foregroundNode = ASImageNode()
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.foregroundNode)
}
func updateLayout(size: CGSize, isOn: Bool) {
if self.validSize != size {
self.validSize = size
self.backgroundNode.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1C1C1E))
}
if self.isOn != isOn {
self.isOn = isOn
self.foregroundNode.image = UIImage(bundleImageName: isOn ? "Call/VoiceChatMicOn" : "Call/VoiceChatMicOff")
}
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
if let image = self.foregroundNode.image {
self.foregroundNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
}
}
}
public final class VoiceChatController: ViewController {
private final class Node: ViewControllerTracingNode {
private struct ListTransition {
@ -127,7 +93,11 @@ public final class VoiceChatController: ViewController {
}
private final class Interaction {
let peerContextAction: (Peer, ASDisplayNode, ContextGesture?) -> Void
init(peerContextAction: @escaping (Peer, ASDisplayNode, ContextGesture?) -> Void) {
self.peerContextAction = peerContextAction
}
}
private struct PeerEntry: Comparable, Identifiable {
@ -152,32 +122,26 @@ public final class VoiceChatController: ViewController {
return lhs.participant.peer.id < rhs.participant.peer.id
}
func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: Interaction) -> ListViewItem {
func item(context: AccountContext, presentationData: PresentationData, interaction: Interaction) -> ListViewItem {
let peer = self.participant.peer
let text: ItemListPeerItemText
let text: VoiceChatParticipantItem.ParticipantText
switch self.state {
case .inactive:
text = .presence
case .listening:
//TODO:localize
text = .text("listening", .accent)
text = .text(presentationData.strings.VoiceChat_StatusListening, .accent)
case .speaking:
//TODO:localize
text = .text("speaking", .constructive)
text = .text(presentationData.strings.VoiceChat_StatusSpeaking, .constructive)
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: context, peer: peer, height: .peerList, presence: self.participant.presences[self.participant.peer.id], text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
//arguments.deleteIncludePeer(peer.peerId)
})]), switchValue: nil, enabled: true, selectable: false, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
//arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) })
}, removePeer: { id in
//arguments.deleteIncludePeer(id)
}, noInsets: true)
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, presence: self.participant.presences[self.participant.peer.id], text: text, enabled: true, action: nil, contextAction: { node, gesture in
interaction.peerContextAction(peer, node, gesture)
})
}
}
private func preparedTransition(from fromEntries: [PeerEntry], to toEntries: [PeerEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, context: AccountContext, presentationData: ItemListPresentationData, interaction: Interaction) -> ListTransition {
private func preparedTransition(from fromEntries: [PeerEntry], to toEntries: [PeerEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, context: AccountContext, presentationData: PresentationData, interaction: Interaction) -> ListTransition {
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
@ -194,12 +158,12 @@ public final class VoiceChatController: ViewController {
private var presentationData: PresentationData
private var darkTheme: PresentationTheme
private let optionsButton: VoiceChatOptionsButton
private let contentContainer: ASDisplayNode
private let listNode: ListView
private let audioOutputNode: CallControllerButtonItemNode
private let leaveNode: CallControllerButtonItemNode
private let actionButton: VoiceChatActionButton
private let radialStatus: RadialStatusNode
private let statusLabel: ImmediateTextNode
private var enqueuedTransitions: [ListTransition] = []
@ -236,7 +200,9 @@ public final class VoiceChatController: ViewController {
self.call = call
self.presentationData = sharedContext.currentPresentationData.with { $0 }
self.darkTheme = defaultDarkPresentationTheme
self.darkTheme = defaultDarkColorPresentationTheme
self.optionsButton = VoiceChatOptionsButton()
self.contentContainer = ASDisplayNode()
@ -248,23 +214,63 @@ public final class VoiceChatController: ViewController {
self.audioOutputNode = CallControllerButtonItemNode()
self.leaveNode = CallControllerButtonItemNode()
self.actionButton = VoiceChatActionButton()
self.actionButton = VoiceChatActionButton(size: CGSize(width: 244.0, height: 244.0))
self.statusLabel = ImmediateTextNode()
self.radialStatus = RadialStatusNode(backgroundNodeColor: .clear)
super.init()
self.itemInteraction = Interaction()
self.itemInteraction = Interaction(peerContextAction: { [weak self] peer, sourceNode, gesture in
guard let strongSelf = self, let controller = strongSelf.controller, let sourceNode = sourceNode as? ContextExtractedContentContainingNode else {
return
}
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_MutePeer, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Call"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
})))
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_RemovePeer, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
guard let strongSelf = self else {
return
}
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme))
var items: [ActionSheetItem] = []
items.append(DeleteChatPeerActionSheetItem(context: strongSelf.context, peer: peer, chatPeer: peer, action: .removeFromGroup, strings: strongSelf.presentationData.strings, nameDisplayOrder: strongSelf.presentationData.nameDisplayOrder))
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
}))
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
strongSelf.controller?.present(actionSheet, in: .window(.root))
})))
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: sourceNode)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController)
})
self.backgroundColor = .black
self.contentContainer.addSubnode(self.actionButton)
self.contentContainer.addSubnode(self.listNode)
self.contentContainer.addSubnode(self.audioOutputNode)
self.contentContainer.addSubnode(self.leaveNode)
self.contentContainer.addSubnode(self.actionButton)
self.contentContainer.addSubnode(self.statusLabel)
self.contentContainer.addSubnode(self.radialStatus)
self.addSubnode(self.contentContainer)
@ -360,6 +366,48 @@ public final class VoiceChatController: ViewController {
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
self.audioOutputNode.addTarget(self, action: #selector(self.audioOutputPressed), forControlEvents: .touchUpInside)
self.optionsButton.contextAction = { [weak self, weak optionsButton] sourceNode, gesture in
guard let strongSelf = self, let controller = strongSelf.controller, let strongOptionsButton = optionsButton else {
return
}
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_SpeakPermissionEveryone, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
})))
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_SpeakPermissionAdmin, icon: { _ in return nil}, action: { _, f in
f(.dismissWithoutContent)
})))
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_Share, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor)
}, action: { [weak self] _, f in
f(.dismissWithoutContent)
if let strongSelf = self {
let shareController = ShareController(context: strongSelf.context, subject: .url("url"), forcedTheme: strongSelf.darkTheme)
strongSelf.controller?.present(shareController, in: .window(.root))
}
})))
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { _, f in
f(.dismissWithoutContent)
})))
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: strongOptionsButton.extractedContainerNode)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.controller?.presentInGlobalOverlay(contextController)
}
let optionsButtonItem = UIBarButtonItem(customDisplayNode: self.optionsButton)!
optionsButtonItem.target = self
optionsButtonItem.action = #selector(self.rightNavigationButtonAction)
self.controller?.navigationItem.setRightBarButton(optionsButtonItem, animated: false)
}
deinit {
@ -372,6 +420,10 @@ public final class VoiceChatController: ViewController {
self.memberStatesDisposable?.dispose()
}
@objc private func rightNavigationButtonAction() {
self.optionsButton.contextAction?(self.optionsButton.containerNode, nil)
}
@objc private func leavePressed() {
self.leaveDisposable.set((self.call.leave()
|> deliverOnMainQueue).start(completed: { [weak self] in
@ -435,13 +487,6 @@ public final class VoiceChatController: ViewController {
}))
}
if hasMute {
items.append(CallRouteActionSheetItem(title: self.presentationData.strings.Call_AudioRouteMute, icon: generateScaledImage(image: UIImage(bundleImageName: "Call/CallMuteButton"), size: CGSize(width: 48.0, height: 48.0), opaque: false), selected: self.callState?.isMuted ?? true, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
self?.call.toggleIsMuted()
}))
}
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: self.presentationData.strings.Call_AudioRouteHide, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
@ -458,10 +503,11 @@ public final class VoiceChatController: ViewController {
transition.updateFrame(node: self.contentContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
let bottomAreaHeight: CGFloat = 302.0
let bottomAreaHeight: CGFloat = 333.0
let listOrigin = CGPoint(x: 16.0, y: navigationHeight + 10.0)
let listFrame = CGRect(origin: listOrigin, size: CGSize(width: layout.size.width - 16.0 * 2.0, height: max(1.0, layout.size.height - bottomAreaHeight - listOrigin.y)))
// let listFrame = CGRect(origin: listOrigin, size: CGSize(width: layout.size.width - 16.0 * 2.0, height: max(1.0, layout.size.height - bottomAreaHeight - listOrigin.y)))
let listFrame = CGRect(origin: listOrigin, size: CGSize(width: layout.size.width - 16.0 * 2.0, height: 168.0))
transition.updateFrame(node: self.listNode, frame: listFrame)
@ -471,7 +517,7 @@ public final class VoiceChatController: ViewController {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
let sideButtonSize = CGSize(width: 60.0, height: 60.0)
let centralButtonSize = CGSize(width: 144.0, height: 144.0)
let centralButtonSize = CGSize(width: 244.0, height: 244.0)
let sideButtonInset: CGFloat = 27.0
var audioMode: CallControllerButtonsSpeakerMode = .none
@ -507,7 +553,7 @@ public final class VoiceChatController: ViewController {
soundImage = .speaker
case .speaker:
soundImage = .speaker
soundAppearance = .blurred(isFilled: true)
soundAppearance = .blurred(isFilled: false)
case .headphones:
soundImage = .bluetooth
case let .bluetooth(type):
@ -521,11 +567,9 @@ public final class VoiceChatController: ViewController {
}
}
//TODO:localize
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: "audio", transition: .immediate)
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: self.presentationData.strings.VoiceChat_Audio, transition: .immediate)
//TODO:localize
self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.redDimmed), image: .end), text: "leave", transition: .immediate)
self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.redDimmed), image: .end), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
transition.updateFrame(node: self.audioOutputNode, frame: CGRect(origin: CGPoint(x: sideButtonInset, y: layout.size.height - bottomAreaHeight + floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
transition.updateFrame(node: self.leaveNode, frame: CGRect(origin: CGPoint(x: layout.size.width - sideButtonInset - sideButtonSize.width, y: layout.size.height - bottomAreaHeight + floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
@ -533,32 +577,35 @@ public final class VoiceChatController: ViewController {
let actionButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - centralButtonSize.width) / 2.0), y: layout.size.height - bottomAreaHeight + floor((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
var isMicOn = false
let actionButtonState: VoiceChatActionButton.State
let actionButtonTitle: String
let actionButtonSubtitle: String
if let callState = callState {
isMicOn = !callState.isMuted
switch callState.networkState {
case .connecting:
self.radialStatus.isHidden = false
self.statusLabel.attributedText = NSAttributedString(string: "Connecting...", font: Font.regular(17.0), textColor: .white)
case .connected:
self.radialStatus.isHidden = true
// switch callState.networkState {
// case .connecting:
// actionButtonState = .connecting
// actionButtonTitle = "Connecting..."
// actionButtonSubtitle = ""
// case .connected:
actionButtonState = .active(state: isMicOn ? .on : .muted)
if isMicOn {
self.statusLabel.attributedText = NSAttributedString(string: "You're Live", font: Font.regular(17.0), textColor: .white)
actionButtonTitle = self.presentationData.strings.VoiceChat_Live
actionButtonSubtitle = ""
} else {
self.statusLabel.attributedText = NSAttributedString(string: "Unmute", font: Font.regular(17.0), textColor: .white)
actionButtonTitle = self.presentationData.strings.VoiceChat_Unmute
actionButtonSubtitle = self.presentationData.strings.VoiceChat_UnmuteHelp
}
// }
} else {
actionButtonState = .connecting
actionButtonTitle = self.presentationData.strings.VoiceChat_Connecting
actionButtonSubtitle = ""
}
let statusSize = self.statusLabel.updateLayout(CGSize(width: layout.size.width, height: .greatestFiniteMagnitude))
self.statusLabel.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - statusSize.width) / 2.0), y: actionButtonFrame.maxY + 12.0), size: statusSize)
self.radialStatus.transitionToState(.progress(color: UIColor(rgb: 0x00ACFF), lineWidth: 3.3, value: nil, cancelEnabled: false), animated: false)
self.radialStatus.frame = actionButtonFrame.insetBy(dx: -3.3, dy: -3.3)
}
self.actionButton.updateLayout(size: centralButtonSize, isOn: isMicOn)
self.actionButton.update(size: centralButtonSize, state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, animated: true)
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
if isFirstTime {
@ -574,10 +621,18 @@ public final class VoiceChatController: ViewController {
self.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.actionButton.startAnimating()
self.actionButton.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
self.audioOutputNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
self.leaveNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
self.actionButton.titleLabel.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
self.actionButton.subtitleLabel.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
self.audioOutputNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
self.leaveNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
self.contentContainer.layer.animateBoundsOriginYAdditive(from: 80.0, to: 0.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
@ -674,8 +729,7 @@ public final class VoiceChatController: ViewController {
self.currentEntries = entries
let presentationData = ItemListPresentationData(theme: self.darkTheme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings)
let presentationData = self.presentationData.withUpdated(theme: self.darkTheme)
let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!)
self.enqueueTransition(transition)
}
@ -710,7 +764,7 @@ public final class VoiceChatController: ViewController {
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
let backItem = UIBarButtonItem(backButtonAppearanceWithTitle: "Chat", target: self, action: #selector(self.closePressed))
let backItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.VoiceChat_BackTitle, target: self, action: #selector(self.closePressed))
self.navigationItem.leftBarButtonItem = backItem
self.statusBar.statusBarStyle = .White
@ -774,3 +828,24 @@ public final class VoiceChatController: ViewController {
self.controllerNode.containerLayoutUpdated(layout, navigationHeight: self.navigationHeight, transition: transition)
}
}
private final class VoiceChatContextExtractedContentSource: ContextExtractedContentSource {
let keepInPlace: Bool = true
let ignoreContentTouches: Bool = true
private let controller: ViewController
private let sourceNode: ContextExtractedContentContainingNode
init(controller: ViewController, sourceNode: ContextExtractedContentContainingNode) {
self.controller = controller
self.sourceNode = sourceNode
}
func takeView() -> ContextControllerTakeViewInfo? {
return ContextControllerTakeViewInfo(contentContainingNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds)
}
func putBack() -> ContextControllerPutBackViewInfo? {
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds)
}
}

View File

@ -0,0 +1,63 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
final class VoiceChatOptionsButton: HighlightableButtonNode {
let extractedContainerNode: ContextExtractedContentContainingNode
let containerNode: ContextControllerSourceNode
private let iconNode: ASImageNode
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
init() {
self.extractedContainerNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
self.containerNode.isGestureEnabled = false
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
super.init()
self.containerNode.addSubnode(self.extractedContainerNode)
self.extractedContainerNode.contentNode.addSubnode(self.iconNode)
self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode
self.addSubnode(self.containerNode)
self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else {
return
}
strongSelf.contextAction?(strongSelf.containerNode, gesture)
}
self.iconNode.image = generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor(rgb: 0x1c1c1e).cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(x: 6.0, y: 12.0, width: 4.0, height: 4.0))
context.fillEllipse(in: CGRect(x: 12.0, y: 12.0, width: 4.0, height: 4.0))
context.fillEllipse(in: CGRect(x: 18.0, y: 12.0, width: 4.0, height: 4.0))
})
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 28.0, height: 28.0))
self.extractedContainerNode.frame = self.containerNode.bounds
self.iconNode.frame = self.containerNode.bounds
}
override func didLoad() {
super.didLoad()
self.view.isOpaque = false
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: 28.0, height: 28.0)
}
func onLayout() {
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ import SyncCore
import TelegramUIPreferences
public let defaultDarkPresentationTheme = makeDefaultDarkPresentationTheme(preview: false)
public let defaultDarkColorPresentationTheme = customizeDefaultDarkPresentationTheme(theme: defaultDarkPresentationTheme, editing: false, title: nil, accentColor: UIColor(rgb: 0x007aff), backgroundColors: nil, bubbleColors: nil, wallpaper: nil)
public func customizeDefaultDarkPresentationTheme(theme: PresentationTheme, editing: Bool, title: String?, accentColor: UIColor?, backgroundColors: (UIColor, UIColor?)?, bubbleColors: (UIColor, UIColor?)?, wallpaper forcedWallpaper: TelegramWallpaper? = nil) -> PresentationTheme {
if (theme.referenceTheme != .night) {

View File

@ -90,6 +90,10 @@ public final class PresentationData: Equatable {
self.largeEmoji = largeEmoji
}
public func withUpdated(theme: PresentationTheme) -> PresentationData {
return PresentationData(strings: self.strings, theme: theme, autoNightModeTriggered: self.autoNightModeTriggered, chatWallpaper: self.chatWallpaper, chatFontSize: self.chatFontSize, chatBubbleCorners: self.chatBubbleCorners, listsFontSize: self.listsFontSize, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, nameSortOrder: self.nameSortOrder, disableAnimations: self.disableAnimations, largeEmoji: self.largeEmoji)
}
public static func ==(lhs: PresentationData, rhs: PresentationData) -> Bool {
return lhs.strings === rhs.strings && lhs.theme === rhs.theme && lhs.autoNightModeTriggered == rhs.autoNightModeTriggered && lhs.chatWallpaper == rhs.chatWallpaper && lhs.chatFontSize == rhs.chatFontSize && lhs.chatBubbleCorners == rhs.chatBubbleCorners && lhs.listsFontSize == rhs.listsFontSize && lhs.dateTimeFormat == rhs.dateTimeFormat && lhs.disableAnimations == rhs.disableAnimations && lhs.largeEmoji == rhs.largeEmoji
}

View File

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

View File

@ -0,0 +1 @@
{"v":"5.5.9","fr":60,"ip":0,"op":20,"w":72,"h":72,"nm":"ic_mute","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Path 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[34.5,37.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-8.5,-8.5],[8.5,8.5]],"c":false},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.33,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Обводка 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Path 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Обрезать контуры 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":20,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Path 5","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[34.5,37.5,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-8.5,-8.5],[8.5,8.5]],"c":false},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.109803922474,0.109803922474,0.117647059262,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Обводка 2","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Path 4","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Обрезать контуры 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":20,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Icon","tt":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[36,37.2,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.36,0],[0,-0.36],[3.81,-0.34],[0,0],[0.37,0],[0.04,0.32],[0,0],[0,0],[0,3.9],[-0.36,0],[0,-0.36],[-3.38,0],[0,3.39]],"o":[[0.37,0],[0,3.9],[0,0],[0,0.37],[-0.33,0],[0,0],[0,0],[-3.81,-0.34],[0,-0.36],[0.37,0],[0,3.39],[3.39,0],[0,-0.36]],"v":[[6.796,-1.464],[7.466,-0.804],[0.666,6.636],[0.666,9.196],[-0.004,9.866],[-0.654,9.286],[-0.664,9.196],[-0.664,6.636],[-7.464,-0.804],[-6.804,-1.464],[-6.134,-0.804],[-0.004,5.336],[6.136,-0.804]],"c":true},"ix":2},"nm":"Контур 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-2.35,0],[-0.09,-2.28],[0,0],[0,0],[2.36,0],[0.1,2.27],[0,0],[0,0]],"o":[[2.3,0],[0,0],[0,0],[0,2.36],[-2.29,0],[0,0],[0,0],[0,-2.35]],"v":[[-0.004,-9.864],[4.256,-5.774],[4.266,-5.604],[4.266,-0.804],[-0.004,3.466],[-4.264,-0.624],[-4.264,-0.804],[-4.264,-5.604]],"c":true},"ix":2},"nm":"Контур 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[1.62,0],[0.09,-1.55],[0,0],[0,0],[-1.63,0],[-0.08,1.55],[0,0],[0,0]],"o":[[-1.57,0],[0,0],[0,0],[0,1.62],[1.57,0],[0,0],[0,0],[0,-1.63]],"v":[[0.004,-8.536],[-2.936,-5.756],[-2.936,-5.596],[-2.936,-0.796],[0.004,2.134],[2.934,-0.646],[2.934,-0.796],[2.934,-5.596]],"c":true},"ix":2},"nm":"Контур 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Объединить контуры 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Заливка 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Преобразовать"}],"nm":"Icon","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":20,"st":0,"bm":0}],"markers":[]}

View File

@ -21,8 +21,6 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
}
}
var tapped: (() -> Void)?
override init() {
self.containerNode = ContextControllerSourceNode()
self.avatarNode = AvatarNode(font: normalFont)
@ -41,9 +39,6 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 37.0, height: 37.0)).offsetBy(dx: 10.0, dy: 1.0)
self.avatarNode.frame = self.containerNode.bounds
/*self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 37.0, height: 37.0))
self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 37.0, height: 37.0))*/
}
override func didLoad() {
@ -51,19 +46,6 @@ final class ChatAvatarNavigationNode: ASDisplayNode {
self.view.isOpaque = false
}
@objc private func avatarTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
if case .ended = recognizer.state {
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
switch gesture {
case .tap:
self.tapped?()
default:
break
}
}
}
}
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: 37.0, height: 37.0)
}

View File

@ -5979,11 +5979,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, passthroughTouches: true)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}, joinGroupCall: { [weak self] activeCall in
}, editMessageMedia: { [weak self] messageId, draw in
if let strongSelf = self {
strongSelf.controllerInteraction?.editMessageMedia(messageId, draw)
}
}, joinGroupCall: { [weak self] messageId in
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
return
}
@ -6011,6 +6006,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
}
}, editMessageMedia: { [weak self] messageId, draw in
if let strongSelf = self {
strongSelf.controllerInteraction?.editMessageMedia(messageId, draw)
}
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get(), inlineSearch: self.performingInlineSearch.get()))
do {

View File

@ -207,7 +207,7 @@ final class ChatPanelInterfaceInteraction {
scrollToTop: @escaping () -> Void,
viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void,
activatePinnedListPreview: @escaping (ASDisplayNode, ContextGesture) -> Void,
joinGroupCall: @escaping (MessageId) -> Void,
joinGroupCall: @escaping (CachedChannelData.ActiveCall) -> Void,
editMessageMedia: @escaping (MessageId, Bool) -> Void,
statuses: ChatPanelInterfaceInteractionStatuses?
) {

View File

@ -132,8 +132,8 @@ final class ChatRecentActionsController: TelegramBaseController {
}, scrollToTop: {
}, viewReplies: { _, _ in
}, activatePinnedListPreview: { _, _ in
}, editMessageMedia: { _, _ in
}, joinGroupCall: { _ in
}, editMessageMedia: { _, _ in
}, statuses: nil)
self.navigationItem.titleView = self.titleView

View File

@ -438,8 +438,8 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, scrollToTop: {
}, viewReplies: { _, _ in
}, activatePinnedListPreview: { _, _ in
}, editMessageMedia: { _, _ in
}, joinGroupCall: { _ in
}, editMessageMedia: { _, _ in
}, statuses: nil)
self.selectionPanel.interfaceInteraction = interfaceInteraction