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.LimitedAccessText" = "You've given Telegram access only to select number of photos.";
|
||||||
"Media.LimitedAccessManage" = "Manage";
|
"Media.LimitedAccessManage" = "Manage";
|
||||||
|
|
||||||
"VoiceChat.BackTitle" = "Chat";
|
|
||||||
|
|
||||||
"VoiceChat.StatusSpeaking" = "speaking";
|
"VoiceChat.StatusSpeaking" = "speaking";
|
||||||
"VoiceChat.StatusListening" = "listening";
|
"VoiceChat.StatusListening" = "listening";
|
||||||
|
|
||||||
@ -5944,7 +5942,6 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"VoiceChat.UnmutePeer" = "Allow to Speak";
|
"VoiceChat.UnmutePeer" = "Allow to Speak";
|
||||||
"VoiceChat.MutePeer" = "Mute";
|
"VoiceChat.MutePeer" = "Mute";
|
||||||
"VoiceChat.InvitePeer" = "Invite";
|
|
||||||
"VoiceChat.RemovePeer" = "Remove";
|
"VoiceChat.RemovePeer" = "Remove";
|
||||||
"VoiceChat.RemovePeerConfirmation" = "Are you sure you want to remove %@ from the group chat?";
|
"VoiceChat.RemovePeerConfirmation" = "Are you sure you want to remove %@ from the group chat?";
|
||||||
"VoiceChat.RemovePeerRemove" = "Remove";
|
"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_START" = "%1$@ has started voice chat in the group %2$@";
|
||||||
"CHAT_VOICECHAT_INVITE" = "%1$@ has invited %3$@ 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$@";
|
"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.minOffset = minOffset
|
||||||
self.maxOffset = maxOffset
|
self.maxOffset = maxOffset
|
||||||
|
|
||||||
let angle = (CGFloat.pi * 2) / CGFloat(pointsCount)
|
self.smoothness = 0.35
|
||||||
self.smoothness = ((4 / 3) * tan(angle / 4)) / sin(angle / 2) / 2
|
|
||||||
|
|
||||||
self.currentOffset = minOffset
|
self.currentOffset = minOffset
|
||||||
|
|
||||||
@ -169,7 +168,13 @@ private final class Curve {
|
|||||||
let timestamp = CACurrentMediaTime()
|
let timestamp = CACurrentMediaTime()
|
||||||
|
|
||||||
if let (startTime, duration) = self.transitionArguments, duration > 0.0 {
|
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 {
|
if self.transition < 1.0 {
|
||||||
} else {
|
} else {
|
||||||
if self.loop {
|
if self.loop {
|
||||||
@ -187,12 +192,12 @@ private final class Curve {
|
|||||||
private func generateNextCurve(for size: CGSize) -> [CGPoint] {
|
private func generateNextCurve(for size: CGSize) -> [CGPoint] {
|
||||||
let randomness = minRandomness + (maxRandomness - minRandomness) * speedLevel
|
let randomness = minRandomness + (maxRandomness - minRandomness) * speedLevel
|
||||||
return curve(pointsCount: pointsCount, randomness: randomness).map {
|
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] {
|
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 rgen = { () -> CGFloat in
|
||||||
let accuracy: UInt32 = 1000
|
let accuracy: UInt32 = 1000
|
||||||
@ -204,7 +209,7 @@ private final class Curve {
|
|||||||
let points = (0 ..< pointsCount).map { i -> CGPoint in
|
let points = (0 ..< pointsCount).map { i -> CGPoint in
|
||||||
let randPointOffset = (rangeStart + CGFloat(rgen()) * (1 - rangeStart)) / 2
|
let randPointOffset = (rangeStart + CGFloat(rgen()) * (1 - rangeStart)) / 2
|
||||||
let segmentRandomness: CGFloat = randomness
|
let segmentRandomness: CGFloat = randomness
|
||||||
|
|
||||||
let pointX: CGFloat
|
let pointX: CGFloat
|
||||||
let pointY: CGFloat
|
let pointY: CGFloat
|
||||||
let randomXDelta: CGFloat
|
let randomXDelta: CGFloat
|
||||||
@ -218,9 +223,10 @@ private final class Curve {
|
|||||||
randomXDelta = 0.0
|
randomXDelta = 0.0
|
||||||
} else {
|
} else {
|
||||||
pointX = segment * CGFloat(i)
|
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
|
randomXDelta = segment - segment * randPointOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
return CGPoint(x: pointX + randomXDelta, y: pointY)
|
return CGPoint(x: pointX + randomXDelta, y: pointY)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,9 +270,9 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
let bigCurveRange: CurveRange = (0.1, 1.0)
|
let bigCurveRange: CurveRange = (0.1, 1.0)
|
||||||
|
|
||||||
let size = CGSize(width: 375.0, height: 44.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 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: 6, minRandomness: 1, maxRandomness: 2, minSpeed: 2.5, maxSpeed: 7, minOffset: mediumCurveRange.min, maxOffset: mediumCurveRange.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: 6, minRandomness: 1, maxRandomness: 2, minSpeed: 2.5, maxSpeed: 7, minOffset: bigCurveRange.min, maxOffset: bigCurveRange.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]
|
self.curves = [smallCurve, mediumCurve, largeCurve]
|
||||||
|
|
||||||
@ -314,7 +320,6 @@ private class CallStatusBarBackgroundNode: ASDisplayNode {
|
|||||||
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||||
self?.updateAnimations()
|
self?.updateAnimations()
|
||||||
})
|
})
|
||||||
animator.frameInterval = 2
|
|
||||||
self.animator = animator
|
self.animator = animator
|
||||||
}
|
}
|
||||||
animator.isPaused = false
|
animator.isPaused = false
|
||||||
@ -526,12 +531,14 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
|
|||||||
if let currentPeer = self.currentPeer {
|
if let currentPeer = self.currentPeer {
|
||||||
title = currentPeer.displayTitle(strings: strings, displayOrder: self.nameDisplayOrder)
|
title = currentPeer.displayTitle(strings: strings, displayOrder: self.nameDisplayOrder)
|
||||||
}
|
}
|
||||||
|
var membersCount: Int32?
|
||||||
if let groupCallState = self.currentGroupCallState {
|
if let groupCallState = self.currentGroupCallState {
|
||||||
if groupCallState.numberOfActiveSpeakers != 0 {
|
membersCount = Int32(max(1, groupCallState.participantCount))
|
||||||
subtitle = strings.VoiceChat_Panel_MembersSpeaking(Int32(groupCallState.numberOfActiveSpeakers))
|
} else if let content = self.currentContent, case .groupCall = content {
|
||||||
} else {
|
membersCount = 1
|
||||||
subtitle = strings.VoiceChat_Panel_Members(Int32(max(1, groupCallState.participantCount)))
|
}
|
||||||
}
|
if let membersCount = membersCount {
|
||||||
|
subtitle = strings.VoiceChat_Panel_Members(membersCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(13.0), textColor: .white)
|
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.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.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 membersText: String
|
||||||
let membersTextIsActive: Bool
|
let membersTextIsActive: Bool
|
||||||
if summaryState.numberOfActiveSpeakers != 0 {
|
if summaryState.participantCount == 0 {
|
||||||
membersText = strongSelf.strings.VoiceChat_Panel_MembersSpeaking(Int32(summaryState.numberOfActiveSpeakers))
|
membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin
|
||||||
membersTextIsActive = true
|
|
||||||
} else {
|
} else {
|
||||||
if summaryState.participantCount == 0 {
|
membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount))
|
||||||
membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin
|
|
||||||
} else {
|
|
||||||
membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount))
|
|
||||||
}
|
|
||||||
membersTextIsActive = false
|
|
||||||
}
|
}
|
||||||
|
membersTextIsActive = false
|
||||||
|
|
||||||
if strongSelf.textIsActive != membersTextIsActive {
|
if strongSelf.textIsActive != membersTextIsActive {
|
||||||
strongSelf.textIsActive = membersTextIsActive
|
strongSelf.textIsActive = membersTextIsActive
|
||||||
@ -373,17 +368,12 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
|
|
||||||
let membersText: String
|
let membersText: String
|
||||||
let membersTextIsActive: Bool
|
let membersTextIsActive: Bool
|
||||||
if data.numberOfActiveSpeakers != 0 {
|
if data.participantCount == 0 {
|
||||||
membersText = self.strings.VoiceChat_Panel_MembersSpeaking(Int32(data.numberOfActiveSpeakers))
|
membersText = self.strings.VoiceChat_Panel_TapToJoin
|
||||||
membersTextIsActive = true
|
|
||||||
} else {
|
} else {
|
||||||
if data.participantCount == 0 {
|
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
|
||||||
membersText = self.strings.VoiceChat_Panel_TapToJoin
|
|
||||||
} else {
|
|
||||||
membersText = self.strings.VoiceChat_Panel_Members(Int32(data.participantCount))
|
|
||||||
}
|
|
||||||
membersTextIsActive = false
|
|
||||||
}
|
}
|
||||||
|
membersTextIsActive = false
|
||||||
|
|
||||||
if self.textIsActive != membersTextIsActive {
|
if self.textIsActive != membersTextIsActive {
|
||||||
self.textIsActive = membersTextIsActive
|
self.textIsActive = membersTextIsActive
|
||||||
|
@ -709,7 +709,6 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
|||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
peer: nil
|
peer: nil
|
||||||
)
|
)
|
||||||
call.sourcePanel = sourcePanel
|
|
||||||
strongSelf.updateCurrentGroupCall(call)
|
strongSelf.updateCurrentGroupCall(call)
|
||||||
strongSelf.currentGroupCallPromise.set(.single(call))
|
strongSelf.currentGroupCallPromise.set(.single(call))
|
||||||
strongSelf.hasActiveGroupCallsPromise.set(true)
|
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 extractedContainerNode: ContextExtractedContentContainingNode
|
||||||
let containerNode: ContextControllerSourceNode
|
let containerNode: ContextControllerSourceNode
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
|
|
||||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
|
|
||||||
override init() {
|
init() {
|
||||||
self.extractedContainerNode = ContextExtractedContentContainingNode()
|
self.extractedContainerNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
self.containerNode.isGestureEnabled = false
|
self.containerNode.isGestureEnabled = false
|
||||||
|
@ -90,7 +90,7 @@ public final class VoiceChatParticipantItem: ListViewItem {
|
|||||||
self.contextAction = contextAction
|
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) {
|
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 {
|
async {
|
||||||
@ -170,11 +170,7 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
|
|
||||||
private var peerPresenceManager: PeerPresenceStatusManager?
|
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||||
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
private var layoutParams: (VoiceChatParticipantItem, ListViewItemLayoutParams, Bool, Bool)?
|
||||||
|
|
||||||
override public var canBeSelected: Bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
@ -586,13 +582,13 @@ public class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
|||||||
let avatarScale: CGFloat
|
let avatarScale: CGFloat
|
||||||
if value > 0.0 {
|
if value > 0.0 {
|
||||||
audioLevelView.startAnimating()
|
audioLevelView.startAnimating()
|
||||||
avatarScale = 1.03 + level * 0.1
|
avatarScale = 1.03 + level * 0.07
|
||||||
} else {
|
} else {
|
||||||
audioLevelView.stopAnimating(duration: 0.5)
|
audioLevelView.stopAnimating(duration: 0.5)
|
||||||
avatarScale = 1.0
|
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)
|
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 {
|
if let call = call {
|
||||||
mainWindow.hostView.containerView.endEditing(true)
|
mainWindow.hostView.containerView.endEditing(true)
|
||||||
let groupCallController = VoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call)
|
let groupCallController = VoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call)
|
||||||
groupCallController.sourcePanel = call.sourcePanel
|
|
||||||
call.sourcePanel = nil
|
|
||||||
strongSelf.groupCallController = groupCallController
|
strongSelf.groupCallController = groupCallController
|
||||||
strongSelf.mainWindow?.present(groupCallController, on: .calls)
|
strongSelf.mainWindow?.present(groupCallController, on: .calls)
|
||||||
strongSelf.hasOngoingCall.set(true)
|
strongSelf.hasOngoingCall.set(true)
|
||||||
@ -987,7 +985,6 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
} else if let groupCallController = self.groupCallController {
|
} else if let groupCallController = self.groupCallController {
|
||||||
if groupCallController.isNodeLoaded && groupCallController.view.superview == nil {
|
if groupCallController.isNodeLoaded && groupCallController.view.superview == nil {
|
||||||
groupCallController.sourcePanel = sourcePanel
|
|
||||||
mainWindow.hostView.containerView.endEditing(true)
|
mainWindow.hostView.containerView.endEditing(true)
|
||||||
mainWindow.present(groupCallController, on: .calls)
|
mainWindow.present(groupCallController, on: .calls)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user