mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Voice Chat UI improvements
This commit is contained in:
parent
811f6981ca
commit
7d3353c01b
@ -5917,8 +5917,6 @@ Sorry for the inconvenience.";
|
||||
"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";
|
||||
|
||||
@ -5944,7 +5942,6 @@ Sorry for the inconvenience.";
|
||||
|
||||
"VoiceChat.UnmutePeer" = "Allow to Speak";
|
||||
"VoiceChat.MutePeer" = "Mute";
|
||||
"VoiceChat.InvitePeer" = "Invite";
|
||||
"VoiceChat.RemovePeer" = "Remove";
|
||||
"VoiceChat.RemovePeerConfirmation" = "Are you sure you want to remove %@ from the group chat?";
|
||||
"VoiceChat.RemovePeerRemove" = "Remove";
|
||||
@ -5985,3 +5982,6 @@ Sorry for the inconvenience.";
|
||||
"CHAT_VOICECHAT_START" = "%1$@ has started voice chat in the group %2$@";
|
||||
"CHAT_VOICECHAT_INVITE" = "%1$@ has invited %3$@ in the group %2$@";
|
||||
"CHAT_VOICECHAT_INVITE_YOU" = "%1$@ has invited you to voice chat in the group %1$@";
|
||||
|
||||
"Call.VoiceChatInProgressTitle" = "Voice Chat in Progress";
|
||||
"Call.VoiceChatInProgressMessageCall" = "Leave voice chat in %1$@ and start a call with %2$@?";
|
||||
|
@ -125,8 +125,7 @@ private final class Curve {
|
||||
self.minOffset = minOffset
|
||||
self.maxOffset = maxOffset
|
||||
|
||||
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
|
||||
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
|
||||
self.smoothness = 0.35
|
||||
|
||||
self.currentOffset = minOffset
|
||||
|
||||
@ -169,7 +168,13 @@ private final class Curve {
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
if let (startTime, duration) = self.transitionArguments, duration > 0.0 {
|
||||
self.transition = max(0.0, min(1.0, CGFloat((timestamp - startTime) / duration)))
|
||||
var t = max(0.0, min(1.0, CGFloat((timestamp - startTime) / duration)))
|
||||
if t < 0.5 {
|
||||
t = 2 * t * t
|
||||
} else {
|
||||
t = -1 + (4 - 2 * t) * t
|
||||
}
|
||||
self.transition = t
|
||||
if self.transition < 1.0 {
|
||||
} else {
|
||||
if self.loop {
|
||||
@ -187,12 +192,12 @@ private final class Curve {
|
||||
private func generateNextCurve(for size: CGSize) -> [CGPoint] {
|
||||
let randomness = minRandomness + (maxRandomness - minRandomness) * speedLevel
|
||||
return curve(pointsCount: pointsCount, randomness: randomness).map {
|
||||
return CGPoint(x: $0.x * CGFloat(size.width), y: size.height - 17.0 + $0.y * 12.0)
|
||||
return CGPoint(x: $0.x * CGFloat(size.width), y: size.height - 18.0 + $0.y * 12.0)
|
||||
}
|
||||
}
|
||||
|
||||
private func curve(pointsCount: Int, randomness: CGFloat) -> [CGPoint] {
|
||||
let segment = 1.0 / CGFloat(pointsCount)
|
||||
let segment = 1.0 / CGFloat(pointsCount - 1)
|
||||
|
||||
let rgen = { () -> CGFloat in
|
||||
let accuracy: UInt32 = 1000
|
||||
@ -204,7 +209,7 @@ private final class Curve {
|
||||
let points = (0 ..< pointsCount).map { i -> CGPoint in
|
||||
let randPointOffset = (rangeStart + CGFloat(rgen()) * (1 - rangeStart)) / 2
|
||||
let segmentRandomness: CGFloat = randomness
|
||||
|
||||
|
||||
let pointX: CGFloat
|
||||
let pointY: CGFloat
|
||||
let randomXDelta: CGFloat
|
||||
@ -218,9 +223,10 @@ private final class Curve {
|
||||
randomXDelta = 0.0
|
||||
} else {
|
||||
pointX = segment * CGFloat(i)
|
||||
pointY = cos(segment * CGFloat(i)) * ((segmentRandomness * CGFloat(arc4random_uniform(100)) / CGFloat(100)) - segmentRandomness * 0.5) * randPointOffset
|
||||
pointY = ((segmentRandomness * CGFloat(arc4random_uniform(100)) / CGFloat(100)) - segmentRandomness * 0.5) * randPointOffset
|
||||
randomXDelta = segment - segment * randPointOffset
|
||||
}
|
||||
|
||||
return CGPoint(x: pointX + randomXDelta, y: pointY)
|
||||
}
|
||||
|
||||
@ -264,9 +270,9 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
||||
let bigCurveRange: CurveRange = (0.1, 1.0)
|
||||
|
||||
let size = CGSize(width: 375.0, height: 44.0)
|
||||
let smallCurve = Curve(size: size, alpha: 1.0, pointsCount: 6, minRandomness: 1, maxRandomness: 1, minSpeed: 2.5, maxSpeed: 7, minOffset: smallCurveRange.min, maxOffset: smallCurveRange.max)
|
||||
let mediumCurve = Curve(size: size, alpha: 0.55, pointsCount: 6, minRandomness: 1, maxRandomness: 2, minSpeed: 2.5, maxSpeed: 7, minOffset: mediumCurveRange.min, maxOffset: mediumCurveRange.max)
|
||||
let largeCurve = Curve(size: size, alpha: 0.35, pointsCount: 6, minRandomness: 1, maxRandomness: 2, minSpeed: 2.5, maxSpeed: 7, minOffset: bigCurveRange.min, maxOffset: bigCurveRange.max)
|
||||
let smallCurve = Curve(size: size, alpha: 1.0, pointsCount: 7, minRandomness: 1, maxRandomness: 1.3, minSpeed: 1.0, maxSpeed: 3.5, minOffset: smallCurveRange.min, maxOffset: smallCurveRange.max)
|
||||
let mediumCurve = Curve(size: size, alpha: 0.55, pointsCount: 7, minRandomness: 1.2, maxRandomness: 1.5, minSpeed: 1.0, maxSpeed: 4.5, minOffset: mediumCurveRange.min, maxOffset: mediumCurveRange.max)
|
||||
let largeCurve = Curve(size: size, alpha: 0.35, pointsCount: 7, minRandomness: 1.2, maxRandomness: 1.7, minSpeed: 1.0, maxSpeed: 6.0, minOffset: bigCurveRange.min, maxOffset: bigCurveRange.max)
|
||||
|
||||
self.curves = [smallCurve, mediumCurve, largeCurve]
|
||||
|
||||
@ -314,7 +320,6 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
||||
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||
self?.updateAnimations()
|
||||
})
|
||||
animator.frameInterval = 2
|
||||
self.animator = animator
|
||||
}
|
||||
animator.isPaused = false
|
||||
@ -526,12 +531,14 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
if let currentPeer = self.currentPeer {
|
||||
title = currentPeer.displayTitle(strings: strings, displayOrder: self.nameDisplayOrder)
|
||||
}
|
||||
var membersCount: Int32?
|
||||
if let groupCallState = self.currentGroupCallState {
|
||||
if groupCallState.numberOfActiveSpeakers != 0 {
|
||||
subtitle = strings.VoiceChat_Panel_MembersSpeaking(Int32(groupCallState.numberOfActiveSpeakers))
|
||||
} else {
|
||||
subtitle = strings.VoiceChat_Panel_Members(Int32(max(1, groupCallState.participantCount)))
|
||||
}
|
||||
membersCount = Int32(max(1, groupCallState.participantCount))
|
||||
} else if let content = self.currentContent, case .groupCall = content {
|
||||
membersCount = 1
|
||||
}
|
||||
if let membersCount = membersCount {
|
||||
subtitle = strings.VoiceChat_Panel_Members(membersCount)
|
||||
}
|
||||
}
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(13.0), textColor: .white)
|
||||
@ -556,6 +563,6 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
||||
self.subtitleNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin + animationSize + iconSpacing + titleSize.width + spacing, y: verticalOrigin + floor((contentHeight - subtitleSize.height) / 2.0)), size: subtitleSize)
|
||||
|
||||
self.backgroundNode.speaking = !self.currentIsMuted
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 17.0))
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + 18.0))
|
||||
}
|
||||
}
|
||||
|
@ -282,17 +282,12 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
|
||||
let membersText: String
|
||||
let membersTextIsActive: Bool
|
||||
if summaryState.numberOfActiveSpeakers != 0 {
|
||||
membersText = strongSelf.strings.VoiceChat_Panel_MembersSpeaking(Int32(summaryState.numberOfActiveSpeakers))
|
||||
membersTextIsActive = true
|
||||
if summaryState.participantCount == 0 {
|
||||
membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin
|
||||
} else {
|
||||
if summaryState.participantCount == 0 {
|
||||
membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin
|
||||
} else {
|
||||
membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount))
|
||||
}
|
||||
membersTextIsActive = false
|
||||
membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount))
|
||||
}
|
||||
membersTextIsActive = false
|
||||
|
||||
if strongSelf.textIsActive != membersTextIsActive {
|
||||
strongSelf.textIsActive = membersTextIsActive
|
||||
@ -373,17 +368,12 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
|
||||
let membersText: String
|
||||
let membersTextIsActive: Bool
|
||||
if data.numberOfActiveSpeakers != 0 {
|
||||
membersText = self.strings.VoiceChat_Panel_MembersSpeaking(Int32(data.numberOfActiveSpeakers))
|
||||
membersTextIsActive = true
|
||||
if data.participantCount == 0 {
|
||||
membersText = self.strings.VoiceChat_Panel_TapToJoin
|
||||
} else {
|
||||
if data.participantCount == 0 {
|
||||
membersText = self.strings.VoiceChat_Panel_TapToJoin
|
||||
} else {
|
||||
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
|
||||
}
|
||||
membersTextIsActive = false
|
||||
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
|
||||
}
|
||||
membersTextIsActive = false
|
||||
|
||||
if self.textIsActive != membersTextIsActive {
|
||||
self.textIsActive = membersTextIsActive
|
||||
|
@ -709,7 +709,6 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
peerId: peerId,
|
||||
peer: nil
|
||||
)
|
||||
call.sourcePanel = sourcePanel
|
||||
strongSelf.updateCurrentGroupCall(call)
|
||||
strongSelf.currentGroupCallPromise.set(.single(call))
|
||||
strongSelf.hasActiveGroupCallsPromise.set(true)
|
||||
|
File diff suppressed because it is too large
Load Diff
260
submodules/TelegramCallsUI/Sources/VoiceChatActionItem.swift
Normal file
260
submodules/TelegramCallsUI/Sources/VoiceChatActionItem.swift
Normal file
@ -0,0 +1,260 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
|
||||
public enum VoiceChatActionItemIcon : Equatable {
|
||||
case none
|
||||
case generic(UIImage)
|
||||
|
||||
public var image: UIImage? {
|
||||
switch self {
|
||||
case .none:
|
||||
return nil
|
||||
case let .generic(image):
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: VoiceChatActionItemIcon, rhs: VoiceChatActionItemIcon) -> Bool {
|
||||
switch lhs {
|
||||
case .none:
|
||||
if case .none = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .generic(image):
|
||||
if case .generic(image) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VoiceChatActionItem: ListViewItem {
|
||||
let presentationData: ItemListPresentationData
|
||||
let title: String
|
||||
let icon: VoiceChatActionItemIcon
|
||||
let action: () -> Void
|
||||
|
||||
init(presentationData: ItemListPresentationData, title: String, icon: VoiceChatActionItemIcon, action: @escaping () -> Void) {
|
||||
self.presentationData = presentationData
|
||||
self.title = title
|
||||
self.icon = icon
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = VoiceChatActionItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, false, nextItem == nil)
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? VoiceChatActionItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, false, nextItem == nil)
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var selectable: Bool = true
|
||||
func selected(listView: ListView){
|
||||
self.action()
|
||||
listView.clearHighlightAnimated(true)
|
||||
}
|
||||
}
|
||||
|
||||
class VoiceChatActionItemNode: ListViewItemNode {
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
private let titleNode: TextNode
|
||||
|
||||
private let activateArea: AccessibilityAreaNode
|
||||
|
||||
private var item: VoiceChatActionItem?
|
||||
|
||||
init() {
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
self.bottomStripeNode = ASDisplayNode()
|
||||
self.bottomStripeNode.isLayerBacked = true
|
||||
|
||||
self.titleNode = TextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.isLayerBacked = true
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
|
||||
self.activateArea = AccessibilityAreaNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.activateArea)
|
||||
|
||||
self.activateArea.activate = { [weak self] in
|
||||
self?.item?.action()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: VoiceChatActionItem, _ params: ListViewItemLayoutParams, _ firstWithHeader: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, params, firstWithHeader, last in
|
||||
var updatedTheme: PresentationTheme?
|
||||
|
||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||
updatedTheme = item.presentationData.theme
|
||||
}
|
||||
|
||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize)
|
||||
|
||||
var leftInset: CGFloat = 16.0 + params.leftInset
|
||||
if case .generic = item.icon {
|
||||
leftInset += 49.0
|
||||
}
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.title, font: titleFont, textColor: UIColor(rgb: 0xffffff)), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - 10.0 - leftInset - params.rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentHeight: CGFloat = 12.0 * 2.0 + titleLayout.size.height
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: contentHeight)
|
||||
let insets = UIEdgeInsets(top: firstWithHeader ? 29.0 : 0.0, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
strongSelf.activateArea.accessibilityLabel = item.title
|
||||
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: params.leftInset, y: 0.0), size: CGSize(width: layout.contentSize.width - params.leftInset - params.rightInset, height: layout.contentSize.height))
|
||||
|
||||
if let _ = updatedTheme {
|
||||
strongSelf.topStripeNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.presentationData.theme.list.itemPlainSeparatorColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||
|
||||
strongSelf.iconNode.image = generateTintedImage(image: item.icon.image, color: UIColor(rgb: 0xffffff))
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
|
||||
let titleOffset = leftInset
|
||||
let hideBottomStripe: Bool = last
|
||||
if let image = item.icon.image {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - image.size.width) / 2.0) + 3.0, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
|
||||
strongSelf.iconNode.frame = iconFrame
|
||||
}
|
||||
|
||||
if strongSelf.topStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.topStripeNode, at: 0)
|
||||
}
|
||||
if strongSelf.bottomStripeNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 1)
|
||||
}
|
||||
|
||||
strongSelf.topStripeNode.isHidden = true
|
||||
strongSelf.bottomStripeNode.isHidden = hideBottomStripe
|
||||
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentSize.height - separatorHeight), size: CGSize(width: params.width - leftInset, height: separatorHeight))
|
||||
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: titleOffset, y: floor((contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: contentSize.height + UIScreenPixel + UIScreenPixel))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
||||
super.setHighlighted(highlighted, at: point, animated: animated)
|
||||
|
||||
if highlighted {
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
if self.highlightedBackgroundNode.supernode == nil {
|
||||
var anchorNode: ASDisplayNode?
|
||||
if self.bottomStripeNode.supernode != nil {
|
||||
anchorNode = self.bottomStripeNode
|
||||
} else if self.topStripeNode.supernode != nil {
|
||||
anchorNode = self.topStripeNode
|
||||
}
|
||||
if let anchorNode = anchorNode {
|
||||
self.insertSubnode(self.highlightedBackgroundNode, aboveSubnode: anchorNode)
|
||||
} else {
|
||||
self.addSubnode(self.highlightedBackgroundNode)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.highlightedBackgroundNode.supernode != nil {
|
||||
if animated {
|
||||
self.highlightedBackgroundNode.layer.animateAlpha(from: self.highlightedBackgroundNode.alpha, to: 0.0, duration: 0.4, completion: { [weak self] completed in
|
||||
if let strongSelf = self {
|
||||
if completed {
|
||||
strongSelf.highlightedBackgroundNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
})
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
} else {
|
||||
self.highlightedBackgroundNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
override public func header() -> ListViewItemHeader? {
|
||||
return nil
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -14,14 +14,35 @@ func optionsButtonImage() -> UIImage? {
|
||||
})
|
||||
}
|
||||
|
||||
final class VoiceChatOptionsButton: ASDisplayNode {
|
||||
func closeButtonImage() -> UIImage? {
|
||||
return 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.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setStrokeColor(UIColor.white.cgColor)
|
||||
|
||||
context.move(to: CGPoint(x: 9.0, y: 9.0))
|
||||
context.addLine(to: CGPoint(x: 19.0, y: 19.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: 19.0, y: 9.0))
|
||||
context.addLine(to: CGPoint(x: 9.0, y: 19.0))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
||||
final class VoiceChatOptionsButton: HighlightableButtonNode {
|
||||
let extractedContainerNode: ContextExtractedContentContainingNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
private let iconNode: ASImageNode
|
||||
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
override init() {
|
||||
init() {
|
||||
self.extractedContainerNode = ContextExtractedContentContainingNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.isGestureEnabled = false
|
||||
|
@ -90,7 +90,7 @@ public final class VoiceChatParticipantItem: ListViewItem {
|
||||
self.contextAction = contextAction
|
||||
}
|
||||
|
||||
public var selectable: Bool = false
|
||||
public var selectable: Bool = true
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
@ -170,11 +170,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
||||
|
||||
override public var canBeSelected: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
public init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
@ -586,13 +582,13 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
let avatarScale: CGFloat
|
||||
if value > 0.0 {
|
||||
audioLevelView.startAnimating()
|
||||
avatarScale = 1.03 + level * 0.1
|
||||
avatarScale = 1.03 + level * 0.07
|
||||
} else {
|
||||
audioLevelView.stopAnimating(duration: 0.5)
|
||||
avatarScale = 1.0
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.15, curve: .spring)
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
transition.updateTransformScale(node: strongSelf.avatarNode, scale: avatarScale, beginWithCurrentState: true)
|
||||
}
|
||||
}))
|
||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -640,8 +640,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
if let call = call {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
let groupCallController = VoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call)
|
||||
groupCallController.sourcePanel = call.sourcePanel
|
||||
call.sourcePanel = nil
|
||||
strongSelf.groupCallController = groupCallController
|
||||
strongSelf.mainWindow?.present(groupCallController, on: .calls)
|
||||
strongSelf.hasOngoingCall.set(true)
|
||||
@ -987,7 +985,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
} else if let groupCallController = self.groupCallController {
|
||||
if groupCallController.isNodeLoaded && groupCallController.view.superview == nil {
|
||||
groupCallController.sourcePanel = sourcePanel
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
mainWindow.present(groupCallController, on: .calls)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user