mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 09:20:08 +00:00
Various Improvements
This commit is contained in:
parent
2c75698e90
commit
5704920d45
@ -6317,7 +6317,7 @@ Sorry for the inconvenience.";
|
|||||||
"VoiceChat.EditBioText" = "Any details such as age, occupation or city.";
|
"VoiceChat.EditBioText" = "Any details such as age, occupation or city.";
|
||||||
"VoiceChat.EditBioPlaceholder" = "Bio";
|
"VoiceChat.EditBioPlaceholder" = "Bio";
|
||||||
"VoiceChat.EditBioSave" = "Save";
|
"VoiceChat.EditBioSave" = "Save";
|
||||||
"VoiceChat.EditBioSuccess" = "Your bio is saved.";
|
"VoiceChat.EditBioSuccess" = "Your bio is changed.";
|
||||||
|
|
||||||
"VoiceChat.SendPublicLinkText" = "%1$@ isn't a member of \"%2$@\" yet. Send them a public invite link instead?";
|
"VoiceChat.SendPublicLinkText" = "%1$@ isn't a member of \"%2$@\" yet. Send them a public invite link instead?";
|
||||||
"VoiceChat.SendPublicLinkSend" = "Send";
|
"VoiceChat.SendPublicLinkSend" = "Send";
|
||||||
@ -6330,3 +6330,10 @@ Sorry for the inconvenience.";
|
|||||||
"VoiceChat.AddPhoto" = "Add Photo";
|
"VoiceChat.AddPhoto" = "Add Photo";
|
||||||
"VoiceChat.AddBio" = "Add Bio";
|
"VoiceChat.AddBio" = "Add Bio";
|
||||||
"VoiceChat.ChangeName" = "Change Name";
|
"VoiceChat.ChangeName" = "Change Name";
|
||||||
|
"VoiceChat.ChangeNameTitle" = "Change Name";
|
||||||
|
"VoiceChat.EditNameSuccess" = "Your name is changed.";
|
||||||
|
|
||||||
|
"VoiceChat.Video" = "video";
|
||||||
|
|
||||||
|
"VoiceChat.PinVideo" = "Pin Video";
|
||||||
|
"VoiceChat.UnpinVideo" = "Unpin Video";
|
||||||
|
|||||||
@ -18,6 +18,7 @@ public final class AnimationNode : ASDisplayNode {
|
|||||||
|
|
||||||
public var didPlay = false
|
public var didPlay = false
|
||||||
public var completion: (() -> Void)?
|
public var completion: (() -> Void)?
|
||||||
|
private var internalCompletion: (() -> Void)?
|
||||||
|
|
||||||
public var isPlaying: Bool {
|
public var isPlaying: Bool {
|
||||||
return self.animationView()?.isAnimationPlaying ?? false
|
return self.animationView()?.isAnimationPlaying ?? false
|
||||||
@ -79,10 +80,22 @@ public final class AnimationNode : ASDisplayNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setAnimation(name: String) {
|
public func seekToEnd() {
|
||||||
|
self.animationView()?.animationProgress = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setAnimation(name: String, colors: [String: UIColor]? = nil) {
|
||||||
if let url = getAppBundle().url(forResource: name, withExtension: "json"), let composition = LOTComposition(filePath: url.path) {
|
if let url = getAppBundle().url(forResource: name, withExtension: "json"), let composition = LOTComposition(filePath: url.path) {
|
||||||
self.didPlay = false
|
self.didPlay = false
|
||||||
self.animationView()?.sceneModel = composition
|
self.animationView()?.sceneModel = composition
|
||||||
|
|
||||||
|
if let colors = colors {
|
||||||
|
for (key, value) in colors {
|
||||||
|
let colorCallback = LOTColorValueCallback(color: value.cgColor)
|
||||||
|
self.colorCallbacks.append(colorCallback)
|
||||||
|
self.animationView()?.setValueDelegate(colorCallback, for: LOTKeypath(string: "\(key).Color"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +108,7 @@ public final class AnimationNode : ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func setAnimation(json: [AnyHashable: Any]) {
|
public func setAnimation(json: [AnyHashable: Any]) {
|
||||||
|
self.didPlay = false
|
||||||
self.animationView()?.setAnimation(json: json)
|
self.animationView()?.setAnimation(json: json)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,7 +117,7 @@ public final class AnimationNode : ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func play() {
|
public func play() {
|
||||||
if let animationView = animationView(), !animationView.isAnimationPlaying && !self.didPlay {
|
if let animationView = self.animationView(), !animationView.isAnimationPlaying && !self.didPlay {
|
||||||
self.didPlay = true
|
self.didPlay = true
|
||||||
animationView.play { [weak self] _ in
|
animationView.play { [weak self] _ in
|
||||||
self?.completion?()
|
self?.completion?()
|
||||||
@ -111,8 +125,20 @@ public final class AnimationNode : ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func playOnce() {
|
||||||
|
if let animationView = self.animationView(), !animationView.isAnimationPlaying && !self.didPlay {
|
||||||
|
self.didPlay = true
|
||||||
|
self.internalCompletion = { [weak self] in
|
||||||
|
self?.didPlay = false
|
||||||
|
}
|
||||||
|
animationView.play { [weak self] _ in
|
||||||
|
self?.internalCompletion?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func loop() {
|
public func loop() {
|
||||||
if let animationView = animationView() {
|
if let animationView = self.animationView() {
|
||||||
animationView.loopAnimation = true
|
animationView.loopAnimation = true
|
||||||
animationView.play()
|
animationView.play()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let wrapperNode: ASDisplayNode
|
||||||
private let contentContainer: ASDisplayNode
|
private let contentContainer: ASDisplayNode
|
||||||
private let effectView: UIVisualEffectView
|
private let effectView: UIVisualEffectView
|
||||||
private let contentBackgroundNode: ASImageNode
|
private let contentBackgroundNode: ASImageNode
|
||||||
@ -70,6 +71,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
|||||||
private(set) var currentText: String = ""
|
private(set) var currentText: String = ""
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
self.wrapperNode = ASDisplayNode()
|
||||||
self.contentContainer = ASDisplayNode()
|
self.contentContainer = ASDisplayNode()
|
||||||
|
|
||||||
self.effectView = UIVisualEffectView()
|
self.effectView = UIVisualEffectView()
|
||||||
@ -94,10 +96,11 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
|||||||
|
|
||||||
super.init(pointerStyle: nil)
|
super.init(pointerStyle: nil)
|
||||||
|
|
||||||
self.addSubnode(self.contentContainer)
|
self.addSubnode(self.wrapperNode)
|
||||||
|
self.wrapperNode.addSubnode(self.contentContainer)
|
||||||
self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize))
|
self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize))
|
||||||
|
|
||||||
self.addSubnode(self.textNode)
|
self.wrapperNode.addSubnode(self.textNode)
|
||||||
|
|
||||||
self.contentContainer.view.addSubview(self.effectView)
|
self.contentContainer.view.addSubview(self.effectView)
|
||||||
self.contentContainer.addSubnode(self.contentBackgroundNode)
|
self.contentContainer.addSubnode(self.contentBackgroundNode)
|
||||||
@ -121,6 +124,11 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func layout() {
|
||||||
|
super.layout()
|
||||||
|
self.wrapperNode.frame = self.bounds
|
||||||
|
}
|
||||||
|
|
||||||
func update(size: CGSize, content: Content, text: String, transition: ContainedViewLayoutTransition) {
|
func update(size: CGSize, content: Content, text: String, transition: ContainedViewLayoutTransition) {
|
||||||
let scaleFactor = size.width / self.largeButtonSize
|
let scaleFactor = size.width / self.largeButtonSize
|
||||||
|
|
||||||
@ -164,8 +172,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
|||||||
self.effectView.isHidden = true
|
self.effectView.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateAlpha(node: self, alpha: content.isEnabled ? 1.0 : 0.4)
|
transition.updateAlpha(node: self.wrapperNode, alpha: content.isEnabled ? 1.0 : 0.4)
|
||||||
self.isUserInteractionEnabled = content.isEnabled
|
self.wrapperNode.isUserInteractionEnabled = content.isEnabled
|
||||||
|
|
||||||
let contentBackgroundImage: UIImage? = nil
|
let contentBackgroundImage: UIImage? = nil
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,9 @@ private let pink = UIColor(rgb: 0xef436c)
|
|||||||
private let areaSize = CGSize(width: 300.0, height: 300.0)
|
private let areaSize = CGSize(width: 300.0, height: 300.0)
|
||||||
private let blobSize = CGSize(width: 190.0, height: 190.0)
|
private let blobSize = CGSize(width: 190.0, height: 190.0)
|
||||||
|
|
||||||
|
private let smallScale: CGFloat = 0.48
|
||||||
|
private let smallIconScale: CGFloat = 0.69
|
||||||
|
|
||||||
final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||||
enum State: Equatable {
|
enum State: Equatable {
|
||||||
enum ActiveState: Equatable {
|
enum ActiveState: Equatable {
|
||||||
@ -80,12 +83,17 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
var wasActiveWhenPressed = false
|
var wasActiveWhenPressed = false
|
||||||
var pressing: Bool = false {
|
var pressing: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
guard let (_, _, state, _, _, _, _, snap) = self.currentParams, !self.isDisabled else {
|
guard let (_, _, state, _, small, _, _, snap) = self.currentParams, !self.isDisabled else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.pressing {
|
if self.pressing {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||||
|
if small {
|
||||||
|
transition.updateTransformScale(node: self.backgroundNode, scale: smallScale * 0.9)
|
||||||
|
transition.updateTransformScale(node: self.iconNode, scale: smallIconScale * 0.9)
|
||||||
|
} else {
|
||||||
transition.updateTransformScale(node: self.iconNode, scale: snap ? 0.5 : 0.9)
|
transition.updateTransformScale(node: self.iconNode, scale: snap ? 0.5 : 0.9)
|
||||||
|
}
|
||||||
|
|
||||||
switch state {
|
switch state {
|
||||||
case let .active(state):
|
case let .active(state):
|
||||||
@ -100,7 +108,12 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||||
|
if small {
|
||||||
|
transition.updateTransformScale(node: self.backgroundNode, scale: smallScale)
|
||||||
|
transition.updateTransformScale(node: self.iconNode, scale: smallIconScale)
|
||||||
|
} else {
|
||||||
transition.updateTransformScale(node: self.iconNode, scale: snap ? 0.5 : 1.0)
|
transition.updateTransformScale(node: self.iconNode, scale: snap ? 0.5 : 1.0)
|
||||||
|
}
|
||||||
self.wasActiveWhenPressed = false
|
self.wasActiveWhenPressed = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,18 +140,28 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
|
|
||||||
self.highligthedChanged = { [weak self] pressing in
|
self.highligthedChanged = { [weak self] pressing in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
guard let (_, _, _, _, _, _, _, snap) = strongSelf.currentParams else {
|
guard let (_, _, _, _, small, _, _, snap) = strongSelf.currentParams else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if pressing {
|
if pressing {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||||
|
if small {
|
||||||
|
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallScale * 0.9)
|
||||||
|
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallIconScale * 0.9)
|
||||||
|
} else {
|
||||||
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 0.9)
|
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 0.9)
|
||||||
|
}
|
||||||
} else if !strongSelf.pressing {
|
} else if !strongSelf.pressing {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||||
|
if small {
|
||||||
|
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallScale)
|
||||||
|
transition.updateTransformScale(node: strongSelf.backgroundNode, scale: smallIconScale)
|
||||||
|
} else {
|
||||||
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 1.0)
|
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.backgroundNode.updatedActive = { [weak self] active in
|
self.backgroundNode.updatedActive = { [weak self] active in
|
||||||
self?.activePromise.set(active)
|
self?.activePromise.set(active)
|
||||||
@ -222,8 +245,14 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 0.0)
|
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 0.0)
|
||||||
} else {
|
} else {
|
||||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
||||||
transition.updateTransformScale(node: self.backgroundNode, scale: small ? 0.85 : 1.0, delay: 0.05)
|
|
||||||
|
if small {
|
||||||
|
transition.updateTransformScale(node: self.backgroundNode, scale: self.pressing ? smallScale * 0.9 : smallScale, delay: 0.05)
|
||||||
|
transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? smallIconScale * 0.9 : smallIconScale, delay: 0.05)
|
||||||
|
} else {
|
||||||
|
transition.updateTransformScale(node: self.backgroundNode, scale: 1.0, delay: 0.05)
|
||||||
transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? 0.9 : 1.0, delay: 0.05)
|
transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? 0.9 : 1.0, delay: 0.05)
|
||||||
|
}
|
||||||
transition.updateAlpha(node: self.titleLabel, alpha: 1.0, delay: 0.05)
|
transition.updateAlpha(node: self.titleLabel, alpha: 1.0, delay: 0.05)
|
||||||
transition.updateAlpha(node: self.subtitleLabel, alpha: 1.0, delay: 0.05)
|
transition.updateAlpha(node: self.subtitleLabel, alpha: 1.0, delay: 0.05)
|
||||||
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 1.0)
|
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 1.0)
|
||||||
|
|||||||
@ -34,8 +34,10 @@ private let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
|
|||||||
private let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
private let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
||||||
private let fullscreenBackgroundColor = UIColor(rgb: 0x000000)
|
private let fullscreenBackgroundColor = UIColor(rgb: 0x000000)
|
||||||
private let dimColor = UIColor(white: 0.0, alpha: 0.5)
|
private let dimColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
|
private let smallButtonSize = CGSize(width: 36.0, height: 36.0)
|
||||||
private let sideButtonSize = CGSize(width: 56.0, height: 56.0)
|
private let sideButtonSize = CGSize(width: 56.0, height: 56.0)
|
||||||
private let bottomAreaHeight: CGFloat = 205.0
|
private let bottomAreaHeight: CGFloat = 205.0
|
||||||
|
private let fullscreenBottomAreaHeight: CGFloat = 80.0
|
||||||
|
|
||||||
private func cornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
|
private func cornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
|
||||||
if !top && !bottom {
|
if !top && !bottom {
|
||||||
@ -604,10 +606,21 @@ public final class VoiceChatController: ViewController {
|
|||||||
if let muteState = peerEntry.muteState, case .speaking = state, muteState.mutedByYou || !muteState.canUnmute {
|
if let muteState = peerEntry.muteState, case .speaking = state, muteState.mutedByYou || !muteState.canUnmute {
|
||||||
state = .listening
|
state = .listening
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let yourText: String
|
||||||
|
if (peerEntry.about?.isEmpty ?? true) && peer.smallProfileImage == nil {
|
||||||
|
yourText = presentationData.strings.VoiceChat_TapToAddPhotoOrBio
|
||||||
|
} else if peer.smallProfileImage == nil {
|
||||||
|
yourText = presentationData.strings.VoiceChat_TapToAddPhoto
|
||||||
|
} else if (peerEntry.about?.isEmpty ?? true) {
|
||||||
|
yourText = presentationData.strings.VoiceChat_TapToAddBio
|
||||||
|
} else {
|
||||||
|
yourText = presentationData.strings.VoiceChat_You
|
||||||
|
}
|
||||||
switch state {
|
switch state {
|
||||||
case .listening:
|
case .listening:
|
||||||
if peerEntry.isMyPeer {
|
if peerEntry.isMyPeer {
|
||||||
text = .text(presentationData.strings.VoiceChat_You, .accent)
|
text = .text(yourText, .accent)
|
||||||
} else if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
} else if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||||
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, .destructive)
|
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, .destructive)
|
||||||
} else if let about = peerEntry.about, !about.isEmpty {
|
} else if let about = peerEntry.about, !about.isEmpty {
|
||||||
@ -640,7 +653,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
icon = .invite(true)
|
icon = .invite(true)
|
||||||
case .raisedHand:
|
case .raisedHand:
|
||||||
if peerEntry.isMyPeer && !peerEntry.displayRaisedHandStatus {
|
if peerEntry.isMyPeer && !peerEntry.displayRaisedHandStatus {
|
||||||
text = .text(presentationData.strings.VoiceChat_You, .accent)
|
text = .text(yourText, .accent)
|
||||||
} else if let about = peerEntry.about, !about.isEmpty && !peerEntry.displayRaisedHandStatus {
|
} else if let about = peerEntry.about, !about.isEmpty && !peerEntry.displayRaisedHandStatus {
|
||||||
text = .text(about, .generic)
|
text = .text(about, .generic)
|
||||||
} else {
|
} else {
|
||||||
@ -655,7 +668,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
|
let revealOptions: [VoiceChatParticipantItem.RevealOption] = []
|
||||||
|
|
||||||
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: peerEntry.presence, text: text, expandedText: expandedText, icon: icon, enabled: true, selectable: true, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
|
return VoiceChatParticipantItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: peer, ssrc: peerEntry.ssrc, presence: peerEntry.presence, text: text, expandedText: expandedText, icon: icon, enabled: true, transparent: false, selectable: true, getAudioLevel: { return interaction.getAudioLevel(peer.id) }, getVideo: {
|
||||||
if let ssrc = peerEntry.ssrc {
|
if let ssrc = peerEntry.ssrc {
|
||||||
return interaction.getPeerVideo(ssrc)
|
return interaction.getPeerVideo(ssrc)
|
||||||
} else {
|
} else {
|
||||||
@ -709,9 +722,10 @@ public final class VoiceChatController: ViewController {
|
|||||||
fileprivate let bottomPanelNode: ASDisplayNode
|
fileprivate let bottomPanelNode: ASDisplayNode
|
||||||
private let bottomPanelBackgroundNode: ASDisplayNode
|
private let bottomPanelBackgroundNode: ASDisplayNode
|
||||||
private let bottomCornersNode: ASImageNode
|
private let bottomCornersNode: ASImageNode
|
||||||
fileprivate let audioOutputNode: CallControllerButtonItemNode
|
fileprivate let audioButton: CallControllerButtonItemNode
|
||||||
fileprivate let cameraButtonNode: CallControllerButtonItemNode
|
fileprivate let cameraButton: CallControllerButtonItemNode
|
||||||
fileprivate let leaveNode: CallControllerButtonItemNode
|
fileprivate let switchCameraButton: CallControllerButtonItemNode
|
||||||
|
fileprivate let leaveButton: CallControllerButtonItemNode
|
||||||
fileprivate let actionButton: VoiceChatActionButton
|
fileprivate let actionButton: VoiceChatActionButton
|
||||||
private let leftBorderNode: ASDisplayNode
|
private let leftBorderNode: ASDisplayNode
|
||||||
private let rightBorderNode: ASDisplayNode
|
private let rightBorderNode: ASDisplayNode
|
||||||
@ -777,13 +791,9 @@ public final class VoiceChatController: ViewController {
|
|||||||
private let reconnectedAsEventsDisposable = MetaDisposable()
|
private let reconnectedAsEventsDisposable = MetaDisposable()
|
||||||
private let voiceSourcesDisposable = MetaDisposable()
|
private let voiceSourcesDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var requestedVideoSources = Set<UInt32>()
|
|
||||||
private var videoNodes: [(PeerId, UInt32, GroupVideoNode)] = []
|
|
||||||
|
|
||||||
private let displayAsPeersPromise = Promise<[FoundPeer]>([])
|
private let displayAsPeersPromise = Promise<[FoundPeer]>([])
|
||||||
private let inviteLinksPromise = Promise<GroupCallInviteLinks?>(nil)
|
private let inviteLinksPromise = Promise<GroupCallInviteLinks?>(nil)
|
||||||
|
|
||||||
|
|
||||||
private var raisedHandDisplayDisposables: [PeerId: Disposable] = [:]
|
private var raisedHandDisplayDisposables: [PeerId: Disposable] = [:]
|
||||||
private var displayedRaisedHands = Set<PeerId>() {
|
private var displayedRaisedHands = Set<PeerId>() {
|
||||||
didSet {
|
didSet {
|
||||||
@ -792,12 +802,21 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
private let displayedRaisedHandsPromise = ValuePromise<Set<PeerId>>(Set())
|
private let displayedRaisedHandsPromise = ValuePromise<Set<PeerId>>(Set())
|
||||||
|
|
||||||
|
private var requestedVideoSources = Set<UInt32>()
|
||||||
|
private var videoNodes: [(PeerId, UInt32, GroupVideoNode)] = []
|
||||||
private var currentDominantSpeakerWithVideo: (PeerId, UInt32)?
|
private var currentDominantSpeakerWithVideo: (PeerId, UInt32)?
|
||||||
|
|
||||||
private var updateAvatarDisposable = MetaDisposable()
|
private var updateAvatarDisposable = MetaDisposable()
|
||||||
private let updateAvatarPromise = Promise<(TelegramMediaImageRepresentation, Float)?>(nil)
|
private let updateAvatarPromise = Promise<(TelegramMediaImageRepresentation, Float)?>(nil)
|
||||||
private var currentUpdatingAvatar: TelegramMediaImageRepresentation?
|
private var currentUpdatingAvatar: TelegramMediaImageRepresentation?
|
||||||
|
|
||||||
|
private enum DisplayMode {
|
||||||
|
case `default`
|
||||||
|
case fullscreen(controlsHidden: Bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var displayMode: DisplayMode = .default
|
||||||
|
|
||||||
init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) {
|
init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.sharedContext = sharedContext
|
self.sharedContext = sharedContext
|
||||||
@ -869,9 +888,12 @@ public final class VoiceChatController: ViewController {
|
|||||||
self.bottomCornersNode.displayWithoutProcessing = true
|
self.bottomCornersNode.displayWithoutProcessing = true
|
||||||
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: false)
|
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: false)
|
||||||
|
|
||||||
self.audioOutputNode = CallControllerButtonItemNode()
|
self.audioButton = CallControllerButtonItemNode()
|
||||||
self.cameraButtonNode = CallControllerButtonItemNode()
|
self.cameraButton = CallControllerButtonItemNode()
|
||||||
self.leaveNode = CallControllerButtonItemNode()
|
self.switchCameraButton = CallControllerButtonItemNode()
|
||||||
|
self.switchCameraButton.alpha = 0.0
|
||||||
|
self.switchCameraButton.isUserInteractionEnabled = false
|
||||||
|
self.leaveButton = CallControllerButtonItemNode()
|
||||||
self.actionButton = VoiceChatActionButton()
|
self.actionButton = VoiceChatActionButton()
|
||||||
|
|
||||||
self.leftBorderNode = ASDisplayNode()
|
self.leftBorderNode = ASDisplayNode()
|
||||||
@ -1245,8 +1267,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strongSelf.context.sharedContext.immediateExperimentalUISettings.demoVideoChats {
|
if strongSelf.context.sharedContext.immediateExperimentalUISettings.demoVideoChats {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Toggle Full Screen", icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_PinVideo, icon: { theme in
|
||||||
return nil
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
@ -1306,12 +1328,34 @@ public final class VoiceChatController: ViewController {
|
|||||||
return .complete()
|
return .complete()
|
||||||
}).start()
|
}).start()
|
||||||
|
|
||||||
strongSelf.presentUndoOverlay(content: .voiceChatFlag(text: strongSelf.presentationData.strings.VoiceChat_EditBioSuccess), action: { _ in return false })
|
strongSelf.presentUndoOverlay(content: .info(text: strongSelf.presentationData.strings.VoiceChat_EditBioSuccess), action: { _ in return false })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
self?.controller?.present(controller, in: .window(.root))
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
}
|
}
|
||||||
})))
|
})))
|
||||||
|
|
||||||
|
if let peer = peer as? TelegramUser {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_ChangeName, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Call/Context Menu/ChangeName"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f(.default)
|
||||||
|
|
||||||
|
Queue.mainQueue().after(0.1) {
|
||||||
|
let controller = voiceChatUserNameController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_ChangeNameTitle, firstNamePlaceholder: presentationData.strings.UserInfo_FirstNamePlaceholder, lastNamePlaceholder: presentationData.strings.UserInfo_LastNamePlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, firstName: peer.firstName, lastName: peer.lastName, maxLength: 128, apply: { firstName, lastName in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let _ = updateAccountPeerName(account: context.account, firstName: firstName, lastName: lastName).start()
|
||||||
|
|
||||||
|
strongSelf.presentUndoOverlay(content: .info(text: strongSelf.presentationData.strings.VoiceChat_EditNameSuccess), action: { _ in return false })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
|
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
|
||||||
if callState.adminIds.contains(peer.id) {
|
if callState.adminIds.contains(peer.id) {
|
||||||
@ -1499,9 +1543,12 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.bottomPanelNode.addSubnode(self.bottomCornersNode)
|
self.bottomPanelNode.addSubnode(self.bottomCornersNode)
|
||||||
self.bottomPanelNode.addSubnode(self.bottomPanelBackgroundNode)
|
self.bottomPanelNode.addSubnode(self.bottomPanelBackgroundNode)
|
||||||
self.bottomPanelNode.addSubnode(self.audioOutputNode)
|
self.bottomPanelNode.addSubnode(self.audioButton)
|
||||||
//self.bottomPanelNode.addSubnode(self.cameraButtonNode)
|
if let mainVideoContainer = self.mainVideoContainer {
|
||||||
self.bottomPanelNode.addSubnode(self.leaveNode)
|
self.bottomPanelNode.addSubnode(self.cameraButton)
|
||||||
|
self.bottomPanelNode.addSubnode(self.switchCameraButton)
|
||||||
|
}
|
||||||
|
self.bottomPanelNode.addSubnode(self.leaveButton)
|
||||||
self.bottomPanelNode.addSubnode(self.actionButton)
|
self.bottomPanelNode.addSubnode(self.actionButton)
|
||||||
|
|
||||||
self.addSubnode(self.dimNode)
|
self.addSubnode(self.dimNode)
|
||||||
@ -1684,13 +1731,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
strongSelf.actionButton.updateLevel(CGFloat(effectiveLevel))
|
strongSelf.actionButton.updateLevel(CGFloat(effectiveLevel))
|
||||||
})
|
})
|
||||||
|
|
||||||
self.leaveNode.addTarget(self, action: #selector(self.leavePressed), forControlEvents: .touchUpInside)
|
self.leaveButton.addTarget(self, action: #selector(self.leavePressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
|
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.audioButton.addTarget(self, action: #selector(self.audioOutputPressed), forControlEvents: .touchUpInside)
|
||||||
self.audioOutputNode.addTarget(self, action: #selector(self.audioOutputPressed), forControlEvents: .touchUpInside)
|
self.cameraButton.addTarget(self, action: #selector(self.cameraPressed), forControlEvents: .touchUpInside)
|
||||||
|
self.switchCameraButton.addTarget(self, action: #selector(self.switchCameraPressed), forControlEvents: .touchUpInside)
|
||||||
self.cameraButtonNode.addTarget(self, action: #selector(self.cameraPressed), forControlEvents: .touchUpInside)
|
|
||||||
|
|
||||||
self.optionsButton.contextAction = { [weak self] sourceNode, gesture in
|
self.optionsButton.contextAction = { [weak self] sourceNode, gesture in
|
||||||
self?.openContextMenu(sourceNode: sourceNode, gesture: gesture)
|
self?.openContextMenu(sourceNode: sourceNode, gesture: gesture)
|
||||||
@ -1830,27 +1875,21 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.titleNode.tapped = { [weak self] in
|
self.titleNode.tapped = { [weak self] in
|
||||||
if let strongSelf = self, !strongSelf.titleNode.recordingIconNode.isHidden {
|
if let strongSelf = self, !strongSelf.titleNode.recordingIconNode.isHidden {
|
||||||
var ignore = false
|
var hasTooltipAlready = false
|
||||||
strongSelf.controller?.forEachController { controller -> Bool in
|
strongSelf.controller?.forEachController { controller -> Bool in
|
||||||
if controller is TooltipScreen {
|
if controller is TooltipScreen {
|
||||||
ignore = true
|
hasTooltipAlready = true
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if !hasTooltipAlready {
|
||||||
guard !ignore else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let location = strongSelf.titleNode.recordingIconNode.convert(strongSelf.titleNode.recordingIconNode.bounds, to: nil)
|
let location = strongSelf.titleNode.recordingIconNode.convert(strongSelf.titleNode.recordingIconNode.bounds, to: nil)
|
||||||
strongSelf.controller?.present(TooltipScreen(text: presentationData.strings.VoiceChat_RecordingInProgress, icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
|
strongSelf.controller?.present(TooltipScreen(text: presentationData.strings.VoiceChat_RecordingInProgress, icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
|
||||||
return .dismiss(consume: true)
|
return .dismiss(consume: true)
|
||||||
}), in: .window(.root))
|
}), in: .window(.root))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//self.isFullscreen = true
|
|
||||||
//self.isExpanded = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -1873,14 +1912,12 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
private func openContextMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
|
private func openContextMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
|
||||||
let canManageCall = !self.optionsButtonIsAvatar
|
let canManageCall = !self.optionsButtonIsAvatar
|
||||||
|
|
||||||
let items: Signal<[ContextMenuItem], NoError>
|
let items: Signal<[ContextMenuItem], NoError>
|
||||||
if canManageCall {
|
if canManageCall {
|
||||||
items = self.contextMenuMainItems()
|
items = self.contextMenuMainItems()
|
||||||
} else {
|
} else {
|
||||||
items = self.contextMenuDisplayAsItems()
|
items = self.contextMenuDisplayAsItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let controller = self.controller {
|
if let controller = self.controller {
|
||||||
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: self.darkTheme), source: .reference(VoiceChatContextReferenceContentSource(controller: controller, sourceNode: self.optionsButton.referenceNode)), items: items, reactionItems: [], gesture: gesture)
|
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: self.darkTheme), source: .reference(VoiceChatContextReferenceContentSource(controller: controller, sourceNode: self.optionsButton.referenceNode)), items: items, reactionItems: [], gesture: gesture)
|
||||||
controller.presentInGlobalOverlay(contextController)
|
controller.presentInGlobalOverlay(contextController)
|
||||||
@ -2576,6 +2613,19 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func switchCameraPressed() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private var effectiveBottomAreaHeight: CGFloat {
|
||||||
|
switch self.displayMode {
|
||||||
|
case .default:
|
||||||
|
return bottomAreaHeight
|
||||||
|
case let .fullscreen(controlsHidden):
|
||||||
|
return controlsHidden ? 0.0 : fullscreenBottomAreaHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
|
private func updateFloatingHeaderOffset(offset: CGFloat, transition: ContainedViewLayoutTransition, completion: (() -> Void)? = nil) {
|
||||||
guard let (layout, _) = self.validLayout else {
|
guard let (layout, _) = self.validLayout else {
|
||||||
return
|
return
|
||||||
@ -2584,7 +2634,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||||
let topPanelHeight: CGFloat = 63.0
|
let topPanelHeight: CGFloat = 63.0
|
||||||
let listTopInset = layoutTopInset + topPanelHeight
|
let listTopInset = layoutTopInset + topPanelHeight
|
||||||
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
let bottomPanelHeight = self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom
|
||||||
|
|
||||||
var size = layout.size
|
var size = layout.size
|
||||||
if case .regular = layout.metrics.widthClass {
|
if case .regular = layout.metrics.widthClass {
|
||||||
@ -2774,13 +2824,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateButtons(animated: Bool) {
|
private func updateButtons(animated: Bool) {
|
||||||
let audioButtonAppearance: CallControllerButtonItemNode.Content.Appearance
|
|
||||||
if let color = self.currentAudioButtonColor {
|
|
||||||
audioButtonAppearance = .color(.custom(color.rgb, 1.0))
|
|
||||||
} else {
|
|
||||||
audioButtonAppearance = .color(.custom(self.isFullscreen ? 0x1c1c1e : 0x2c2c2e, 1.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
var audioMode: CallControllerButtonsSpeakerMode = .none
|
var audioMode: CallControllerButtonsSpeakerMode = .none
|
||||||
//var hasAudioRouteMenu: Bool = false
|
//var hasAudioRouteMenu: Bool = false
|
||||||
if let (availableOutputs, maybeCurrentOutput) = self.audioOutputState, let currentOutput = maybeCurrentOutput {
|
if let (availableOutputs, maybeCurrentOutput) = self.audioOutputState, let currentOutput = maybeCurrentOutput {
|
||||||
@ -2807,8 +2850,15 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let coloredButtonAppearance: CallControllerButtonItemNode.Content.Appearance
|
||||||
|
if let color = self.currentAudioButtonColor {
|
||||||
|
coloredButtonAppearance = .color(.custom(color.rgb, 1.0))
|
||||||
|
} else {
|
||||||
|
coloredButtonAppearance = .color(.custom(self.isFullscreen ? 0x1c1c1e : 0x2c2c2e, 1.0))
|
||||||
|
}
|
||||||
|
|
||||||
let soundImage: CallControllerButtonItemNode.Content.Image
|
let soundImage: CallControllerButtonItemNode.Content.Image
|
||||||
var soundAppearance: CallControllerButtonItemNode.Content.Appearance = audioButtonAppearance
|
var soundAppearance: CallControllerButtonItemNode.Content.Appearance = coloredButtonAppearance
|
||||||
var soundTitle: String = self.presentationData.strings.Call_Speaker
|
var soundTitle: String = self.presentationData.strings.Call_Speaker
|
||||||
switch audioMode {
|
switch audioMode {
|
||||||
case .none, .builtin:
|
case .none, .builtin:
|
||||||
@ -2831,13 +2881,30 @@ public final class VoiceChatController: ViewController {
|
|||||||
soundTitle = self.presentationData.strings.Call_Audio
|
soundTitle = self.presentationData.strings.Call_Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: soundTitle, transition: animated ? .animated(duration: 0.3, curve: .linear) : .immediate)
|
let videoButtonSize: CGSize
|
||||||
|
var buttonsTitleAlpha: CGFloat
|
||||||
|
switch self.displayMode {
|
||||||
|
case .default:
|
||||||
|
videoButtonSize = smallButtonSize
|
||||||
|
buttonsTitleAlpha = 1.0
|
||||||
|
case .fullscreen:
|
||||||
|
videoButtonSize = sideButtonSize
|
||||||
|
buttonsTitleAlpha = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
let cameraButtonSize = CGSize(width: 40.0, height: 40.0)
|
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
|
||||||
|
self.cameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: coloredButtonAppearance, image: .camera), text: self.presentationData.strings.VoiceChat_Video, transition: transition)
|
||||||
|
|
||||||
self.cameraButtonNode.update(size: cameraButtonSize, content: CallControllerButtonItemNode.Content(appearance: CallControllerButtonItemNode.Content.Appearance.blurred(isFilled: false), image: .camera), text: " ", transition: animated ? .animated(duration: 0.3, curve: .linear) : .immediate)
|
self.switchCameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: coloredButtonAppearance, image: .flipCamera), text: "", transition: transition)
|
||||||
|
|
||||||
self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .cancel), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
|
self.audioButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: soundTitle, transition: transition)
|
||||||
|
|
||||||
|
self.leaveButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .cancel), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
|
||||||
|
|
||||||
|
transition.updateAlpha(node: self.cameraButton.textNode, alpha: 0.0)
|
||||||
|
transition.updateAlpha(node: self.switchCameraButton.textNode, alpha: buttonsTitleAlpha)
|
||||||
|
transition.updateAlpha(node: self.audioButton.textNode, alpha: buttonsTitleAlpha)
|
||||||
|
transition.updateAlpha(node: self.leaveButton.textNode, alpha: buttonsTitleAlpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
@ -2883,7 +2950,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
transition.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
|
transition.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
let bottomPanelHeight = self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom
|
||||||
var listTopInset = layoutTopInset + topPanelHeight
|
var listTopInset = layoutTopInset + topPanelHeight
|
||||||
if self.mainVideoContainer != nil {
|
if self.mainVideoContainer != nil {
|
||||||
listTopInset += min(300.0, layout.size.width)
|
listTopInset += min(300.0, layout.size.width)
|
||||||
@ -2917,10 +2984,39 @@ public final class VoiceChatController: ViewController {
|
|||||||
let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: size.width, height: bottomPanelHeight))
|
let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: size.width, height: bottomPanelHeight))
|
||||||
transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame)
|
transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame)
|
||||||
|
|
||||||
let cameraButtonSize = CGSize(width: 40.0, height: 40.0)
|
|
||||||
let centralButtonSize = CGSize(width: 300.0, height: 300.0)
|
let centralButtonSize = CGSize(width: 300.0, height: 300.0)
|
||||||
|
let cameraButtonSize = CGSize(width: 36.0, height: 36.0)
|
||||||
|
let sideButtonMinimalInset: CGFloat = 16.0
|
||||||
|
let sideButtonOffset = min(42.0, floor((((size.width - 112.0) / 2.0) - sideButtonSize.width) / 2.0))
|
||||||
|
let sideButtonOrigin = max(sideButtonMinimalInset, floor((size.width - 112.0) / 2.0) - sideButtonOffset - sideButtonSize.width)
|
||||||
|
|
||||||
let actionButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - centralButtonSize.width) / 2.0), y: floorToScreenPixels((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
|
let upperButtonDistance: CGFloat = 12.0
|
||||||
|
let firstButtonFrame: CGRect
|
||||||
|
let secondButtonFrame: CGRect
|
||||||
|
let thirdButtonFrame: CGRect
|
||||||
|
let forthButtonFrame: CGRect
|
||||||
|
let leftButtonFrame = CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((self.effectiveBottomAreaHeight - sideButtonSize.height - upperButtonDistance - cameraButtonSize.height) / 2.0) + upperButtonDistance + cameraButtonSize.height), size: sideButtonSize)
|
||||||
|
let rightButtonFrame = CGRect(origin: CGPoint(x: size.width - sideButtonOrigin - sideButtonSize.width, y: floor((self.effectiveBottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)
|
||||||
|
|
||||||
|
let smallButtons: Bool
|
||||||
|
switch self.displayMode {
|
||||||
|
case .default:
|
||||||
|
smallButtons = false
|
||||||
|
firstButtonFrame = CGRect(origin: CGPoint(x: floor(leftButtonFrame.midX - cameraButtonSize.width / 2.0), y: leftButtonFrame.minY - upperButtonDistance - cameraButtonSize.height), size: cameraButtonSize)
|
||||||
|
secondButtonFrame = leftButtonFrame
|
||||||
|
thirdButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - centralButtonSize.width) / 2.0), y: floorToScreenPixels((self.effectiveBottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
|
||||||
|
forthButtonFrame = rightButtonFrame
|
||||||
|
case let .fullscreen(controlsHidden):
|
||||||
|
smallButtons = true
|
||||||
|
let sideInset: CGFloat = 26.0
|
||||||
|
let spacing = floor((layout.size.width - sideInset * 2.0 - sideButtonSize.width * 4.0) / 3.0)
|
||||||
|
|
||||||
|
firstButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: controlsHidden ? layout.size.height : floor((self.effectiveBottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)
|
||||||
|
secondButtonFrame = CGRect(origin: CGPoint(x: sideInset + sideButtonSize.width + spacing, y: controlsHidden ? layout.size.height : floor((self.effectiveBottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)
|
||||||
|
let thirdButtonPreFrame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - sideButtonSize.width - spacing - sideButtonSize.width, y: controlsHidden ? layout.size.height : floor((self.effectiveBottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)
|
||||||
|
thirdButtonFrame = CGRect(origin: CGPoint(x: floor(thirdButtonPreFrame.midX - centralButtonSize.width / 2.0), y: floor(thirdButtonPreFrame.midY - centralButtonSize.height / 2.0)), size: centralButtonSize)
|
||||||
|
forthButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - sideButtonSize.width, y: controlsHidden ? layout.size.height : floor((self.effectiveBottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)
|
||||||
|
}
|
||||||
|
|
||||||
let actionButtonState: VoiceChatActionButton.State
|
let actionButtonState: VoiceChatActionButton.State
|
||||||
let actionButtonTitle: String
|
let actionButtonTitle: String
|
||||||
@ -2966,10 +3062,10 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.actionButton.isDisabled = !actionButtonEnabled
|
self.actionButton.isDisabled = !actionButtonEnabled
|
||||||
self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 112.0, height: 112.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, dark: self.isFullscreen, small: false, animated: true)
|
self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 112.0, height: 112.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, dark: self.isFullscreen, small: smallButtons, animated: true)
|
||||||
|
|
||||||
if self.actionButton.supernode === self.bottomPanelNode {
|
if self.actionButton.supernode === self.bottomPanelNode {
|
||||||
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
transition.updateFrame(node: self.actionButton, frame: thirdButtonFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateButtons(animated: !isFirstTime)
|
self.updateButtons(animated: !isFirstTime)
|
||||||
@ -2991,25 +3087,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
currentVideoOrigin.x += videoSize.width + 4.0
|
currentVideoOrigin.x += videoSize.width + 4.0
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
let sideButtonMinimalInset: CGFloat = 16.0
|
if self.audioButton.supernode === self.bottomPanelNode {
|
||||||
let sideButtonOffset = min(42.0, floor((((size.width - 112.0) / 2.0) - sideButtonSize.width) / 2.0))
|
transition.updateFrame(node: self.cameraButton, frame: firstButtonFrame)
|
||||||
let sideButtonOrigin = max(sideButtonMinimalInset, floor((size.width - 112.0) / 2.0) - sideButtonOffset - sideButtonSize.width)
|
transition.updateFrame(node: self.switchCameraButton, frame: firstButtonFrame)
|
||||||
|
transition.updateFrame(node: self.audioButton, frame: secondButtonFrame)
|
||||||
if self.audioOutputNode.supernode === self.bottomPanelNode {
|
transition.updateFrame(node: self.leaveButton, frame: forthButtonFrame)
|
||||||
if true {
|
|
||||||
let audioOutputFrame = CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)
|
|
||||||
transition.updateFrame(node: self.audioOutputNode, frame: audioOutputFrame)
|
|
||||||
} else {
|
|
||||||
let cameraButtonDistance: CGFloat = 4.0
|
|
||||||
|
|
||||||
let audioOutputFrame = CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((bottomAreaHeight - sideButtonSize.height - cameraButtonDistance - cameraButtonSize.height) / 2.0) + cameraButtonDistance + cameraButtonSize.height), size: sideButtonSize)
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.audioOutputNode, frame: audioOutputFrame)
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.cameraButtonNode, frame: CGRect(origin: CGPoint(x: floor(audioOutputFrame.midX - cameraButtonSize.width / 2.0), y: audioOutputFrame.minY - cameraButtonDistance - cameraButtonSize.height), size: cameraButtonSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
transition.updateFrame(node: self.leaveNode, frame: CGRect(origin: CGPoint(x: size.width - sideButtonOrigin - sideButtonSize.width, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize))
|
|
||||||
}
|
}
|
||||||
if isFirstTime {
|
if isFirstTime {
|
||||||
while !self.enqueuedTransitions.isEmpty {
|
while !self.enqueuedTransitions.isEmpty {
|
||||||
@ -3034,15 +3116,15 @@ public final class VoiceChatController: ViewController {
|
|||||||
}, completion: { _ in
|
}, completion: { _ in
|
||||||
if self.actionButton.supernode !== self.bottomPanelNode {
|
if self.actionButton.supernode !== self.bottomPanelNode {
|
||||||
self.actionButton.ignoreHierarchyChanges = true
|
self.actionButton.ignoreHierarchyChanges = true
|
||||||
self.audioOutputNode.isHidden = false
|
self.audioButton.isHidden = false
|
||||||
self.cameraButtonNode.isHidden = false
|
self.cameraButton.isHidden = false
|
||||||
self.leaveNode.isHidden = false
|
self.leaveButton.isHidden = false
|
||||||
self.audioOutputNode.layer.removeAllAnimations()
|
self.audioButton.layer.removeAllAnimations()
|
||||||
self.cameraButtonNode.layer.removeAllAnimations()
|
self.cameraButton.layer.removeAllAnimations()
|
||||||
self.leaveNode.layer.removeAllAnimations()
|
self.leaveButton.layer.removeAllAnimations()
|
||||||
self.bottomPanelNode.addSubnode(self.audioOutputNode)
|
self.bottomPanelNode.addSubnode(self.audioButton)
|
||||||
//self.bottomPanelNode.addSubnode(self.cameraButtonNode)
|
//self.bottomPanelNode.addSubnode(self.cameraButtonNode)
|
||||||
self.bottomPanelNode.addSubnode(self.leaveNode)
|
self.bottomPanelNode.addSubnode(self.leaveButton)
|
||||||
self.bottomPanelNode.addSubnode(self.actionButton)
|
self.bottomPanelNode.addSubnode(self.actionButton)
|
||||||
self.containerLayoutUpdated(layout, navigationHeight :navigationHeight, transition: .immediate)
|
self.containerLayoutUpdated(layout, navigationHeight :navigationHeight, transition: .immediate)
|
||||||
self.actionButton.ignoreHierarchyChanges = false
|
self.actionButton.ignoreHierarchyChanges = false
|
||||||
@ -3134,7 +3216,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
size.width = floor(min(size.width, size.height) * 0.5)
|
size.width = floor(min(size.width, size.height) * 0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
let bottomPanelHeight = self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom
|
||||||
let listTopInset = layoutTopInset + 63.0
|
let listTopInset = layoutTopInset + 63.0
|
||||||
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
|
let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight)
|
||||||
|
|
||||||
@ -3358,7 +3440,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
if gestureRecognizer is DirectionalPanGestureRecognizer {
|
if gestureRecognizer is DirectionalPanGestureRecognizer {
|
||||||
let location = gestureRecognizer.location(in: self.bottomPanelNode.view)
|
let location = gestureRecognizer.location(in: self.bottomPanelNode.view)
|
||||||
if self.audioOutputNode.frame.contains(location) || (!self.cameraButtonNode.isHidden && self.cameraButtonNode.frame.contains(location)) || self.leaveNode.frame.contains(location) {
|
if self.audioButton.frame.contains(location) || (!self.cameraButton.isHidden && self.cameraButton.frame.contains(location)) || self.leaveButton.frame.contains(location) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3810,7 +3892,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.blocksBackgroundWhenInOverlay = true
|
self.blocksBackgroundWhenInOverlay = true
|
||||||
|
|
||||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = .Ignore
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
|
|
||||||
@ -3934,7 +4016,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let overlayController = VoiceChatOverlayController(actionButton: self.controllerNode.actionButton, audioOutputNode: self.controllerNode.audioOutputNode, leaveNode: self.controllerNode.leaveNode, navigationController: self.navigationController as? NavigationController, initiallyHidden: self.dismissedManually)
|
let overlayController = VoiceChatOverlayController(actionButton: self.controllerNode.actionButton, audioOutputNode: self.controllerNode.audioButton, leaveNode: self.controllerNode.leaveButton, navigationController: self.navigationController as? NavigationController, initiallyHidden: self.dismissedManually)
|
||||||
if let navigationController = self.navigationController as? NavigationController {
|
if let navigationController = self.navigationController as? NavigationController {
|
||||||
navigationController.presentOverlay(controller: overlayController, inGlobal: true, blockInteraction: false)
|
navigationController.presentOverlay(controller: overlayController, inGlobal: true, blockInteraction: false)
|
||||||
}
|
}
|
||||||
@ -3948,8 +4030,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
if let strongSelf = self, immediate {
|
if let strongSelf = self, immediate {
|
||||||
strongSelf.controllerNode.actionButton.ignoreHierarchyChanges = true
|
strongSelf.controllerNode.actionButton.ignoreHierarchyChanges = true
|
||||||
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.actionButton)
|
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.actionButton)
|
||||||
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.audioOutputNode)
|
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.audioButton)
|
||||||
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.leaveNode)
|
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.leaveButton)
|
||||||
|
|
||||||
if immediate, let layout = strongSelf.validLayout {
|
if immediate, let layout = strongSelf.validLayout {
|
||||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||||
|
|||||||
@ -70,6 +70,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
|||||||
let expandedText: ParticipantText?
|
let expandedText: ParticipantText?
|
||||||
let icon: Icon
|
let icon: Icon
|
||||||
let enabled: Bool
|
let enabled: Bool
|
||||||
|
let transparent: Bool
|
||||||
public let selectable: Bool
|
public let selectable: Bool
|
||||||
let getAudioLevel: (() -> Signal<Float, NoError>)?
|
let getAudioLevel: (() -> Signal<Float, NoError>)?
|
||||||
let getVideo: () -> GroupVideoNode?
|
let getVideo: () -> GroupVideoNode?
|
||||||
@ -81,7 +82,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
|||||||
let getIsExpanded: () -> Bool
|
let getIsExpanded: () -> Bool
|
||||||
let getUpdatingAvatar: () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>
|
let getUpdatingAvatar: () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, ssrc: UInt32?, presence: PeerPresence?, text: ParticipantText, expandedText: ParticipantText?, icon: Icon, enabled: Bool, selectable: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getIsExpanded: @escaping () -> Bool, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) {
|
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: Peer, ssrc: UInt32?, presence: PeerPresence?, text: ParticipantText, expandedText: ParticipantText?, icon: Icon, enabled: Bool, transparent: Bool, selectable: Bool, getAudioLevel: (() -> Signal<Float, NoError>)?, getVideo: @escaping () -> GroupVideoNode?, revealOptions: [RevealOption], revealed: Bool?, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, action: ((ASDisplayNode) -> Void)?, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, getIsExpanded: @escaping () -> Bool, getUpdatingAvatar: @escaping () -> Signal<(TelegramMediaImageRepresentation, Float)?, NoError>) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
@ -93,6 +94,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
|||||||
self.expandedText = expandedText
|
self.expandedText = expandedText
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
|
self.transparent = transparent
|
||||||
self.selectable = selectable
|
self.selectable = selectable
|
||||||
self.getAudioLevel = getAudioLevel
|
self.getAudioLevel = getAudioLevel
|
||||||
self.getVideo = getVideo
|
self.getVideo = getVideo
|
||||||
|
|||||||
@ -31,7 +31,11 @@ private final class VoiceChatTitleEditInputFieldNode: ASDisplayNode, ASEditableT
|
|||||||
set {
|
set {
|
||||||
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor)
|
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor)
|
||||||
self.placeholderNode.isHidden = !newValue.isEmpty
|
self.placeholderNode.isHidden = !newValue.isEmpty
|
||||||
|
if self.textInputNode.isFirstResponder() {
|
||||||
self.clearButton.isHidden = newValue.isEmpty
|
self.clearButton.isHidden = newValue.isEmpty
|
||||||
|
} else {
|
||||||
|
self.clearButton.isHidden = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,6 +140,14 @@ private final class VoiceChatTitleEditInputFieldNode: ASDisplayNode, ASEditableT
|
|||||||
self.clearButton.isHidden = !self.placeholderNode.isHidden
|
self.clearButton.isHidden = !self.placeholderNode.isHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
||||||
|
self.clearButton.isHidden = (editableTextNode.textView.text ?? "").isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
|
||||||
|
self.clearButton.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||||
let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
|
let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
|
||||||
if updatedText.count > maxLength {
|
if updatedText.count > maxLength {
|
||||||
@ -174,7 +186,6 @@ private final class VoiceChatTitleEditInputFieldNode: ASDisplayNode, ASEditableT
|
|||||||
self.clearButton.isHidden = true
|
self.clearButton.isHidden = true
|
||||||
|
|
||||||
self.textInputNode.attributedText = nil
|
self.textInputNode.attributedText = nil
|
||||||
self.deactivateInput()
|
|
||||||
self.updateHeight?()
|
self.updateHeight?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -453,8 +464,301 @@ func voiceChatTitleEditController(sharedContext: SharedAccountContext, account:
|
|||||||
controller.dismissed = {
|
controller.dismissed = {
|
||||||
presentationDataDisposable.dispose()
|
presentationDataDisposable.dispose()
|
||||||
}
|
}
|
||||||
dismissImpl = { [weak controller] animated in
|
dismissImpl = { [weak controller, weak contentNode] animated in
|
||||||
contentNode.inputFieldNode.deactivateInput()
|
contentNode?.inputFieldNode.deactivateInput()
|
||||||
|
if animated {
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
} else {
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class VoiceChatUserNameEditAlertContentNode: AlertContentNode {
|
||||||
|
private let strings: PresentationStrings
|
||||||
|
private let title: String
|
||||||
|
|
||||||
|
private let titleNode: ASTextNode
|
||||||
|
let firstNameInputFieldNode: VoiceChatTitleEditInputFieldNode
|
||||||
|
let lastNameInputFieldNode: VoiceChatTitleEditInputFieldNode
|
||||||
|
|
||||||
|
private let actionNodesSeparator: ASDisplayNode
|
||||||
|
private let actionNodes: [TextAlertContentActionNode]
|
||||||
|
private let actionVerticalSeparators: [ASDisplayNode]
|
||||||
|
|
||||||
|
private let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
|
private let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
|
var complete: (() -> Void)? {
|
||||||
|
didSet {
|
||||||
|
self.lastNameInputFieldNode.complete = self.complete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var dismissOnOutsideTap: Bool {
|
||||||
|
return self.isUserInteractionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String, firstNamePlaceholder: String, lastNamePlaceholder: String, firstNameValue: String?, lastNameValue: String?, maxLength: Int) {
|
||||||
|
self.strings = strings
|
||||||
|
self.title = title
|
||||||
|
|
||||||
|
self.titleNode = ASTextNode()
|
||||||
|
self.titleNode.maximumNumberOfLines = 2
|
||||||
|
|
||||||
|
self.firstNameInputFieldNode = VoiceChatTitleEditInputFieldNode(theme: ptheme, placeholder: firstNamePlaceholder, maxLength: maxLength)
|
||||||
|
self.firstNameInputFieldNode.text = firstNameValue ?? ""
|
||||||
|
|
||||||
|
self.lastNameInputFieldNode = VoiceChatTitleEditInputFieldNode(theme: ptheme, placeholder: lastNamePlaceholder, maxLength: maxLength)
|
||||||
|
self.lastNameInputFieldNode.text = lastNameValue ?? ""
|
||||||
|
|
||||||
|
self.actionNodesSeparator = ASDisplayNode()
|
||||||
|
self.actionNodesSeparator.isLayerBacked = true
|
||||||
|
|
||||||
|
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||||
|
return TextAlertContentActionNode(theme: theme, action: action)
|
||||||
|
}
|
||||||
|
|
||||||
|
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||||
|
if actions.count > 1 {
|
||||||
|
for _ in 0 ..< actions.count - 1 {
|
||||||
|
let separatorNode = ASDisplayNode()
|
||||||
|
separatorNode.isLayerBacked = true
|
||||||
|
actionVerticalSeparators.append(separatorNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.actionVerticalSeparators = actionVerticalSeparators
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.titleNode)
|
||||||
|
|
||||||
|
self.addSubnode(self.firstNameInputFieldNode)
|
||||||
|
self.addSubnode(self.lastNameInputFieldNode)
|
||||||
|
|
||||||
|
self.addSubnode(self.actionNodesSeparator)
|
||||||
|
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
self.addSubnode(actionNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
for separatorNode in self.actionVerticalSeparators {
|
||||||
|
self.addSubnode(separatorNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.firstNameInputFieldNode.updateHeight = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
if let _ = strongSelf.validLayout {
|
||||||
|
strongSelf.requestLayout?(.animated(duration: 0.15, curve: .spring))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateTheme(theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstName: String {
|
||||||
|
return self.firstNameInputFieldNode.text
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastName: String {
|
||||||
|
return self.lastNameInputFieldNode.text
|
||||||
|
}
|
||||||
|
|
||||||
|
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||||
|
|
||||||
|
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
actionNode.updateTheme(theme)
|
||||||
|
}
|
||||||
|
for separatorNode in self.actionVerticalSeparators {
|
||||||
|
separatorNode.backgroundColor = theme.separatorColor
|
||||||
|
}
|
||||||
|
|
||||||
|
if let size = self.validLayout {
|
||||||
|
_ = self.updateLayout(size: size, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||||
|
var size = size
|
||||||
|
size.width = min(size.width, 270.0)
|
||||||
|
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
|
||||||
|
|
||||||
|
let hadValidLayout = self.validLayout != nil
|
||||||
|
|
||||||
|
self.validLayout = size
|
||||||
|
|
||||||
|
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||||
|
let spacing: CGFloat = 0.0
|
||||||
|
|
||||||
|
let titleSize = self.titleNode.measure(measureSize)
|
||||||
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||||
|
origin.y += titleSize.height + 4.0
|
||||||
|
|
||||||
|
let actionButtonHeight: CGFloat = 44.0
|
||||||
|
var minActionsWidth: CGFloat = 0.0
|
||||||
|
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||||
|
let actionTitleInsets: CGFloat = 8.0
|
||||||
|
|
||||||
|
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||||
|
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
|
||||||
|
effectiveActionLayout = .vertical
|
||||||
|
}
|
||||||
|
switch effectiveActionLayout {
|
||||||
|
case .horizontal:
|
||||||
|
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||||
|
case .vertical:
|
||||||
|
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0)
|
||||||
|
|
||||||
|
var contentWidth = max(titleSize.width, minActionsWidth)
|
||||||
|
contentWidth = max(contentWidth, 234.0)
|
||||||
|
|
||||||
|
var actionsHeight: CGFloat = 0.0
|
||||||
|
switch effectiveActionLayout {
|
||||||
|
case .horizontal:
|
||||||
|
actionsHeight = actionButtonHeight
|
||||||
|
case .vertical:
|
||||||
|
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
let resultWidth = contentWidth + insets.left + insets.right
|
||||||
|
|
||||||
|
let inputFieldWidth = resultWidth
|
||||||
|
let firstInputFieldHeight = self.firstNameInputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
|
||||||
|
transition.updateFrame(node: self.firstNameInputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: firstInputFieldHeight))
|
||||||
|
|
||||||
|
origin.y += firstInputFieldHeight + spacing
|
||||||
|
|
||||||
|
let lastInputFieldHeight = self.lastNameInputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
|
||||||
|
transition.updateFrame(node: self.lastNameInputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: lastInputFieldHeight))
|
||||||
|
|
||||||
|
let resultSize = CGSize(width: resultWidth, height: titleSize.height + firstInputFieldHeight + spacing + lastInputFieldHeight + actionsHeight + insets.top + insets.bottom)
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
|
var actionOffset: CGFloat = 0.0
|
||||||
|
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||||
|
var separatorIndex = -1
|
||||||
|
var nodeIndex = 0
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
if separatorIndex >= 0 {
|
||||||
|
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||||
|
switch effectiveActionLayout {
|
||||||
|
case .horizontal:
|
||||||
|
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||||
|
case .vertical:
|
||||||
|
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
separatorIndex += 1
|
||||||
|
|
||||||
|
let currentActionWidth: CGFloat
|
||||||
|
switch effectiveActionLayout {
|
||||||
|
case .horizontal:
|
||||||
|
if nodeIndex == self.actionNodes.count - 1 {
|
||||||
|
currentActionWidth = resultSize.width - actionOffset
|
||||||
|
} else {
|
||||||
|
currentActionWidth = actionWidth
|
||||||
|
}
|
||||||
|
case .vertical:
|
||||||
|
currentActionWidth = resultSize.width
|
||||||
|
}
|
||||||
|
|
||||||
|
let actionNodeFrame: CGRect
|
||||||
|
switch effectiveActionLayout {
|
||||||
|
case .horizontal:
|
||||||
|
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||||
|
actionOffset += currentActionWidth
|
||||||
|
case .vertical:
|
||||||
|
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||||
|
actionOffset += actionButtonHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||||
|
|
||||||
|
nodeIndex += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hadValidLayout {
|
||||||
|
self.firstNameInputFieldNode.activateInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateError() {
|
||||||
|
if self.firstNameInputFieldNode.text.isEmpty {
|
||||||
|
self.firstNameInputFieldNode.layer.addShakeAnimation()
|
||||||
|
}
|
||||||
|
self.hapticFeedback.error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func voiceChatUserNameController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, firstNamePlaceholder: String, lastNamePlaceholder: String, doneButtonTitle: String? = nil, firstName: String?, lastName: String?, maxLength: Int, apply: @escaping (String, String) -> Void) -> AlertController {
|
||||||
|
var presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||||
|
if let forceTheme = forceTheme {
|
||||||
|
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dismissImpl: ((Bool) -> Void)?
|
||||||
|
var applyImpl: (() -> Void)?
|
||||||
|
|
||||||
|
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||||
|
dismissImpl?(true)
|
||||||
|
}), TextAlertAction(type: .defaultAction, title: doneButtonTitle ?? presentationData.strings.Common_Done, action: {
|
||||||
|
applyImpl?()
|
||||||
|
})]
|
||||||
|
|
||||||
|
let contentNode = VoiceChatUserNameEditAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title, firstNamePlaceholder: firstNamePlaceholder, lastNamePlaceholder: lastNamePlaceholder, firstNameValue: firstName, lastNameValue: lastName, maxLength: maxLength)
|
||||||
|
contentNode.complete = {
|
||||||
|
applyImpl?()
|
||||||
|
}
|
||||||
|
applyImpl = { [weak contentNode] in
|
||||||
|
guard let contentNode = contentNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dismissImpl?(true)
|
||||||
|
|
||||||
|
let previousFirstName = firstName ?? ""
|
||||||
|
let previousLastName = firstName ?? ""
|
||||||
|
|
||||||
|
let newFirstName = contentNode.firstName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
let newLastName = contentNode.lastName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
apply(newFirstName, newLastName)
|
||||||
|
}
|
||||||
|
|
||||||
|
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||||
|
let presentationDataDisposable = sharedContext.presentationData.start(next: { [weak controller, weak contentNode] presentationData in
|
||||||
|
var presentationData = presentationData
|
||||||
|
if let forceTheme = forceTheme {
|
||||||
|
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||||
|
}
|
||||||
|
controller?.theme = AlertControllerTheme(presentationData: presentationData)
|
||||||
|
contentNode?.firstNameInputFieldNode.updateTheme(presentationData.theme)
|
||||||
|
contentNode?.lastNameInputFieldNode.updateTheme(presentationData.theme)
|
||||||
|
})
|
||||||
|
controller.dismissed = {
|
||||||
|
presentationDataDisposable.dispose()
|
||||||
|
}
|
||||||
|
dismissImpl = { [weak controller, weak contentNode] animated in
|
||||||
|
contentNode?.firstNameInputFieldNode.deactivateInput()
|
||||||
|
contentNode?.lastNameInputFieldNode.deactivateInput()
|
||||||
if animated {
|
if animated {
|
||||||
controller?.dismissAnimated()
|
controller?.dismissAnimated()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "ic_call_camerahd.pdf",
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "ic_menu_hdoff.pdf",
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "ic_menu_hdon.pdf",
|
|
||||||
"idiom" : "universal"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
|||||||
|
{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":45,"w":512,"h":512,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Point 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":0,"s":[153.6,256,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":10,"s":[153.6,204,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":20,"s":[153.6,276,0],"to":[0,0,0],"ti":[0,0,0]},{"t":30,"s":[153.6,256,0]}],"ix":2},"a":{"a":0,"k":[-102.4,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":5,"s":[100,100,100]},{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":15,"s":[125,125,100]},{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":25,"s":[95,95,100]},{"t":35,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-17.673,0],[0,-17.673],[17.673,0],[0,17.673]],"o":[[17.673,0],[0,17.673],[-17.673,0],[0,-17.673]],"v":[[-102.4,-32],[-70.4,0],[-102.4,32],[-134.4,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 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":[100,100],"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":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Point 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":5,"s":[256,256,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":15,"s":[256,204,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":25,"s":[256,276,0],"to":[0,0,0],"ti":[0,0,0]},{"t":35,"s":[256,256,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":10,"s":[100,100,100]},{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":20,"s":[125,125,100]},{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":30,"s":[95,95,100]},{"t":40,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-17.673,0],[0,-17.673],[17.673,0],[0,17.673]],"o":[[17.673,0],[0,17.673],[-17.673,0],[0,-17.673]],"v":[[0,-32],[32,0],[0,32],[-32,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 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":[100,100],"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":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Point 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":10,"s":[358.4,256,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":20,"s":[358.4,204,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":30,"s":[358.4,276,0],"to":[0,0,0],"ti":[0,0,0]},{"t":40,"s":[358.4,256,0]}],"ix":2},"a":{"a":0,"k":[102.4,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":15,"s":[100,100,100]},{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":25,"s":[125,125,100]},{"i":{"x":[0.6,0.6,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":35,"s":[95,95,100]},{"t":44,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-17.673,0],[0,-17.673],[17.673,0],[0,17.673]],"o":[[17.673,0],[0,17.673],[-17.673,0],[0,-17.673]],"v":[[102.4,-32],[134.4,0],[102.4,32],[70.4,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 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":[100,100],"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":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0}],"markers":[]}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
|||||||
|
{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":48,"w":512,"h":512,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Line 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[153.7,256.05,0],"ix":2},"a":{"a":0,"k":[-102.3,0.05,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":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-102.3,-50.8],[-102.3,50.9]],"c":false}]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-102.1,-115.632],[-102.1,115.232]],"c":false}]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-102.3,-39.5],[-102.3,39.6]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":30,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-102.1,-97.4],[-102.1,97]],"c":false}]},{"t":45,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-102.3,-50.8],[-102.3,50.9]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":43,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 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":[100,100],"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":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Line 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[256,256,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":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":2,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,-115.5],[0,115.5]],"c":false}]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":12,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,-52],[0,52]],"c":false}]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":22,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,-115.5],[0,115.5]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":32,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,-52],[0,52]],"c":false}]},{"t":46,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[0,-115.5],[0,115.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":43,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 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":[100,100],"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":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Line 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[358.5,256.05,0],"ix":2},"a":{"a":0,"k":[102.5,0.05,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":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":4,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[102.5,-50.8],[102.5,50.9]],"c":false}]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":14,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[102.5,-115.34],[102.5,114.94]],"c":false}]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"t":24,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[102.5,-37.4],[102.5,37.5]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":34,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[102.5,-115.34],[102.5,114.94]],"c":false}]},{"t":47,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[102.5,-50.8],[102.5,50.9]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":43,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 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":[100,100],"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":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0}],"markers":[]}
|
||||||
Binary file not shown.
@ -20,6 +20,7 @@ import UniversalMediaPlayer
|
|||||||
import RadialStatusNode
|
import RadialStatusNode
|
||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import PeerInfoAvatarListNode
|
import PeerInfoAvatarListNode
|
||||||
|
import AnimationUI
|
||||||
|
|
||||||
enum PeerInfoHeaderButtonKey: Hashable {
|
enum PeerInfoHeaderButtonKey: Hashable {
|
||||||
case message
|
case message
|
||||||
@ -53,7 +54,9 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
let referenceNode: ContextReferenceContentNode
|
let referenceNode: ContextReferenceContentNode
|
||||||
let containerNode: ContextControllerSourceNode
|
let containerNode: ContextControllerSourceNode
|
||||||
private let backgroundNode: ASImageNode
|
private let backgroundNode: ASImageNode
|
||||||
|
private let backgroundWithIconNode: ASImageNode
|
||||||
private let textNode: ImmediateTextNode
|
private let textNode: ImmediateTextNode
|
||||||
|
private var animationNode: AnimationNode?
|
||||||
|
|
||||||
private var theme: PresentationTheme?
|
private var theme: PresentationTheme?
|
||||||
private var icon: PeerInfoHeaderButtonIcon?
|
private var icon: PeerInfoHeaderButtonIcon?
|
||||||
@ -70,6 +73,11 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
self.backgroundNode = ASImageNode()
|
self.backgroundNode = ASImageNode()
|
||||||
self.backgroundNode.displaysAsynchronously = false
|
self.backgroundNode.displaysAsynchronously = false
|
||||||
self.backgroundNode.displayWithoutProcessing = true
|
self.backgroundNode.displayWithoutProcessing = true
|
||||||
|
self.backgroundNode.isHidden = true
|
||||||
|
|
||||||
|
self.backgroundWithIconNode = ASImageNode()
|
||||||
|
self.backgroundWithIconNode.displaysAsynchronously = false
|
||||||
|
self.backgroundWithIconNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
self.textNode = ImmediateTextNode()
|
self.textNode = ImmediateTextNode()
|
||||||
self.textNode.displaysAsynchronously = false
|
self.textNode.displaysAsynchronously = false
|
||||||
@ -80,6 +88,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
|
|
||||||
self.containerNode.addSubnode(self.referenceNode)
|
self.containerNode.addSubnode(self.referenceNode)
|
||||||
self.referenceNode.addSubnode(self.backgroundNode)
|
self.referenceNode.addSubnode(self.backgroundNode)
|
||||||
|
self.referenceNode.addSubnode(self.backgroundWithIconNode)
|
||||||
self.addSubnode(self.containerNode)
|
self.addSubnode(self.containerNode)
|
||||||
self.addSubnode(self.textNode)
|
self.addSubnode(self.textNode)
|
||||||
|
|
||||||
@ -105,10 +114,20 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@objc private func buttonPressed() {
|
@objc private func buttonPressed() {
|
||||||
|
switch self.icon {
|
||||||
|
case .voiceChat:
|
||||||
|
self.animationNode?.playOnce()
|
||||||
|
case .more:
|
||||||
|
self.animationNode?.playOnce()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
self.action(self, nil)
|
self.action(self, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isActive: Bool, isExpanded: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
func update(size: CGSize, text: String, icon: PeerInfoHeaderButtonIcon, isActive: Bool, isExpanded: Bool, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
|
||||||
|
let previousIcon = self.icon
|
||||||
|
let iconUpdated = self.icon != icon
|
||||||
if self.theme != presentationData.theme || self.icon != icon || self.isActive != isActive {
|
if self.theme != presentationData.theme || self.icon != icon || self.isActive != isActive {
|
||||||
self.theme = presentationData.theme
|
self.theme = presentationData.theme
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
@ -122,11 +141,87 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
}
|
}
|
||||||
self.containerNode.isGestureEnabled = isGestureEnabled
|
self.containerNode.isGestureEnabled = isGestureEnabled
|
||||||
|
|
||||||
if isActiveUpdated, !self.containerNode.alpha.isZero {
|
let animationName: String?
|
||||||
if let snapshotView = self.backgroundNode.view.snapshotContentTree() {
|
var colors: [String: UIColor] = [:]
|
||||||
snapshotView.frame = self.backgroundNode.view.frame
|
var playOnce = false
|
||||||
self.view.addSubview(snapshotView)
|
var seekToEnd = false
|
||||||
|
let iconColor = presentationData.theme.list.itemCheckColors.foregroundColor
|
||||||
|
switch icon {
|
||||||
|
case .voiceChat:
|
||||||
|
animationName = "anim_profilevc"
|
||||||
|
colors = ["Line 3.Group 1.Stroke 1": iconColor,
|
||||||
|
"Line 1.Group 1.Stroke 1": iconColor,
|
||||||
|
"Line 2.Group 1.Stroke 1": iconColor]
|
||||||
|
case .mute:
|
||||||
|
animationName = "anim_profileunmute"
|
||||||
|
colors = ["Middle.Group 1.Fill 1": iconColor,
|
||||||
|
"Top.Group 1.Fill 1": iconColor,
|
||||||
|
"Bottom.Group 1.Fill 1": iconColor,
|
||||||
|
"Line.Group 1.Stroke 1": iconColor]
|
||||||
|
if previousIcon == .unmute {
|
||||||
|
playOnce = true
|
||||||
|
} else {
|
||||||
|
seekToEnd = true
|
||||||
|
}
|
||||||
|
case .unmute:
|
||||||
|
animationName = "anim_profilemute"
|
||||||
|
colors = ["Middle.Group 1.Fill 1": iconColor,
|
||||||
|
"Top.Group 1.Fill 1": iconColor,
|
||||||
|
"Bottom.Group 1.Fill 1": iconColor,
|
||||||
|
"Line.Group 1.Stroke 1": iconColor]
|
||||||
|
if previousIcon == .mute {
|
||||||
|
playOnce = true
|
||||||
|
} else {
|
||||||
|
seekToEnd = true
|
||||||
|
}
|
||||||
|
case .more:
|
||||||
|
animationName = "anim_profilemore"
|
||||||
|
colors = ["Point 2.Group 1.Fill 1": iconColor,
|
||||||
|
"Point 3.Group 1.Fill 1": iconColor,
|
||||||
|
"Point 1.Group 1.Fill 1": iconColor]
|
||||||
|
case .leave:
|
||||||
|
animationName = "anim_profileleave"
|
||||||
|
default:
|
||||||
|
animationName = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let animationName = animationName {
|
||||||
|
let animationNode: AnimationNode
|
||||||
|
if let current = self.animationNode {
|
||||||
|
animationNode = current
|
||||||
|
if iconUpdated {
|
||||||
|
animationNode.setAnimation(name: animationName, colors: colors)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
animationNode = AnimationNode(animation: animationName, colors: colors, scale: 1.0)
|
||||||
|
self.addSubnode(animationNode)
|
||||||
|
self.animationNode = animationNode
|
||||||
|
}
|
||||||
|
animationNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.backgroundWithIconNode.isHidden = true
|
||||||
|
self.backgroundNode.isHidden = false
|
||||||
|
} else if let animationNode = self.animationNode {
|
||||||
|
self.animationNode = nil
|
||||||
|
animationNode.removeFromSupernode()
|
||||||
|
self.backgroundWithIconNode.isHidden = false
|
||||||
|
self.backgroundNode.isHidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if playOnce {
|
||||||
|
self.animationNode?.play()
|
||||||
|
} else if seekToEnd {
|
||||||
|
self.animationNode?.seekToEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isActiveUpdated, !self.containerNode.alpha.isZero {
|
||||||
|
let backgroundNode = !self.backgroundNode.isHidden ? self.backgroundNode : self.backgroundWithIconNode
|
||||||
|
if let snapshotView = backgroundNode.view.snapshotContentTree() {
|
||||||
|
snapshotView.frame = backgroundNode.view.frame
|
||||||
|
if let animationNode = self.animationNode {
|
||||||
|
self.view.insertSubview(snapshotView, belowSubview: animationNode.view)
|
||||||
|
} else {
|
||||||
|
self.view.addSubview(snapshotView)
|
||||||
|
}
|
||||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||||
snapshotView?.removeFromSuperview()
|
snapshotView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
@ -141,13 +236,14 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.backgroundNode.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
|
self.backgroundNode.image = generateFilledCircleImage(diameter: 40.0, color: isActive ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemDisabledTextColor)
|
||||||
|
self.backgroundWithIconNode.image = generateImage(CGSize(width: 40.0, height: 40.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
context.setFillColor(isActive ? presentationData.theme.list.itemAccentColor.cgColor : presentationData.theme.list.itemDisabledTextColor.cgColor)
|
context.setFillColor(isActive ? presentationData.theme.list.itemAccentColor.cgColor : presentationData.theme.list.itemDisabledTextColor.cgColor)
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||||
context.setBlendMode(.normal)
|
context.setBlendMode(.normal)
|
||||||
context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor)
|
context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor)
|
||||||
let imageName: String
|
let imageName: String?
|
||||||
switch icon {
|
switch icon {
|
||||||
case .message:
|
case .message:
|
||||||
imageName = "Peer Info/ButtonMessage"
|
imageName = "Peer Info/ButtonMessage"
|
||||||
@ -156,21 +252,21 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
case .videoCall:
|
case .videoCall:
|
||||||
imageName = "Peer Info/ButtonVideo"
|
imageName = "Peer Info/ButtonVideo"
|
||||||
case .voiceChat:
|
case .voiceChat:
|
||||||
imageName = "Peer Info/ButtonVoiceChat"
|
imageName = nil
|
||||||
case .mute:
|
case .mute:
|
||||||
imageName = "Peer Info/ButtonMute"
|
imageName = nil
|
||||||
case .unmute:
|
case .unmute:
|
||||||
imageName = "Peer Info/ButtonUnmute"
|
imageName = nil
|
||||||
case .more:
|
case .more:
|
||||||
imageName = "Peer Info/ButtonMore"
|
imageName = nil
|
||||||
case .addMember:
|
case .addMember:
|
||||||
imageName = "Peer Info/ButtonAddMember"
|
imageName = "Peer Info/ButtonAddMember"
|
||||||
case .search:
|
case .search:
|
||||||
imageName = "Peer Info/ButtonSearch"
|
imageName = "Peer Info/ButtonSearch"
|
||||||
case .leave:
|
case .leave:
|
||||||
imageName = "Peer Info/ButtonLeave"
|
imageName = nil
|
||||||
}
|
}
|
||||||
if let image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: .white) {
|
if let imageName = imageName, let image = generateTintedImage(image: UIImage(bundleImageName: imageName), color: .white) {
|
||||||
let imageRect = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
|
let imageRect = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
|
||||||
context.clip(to: imageRect, mask: image.cgImage!)
|
context.clip(to: imageRect, mask: image.cgImage!)
|
||||||
context.fill(imageRect)
|
context.fill(imageRect)
|
||||||
@ -184,6 +280,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
|||||||
|
|
||||||
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
transition.updateFrame(node: self.backgroundWithIconNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
transition.updateFrameAdditiveToCenter(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: size.height + 6.0), size: titleSize))
|
transition.updateFrameAdditiveToCenter(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: size.height + 6.0), size: titleSize))
|
||||||
transition.updateAlpha(node: self.textNode, alpha: isExpanded ? 0.0 : 1.0)
|
transition.updateAlpha(node: self.textNode, alpha: isExpanded ? 0.0 : 1.0)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user