mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +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.EditBioPlaceholder" = "Bio";
|
||||
"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.SendPublicLinkSend" = "Send";
|
||||
@ -6330,3 +6330,10 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.AddPhoto" = "Add Photo";
|
||||
"VoiceChat.AddBio" = "Add Bio";
|
||||
"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 completion: (() -> Void)?
|
||||
private var internalCompletion: (() -> Void)?
|
||||
|
||||
public var isPlaying: Bool {
|
||||
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) {
|
||||
self.didPlay = false
|
||||
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]) {
|
||||
self.didPlay = false
|
||||
self.animationView()?.setAnimation(json: json)
|
||||
}
|
||||
|
||||
@ -103,7 +117,7 @@ public final class AnimationNode : ASDisplayNode {
|
||||
}
|
||||
|
||||
public func play() {
|
||||
if let animationView = animationView(), !animationView.isAnimationPlaying && !self.didPlay {
|
||||
if let animationView = self.animationView(), !animationView.isAnimationPlaying && !self.didPlay {
|
||||
self.didPlay = true
|
||||
animationView.play { [weak self] _ in
|
||||
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() {
|
||||
if let animationView = animationView() {
|
||||
if let animationView = self.animationView() {
|
||||
animationView.loopAnimation = true
|
||||
animationView.play()
|
||||
}
|
||||
|
@ -965,7 +965,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
|
||||
let resolvedMessage = .single(nil)
|
||||
|> then(context.sharedContext.resolveUrl(context: context, peerId: nil, url: finalQuery, skipUrlAuth: true)
|
||||
|> then(context.sharedContext.resolveUrl(context: context, peerId: nil, url: finalQuery, skipUrlAuth: true)
|
||||
|> mapToSignal { resolvedUrl -> Signal<Message?, NoError> in
|
||||
if case let .channelMessage(_, messageId) = resolvedUrl {
|
||||
return downloadMessage(postbox: context.account.postbox, network: context.account.network, messageId: messageId)
|
||||
|
@ -56,6 +56,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
}
|
||||
}
|
||||
|
||||
private let wrapperNode: ASDisplayNode
|
||||
private let contentContainer: ASDisplayNode
|
||||
private let effectView: UIVisualEffectView
|
||||
private let contentBackgroundNode: ASImageNode
|
||||
@ -70,6 +71,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
private(set) var currentText: String = ""
|
||||
|
||||
init() {
|
||||
self.wrapperNode = ASDisplayNode()
|
||||
self.contentContainer = ASDisplayNode()
|
||||
|
||||
self.effectView = UIVisualEffectView()
|
||||
@ -94,10 +96,11 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
|
||||
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.addSubnode(self.textNode)
|
||||
self.wrapperNode.addSubnode(self.textNode)
|
||||
|
||||
self.contentContainer.view.addSubview(self.effectView)
|
||||
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) {
|
||||
let scaleFactor = size.width / self.largeButtonSize
|
||||
|
||||
@ -164,8 +172,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
||||
self.effectView.isHidden = true
|
||||
}
|
||||
|
||||
transition.updateAlpha(node: self, alpha: content.isEnabled ? 1.0 : 0.4)
|
||||
self.isUserInteractionEnabled = content.isEnabled
|
||||
transition.updateAlpha(node: self.wrapperNode, alpha: content.isEnabled ? 1.0 : 0.4)
|
||||
self.wrapperNode.isUserInteractionEnabled = content.isEnabled
|
||||
|
||||
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 blobSize = CGSize(width: 190.0, height: 190.0)
|
||||
|
||||
private let smallScale: CGFloat = 0.48
|
||||
private let smallIconScale: CGFloat = 0.69
|
||||
|
||||
final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
enum State: Equatable {
|
||||
enum ActiveState: Equatable {
|
||||
@ -80,13 +83,18 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
var wasActiveWhenPressed = false
|
||||
var pressing: Bool = false {
|
||||
didSet {
|
||||
guard let (_, _, state, _, _, _, _, snap) = self.currentParams, !self.isDisabled else {
|
||||
guard let (_, _, state, _, small, _, _, snap) = self.currentParams, !self.isDisabled else {
|
||||
return
|
||||
}
|
||||
if self.pressing {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
transition.updateTransformScale(node: self.iconNode, scale: snap ? 0.5 : 0.9)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
switch state {
|
||||
case let .active(state):
|
||||
switch state {
|
||||
@ -100,7 +108,12 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
}
|
||||
} else {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
transition.updateTransformScale(node: self.iconNode, scale: snap ? 0.5 : 1.0)
|
||||
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)
|
||||
}
|
||||
self.wasActiveWhenPressed = false
|
||||
}
|
||||
}
|
||||
@ -127,15 +140,25 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
|
||||
self.highligthedChanged = { [weak self] pressing in
|
||||
if let strongSelf = self {
|
||||
guard let (_, _, _, _, _, _, _, snap) = strongSelf.currentParams else {
|
||||
guard let (_, _, _, _, small, _, _, snap) = strongSelf.currentParams else {
|
||||
return
|
||||
}
|
||||
if pressing {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 0.9)
|
||||
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)
|
||||
}
|
||||
} else if !strongSelf.pressing {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
transition.updateTransformScale(node: strongSelf.iconNode, scale: snap ? 0.5 : 1.0)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -222,8 +245,14 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 0.0)
|
||||
} else {
|
||||
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)
|
||||
transition.updateTransformScale(node: self.iconNode, scale: self.pressing ? 0.9 : 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.updateAlpha(node: self.titleLabel, 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)
|
||||
|
@ -34,8 +34,10 @@ private let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||
private let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
||||
private let fullscreenBackgroundColor = UIColor(rgb: 0x000000)
|
||||
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 bottomAreaHeight: CGFloat = 205.0
|
||||
private let fullscreenBottomAreaHeight: CGFloat = 80.0
|
||||
|
||||
private func cornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
|
||||
if !top && !bottom {
|
||||
@ -604,10 +606,21 @@ public final class VoiceChatController: ViewController {
|
||||
if let muteState = peerEntry.muteState, case .speaking = state, muteState.mutedByYou || !muteState.canUnmute {
|
||||
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 {
|
||||
case .listening:
|
||||
if peerEntry.isMyPeer {
|
||||
text = .text(presentationData.strings.VoiceChat_You, .accent)
|
||||
text = .text(yourText, .accent)
|
||||
} else if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, .destructive)
|
||||
} else if let about = peerEntry.about, !about.isEmpty {
|
||||
@ -640,7 +653,7 @@ public final class VoiceChatController: ViewController {
|
||||
icon = .invite(true)
|
||||
case .raisedHand:
|
||||
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 {
|
||||
text = .text(about, .generic)
|
||||
} else {
|
||||
@ -655,7 +668,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
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 {
|
||||
return interaction.getPeerVideo(ssrc)
|
||||
} else {
|
||||
@ -709,9 +722,10 @@ public final class VoiceChatController: ViewController {
|
||||
fileprivate let bottomPanelNode: ASDisplayNode
|
||||
private let bottomPanelBackgroundNode: ASDisplayNode
|
||||
private let bottomCornersNode: ASImageNode
|
||||
fileprivate let audioOutputNode: CallControllerButtonItemNode
|
||||
fileprivate let cameraButtonNode: CallControllerButtonItemNode
|
||||
fileprivate let leaveNode: CallControllerButtonItemNode
|
||||
fileprivate let audioButton: CallControllerButtonItemNode
|
||||
fileprivate let cameraButton: CallControllerButtonItemNode
|
||||
fileprivate let switchCameraButton: CallControllerButtonItemNode
|
||||
fileprivate let leaveButton: CallControllerButtonItemNode
|
||||
fileprivate let actionButton: VoiceChatActionButton
|
||||
private let leftBorderNode: ASDisplayNode
|
||||
private let rightBorderNode: ASDisplayNode
|
||||
@ -777,13 +791,9 @@ public final class VoiceChatController: ViewController {
|
||||
private let reconnectedAsEventsDisposable = MetaDisposable()
|
||||
private let voiceSourcesDisposable = MetaDisposable()
|
||||
|
||||
private var requestedVideoSources = Set<UInt32>()
|
||||
private var videoNodes: [(PeerId, UInt32, GroupVideoNode)] = []
|
||||
|
||||
private let displayAsPeersPromise = Promise<[FoundPeer]>([])
|
||||
private let inviteLinksPromise = Promise<GroupCallInviteLinks?>(nil)
|
||||
|
||||
|
||||
private var raisedHandDisplayDisposables: [PeerId: Disposable] = [:]
|
||||
private var displayedRaisedHands = Set<PeerId>() {
|
||||
didSet {
|
||||
@ -792,12 +802,21 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
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 updateAvatarDisposable = MetaDisposable()
|
||||
private let updateAvatarPromise = Promise<(TelegramMediaImageRepresentation, Float)?>(nil)
|
||||
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) {
|
||||
self.controller = controller
|
||||
self.sharedContext = sharedContext
|
||||
@ -869,9 +888,12 @@ public final class VoiceChatController: ViewController {
|
||||
self.bottomCornersNode.displayWithoutProcessing = true
|
||||
self.bottomCornersNode.image = cornersImage(top: false, bottom: true, dark: false)
|
||||
|
||||
self.audioOutputNode = CallControllerButtonItemNode()
|
||||
self.cameraButtonNode = CallControllerButtonItemNode()
|
||||
self.leaveNode = CallControllerButtonItemNode()
|
||||
self.audioButton = CallControllerButtonItemNode()
|
||||
self.cameraButton = CallControllerButtonItemNode()
|
||||
self.switchCameraButton = CallControllerButtonItemNode()
|
||||
self.switchCameraButton.alpha = 0.0
|
||||
self.switchCameraButton.isUserInteractionEnabled = false
|
||||
self.leaveButton = CallControllerButtonItemNode()
|
||||
self.actionButton = VoiceChatActionButton()
|
||||
|
||||
self.leftBorderNode = ASDisplayNode()
|
||||
@ -1245,8 +1267,8 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
if strongSelf.context.sharedContext.immediateExperimentalUISettings.demoVideoChats {
|
||||
items.append(.action(ContextMenuActionItem(text: "Toggle Full Screen", icon: { theme in
|
||||
return nil
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_PinVideo, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { _, f in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -1306,12 +1328,34 @@ public final class VoiceChatController: ViewController {
|
||||
return .complete()
|
||||
}).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))
|
||||
}
|
||||
})))
|
||||
|
||||
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 {
|
||||
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
|
||||
if callState.adminIds.contains(peer.id) {
|
||||
@ -1499,9 +1543,12 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.bottomPanelNode.addSubnode(self.bottomCornersNode)
|
||||
self.bottomPanelNode.addSubnode(self.bottomPanelBackgroundNode)
|
||||
self.bottomPanelNode.addSubnode(self.audioOutputNode)
|
||||
//self.bottomPanelNode.addSubnode(self.cameraButtonNode)
|
||||
self.bottomPanelNode.addSubnode(self.leaveNode)
|
||||
self.bottomPanelNode.addSubnode(self.audioButton)
|
||||
if let mainVideoContainer = self.mainVideoContainer {
|
||||
self.bottomPanelNode.addSubnode(self.cameraButton)
|
||||
self.bottomPanelNode.addSubnode(self.switchCameraButton)
|
||||
}
|
||||
self.bottomPanelNode.addSubnode(self.leaveButton)
|
||||
self.bottomPanelNode.addSubnode(self.actionButton)
|
||||
|
||||
self.addSubnode(self.dimNode)
|
||||
@ -1684,13 +1731,11 @@ public final class VoiceChatController: ViewController {
|
||||
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.audioOutputNode.addTarget(self, action: #selector(self.audioOutputPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.cameraButtonNode.addTarget(self, action: #selector(self.cameraPressed), forControlEvents: .touchUpInside)
|
||||
self.audioButton.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.optionsButton.contextAction = { [weak self] sourceNode, gesture in
|
||||
self?.openContextMenu(sourceNode: sourceNode, gesture: gesture)
|
||||
@ -1830,27 +1875,21 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.titleNode.tapped = { [weak self] in
|
||||
if let strongSelf = self, !strongSelf.titleNode.recordingIconNode.isHidden {
|
||||
var ignore = false
|
||||
var hasTooltipAlready = false
|
||||
strongSelf.controller?.forEachController { controller -> Bool in
|
||||
if controller is TooltipScreen {
|
||||
ignore = true
|
||||
hasTooltipAlready = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
guard !ignore else {
|
||||
return
|
||||
if !hasTooltipAlready {
|
||||
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
|
||||
return .dismiss(consume: true)
|
||||
}), in: .window(.root))
|
||||
}
|
||||
|
||||
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
|
||||
return .dismiss(consume: true)
|
||||
}), in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
//self.isFullscreen = true
|
||||
//self.isExpanded = true
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -1873,14 +1912,12 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
private func openContextMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
|
||||
let canManageCall = !self.optionsButtonIsAvatar
|
||||
|
||||
let items: Signal<[ContextMenuItem], NoError>
|
||||
if canManageCall {
|
||||
items = self.contextMenuMainItems()
|
||||
} else {
|
||||
items = self.contextMenuDisplayAsItems()
|
||||
}
|
||||
|
||||
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)
|
||||
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) {
|
||||
guard let (layout, _) = self.validLayout else {
|
||||
return
|
||||
@ -2584,7 +2634,7 @@ public final class VoiceChatController: ViewController {
|
||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||
let topPanelHeight: CGFloat = 63.0
|
||||
let listTopInset = layoutTopInset + topPanelHeight
|
||||
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
||||
let bottomPanelHeight = self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom
|
||||
|
||||
var size = layout.size
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
@ -2774,13 +2824,6 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
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 hasAudioRouteMenu: Bool = false
|
||||
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
|
||||
var soundAppearance: CallControllerButtonItemNode.Content.Appearance = audioButtonAppearance
|
||||
var soundAppearance: CallControllerButtonItemNode.Content.Appearance = coloredButtonAppearance
|
||||
var soundTitle: String = self.presentationData.strings.Call_Speaker
|
||||
switch audioMode {
|
||||
case .none, .builtin:
|
||||
@ -2831,13 +2881,30 @@ public final class VoiceChatController: ViewController {
|
||||
soundTitle = self.presentationData.strings.Call_Audio
|
||||
}
|
||||
|
||||
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: soundTitle, transition: animated ? .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.switchCameraButton.update(size: videoButtonSize, content: CallControllerButtonItemNode.Content(appearance: coloredButtonAppearance, image: .flipCamera), text: "", 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.audioButton.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: soundTitle, 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.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) {
|
||||
@ -2883,7 +2950,7 @@ public final class VoiceChatController: ViewController {
|
||||
transition.updateFrame(node: self.topPanelEdgeNode, frame: topEdgeFrame)
|
||||
}
|
||||
|
||||
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
||||
let bottomPanelHeight = self.effectiveBottomAreaHeight + layout.intrinsicInsets.bottom
|
||||
var listTopInset = layoutTopInset + topPanelHeight
|
||||
if self.mainVideoContainer != nil {
|
||||
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))
|
||||
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 actionButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - centralButtonSize.width) / 2.0), y: floorToScreenPixels((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
|
||||
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 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 actionButtonTitle: String
|
||||
@ -2966,10 +3062,10 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
|
||||
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 {
|
||||
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
||||
transition.updateFrame(node: self.actionButton, frame: thirdButtonFrame)
|
||||
}
|
||||
|
||||
self.updateButtons(animated: !isFirstTime)
|
||||
@ -2991,25 +3087,11 @@ public final class VoiceChatController: ViewController {
|
||||
currentVideoOrigin.x += videoSize.width + 4.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)
|
||||
|
||||
if self.audioOutputNode.supernode === self.bottomPanelNode {
|
||||
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 self.audioButton.supernode === self.bottomPanelNode {
|
||||
transition.updateFrame(node: self.cameraButton, frame: firstButtonFrame)
|
||||
transition.updateFrame(node: self.switchCameraButton, frame: firstButtonFrame)
|
||||
transition.updateFrame(node: self.audioButton, frame: secondButtonFrame)
|
||||
transition.updateFrame(node: self.leaveButton, frame: forthButtonFrame)
|
||||
}
|
||||
if isFirstTime {
|
||||
while !self.enqueuedTransitions.isEmpty {
|
||||
@ -3034,15 +3116,15 @@ public final class VoiceChatController: ViewController {
|
||||
}, completion: { _ in
|
||||
if self.actionButton.supernode !== self.bottomPanelNode {
|
||||
self.actionButton.ignoreHierarchyChanges = true
|
||||
self.audioOutputNode.isHidden = false
|
||||
self.cameraButtonNode.isHidden = false
|
||||
self.leaveNode.isHidden = false
|
||||
self.audioOutputNode.layer.removeAllAnimations()
|
||||
self.cameraButtonNode.layer.removeAllAnimations()
|
||||
self.leaveNode.layer.removeAllAnimations()
|
||||
self.bottomPanelNode.addSubnode(self.audioOutputNode)
|
||||
self.audioButton.isHidden = false
|
||||
self.cameraButton.isHidden = false
|
||||
self.leaveButton.isHidden = false
|
||||
self.audioButton.layer.removeAllAnimations()
|
||||
self.cameraButton.layer.removeAllAnimations()
|
||||
self.leaveButton.layer.removeAllAnimations()
|
||||
self.bottomPanelNode.addSubnode(self.audioButton)
|
||||
//self.bottomPanelNode.addSubnode(self.cameraButtonNode)
|
||||
self.bottomPanelNode.addSubnode(self.leaveNode)
|
||||
self.bottomPanelNode.addSubnode(self.leaveButton)
|
||||
self.bottomPanelNode.addSubnode(self.actionButton)
|
||||
self.containerLayoutUpdated(layout, navigationHeight :navigationHeight, transition: .immediate)
|
||||
self.actionButton.ignoreHierarchyChanges = false
|
||||
@ -3134,7 +3216,7 @@ public final class VoiceChatController: ViewController {
|
||||
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 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 {
|
||||
if gestureRecognizer is DirectionalPanGestureRecognizer {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -3810,7 +3892,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
@ -3934,7 +4016,7 @@ public final class VoiceChatController: ViewController {
|
||||
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 {
|
||||
navigationController.presentOverlay(controller: overlayController, inGlobal: true, blockInteraction: false)
|
||||
}
|
||||
@ -3948,8 +4030,8 @@ public final class VoiceChatController: ViewController {
|
||||
if let strongSelf = self, immediate {
|
||||
strongSelf.controllerNode.actionButton.ignoreHierarchyChanges = true
|
||||
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.actionButton)
|
||||
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.audioOutputNode)
|
||||
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.leaveNode)
|
||||
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.audioButton)
|
||||
strongSelf.controllerNode.bottomPanelNode.addSubnode(strongSelf.controllerNode.leaveButton)
|
||||
|
||||
if immediate, let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||
|
@ -70,6 +70,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
||||
let expandedText: ParticipantText?
|
||||
let icon: Icon
|
||||
let enabled: Bool
|
||||
let transparent: Bool
|
||||
public let selectable: Bool
|
||||
let getAudioLevel: (() -> Signal<Float, NoError>)?
|
||||
let getVideo: () -> GroupVideoNode?
|
||||
@ -81,7 +82,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
||||
let getIsExpanded: () -> Bool
|
||||
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.dateTimeFormat = dateTimeFormat
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
@ -93,6 +94,7 @@ final class VoiceChatParticipantItem: ListViewItem {
|
||||
self.expandedText = expandedText
|
||||
self.icon = icon
|
||||
self.enabled = enabled
|
||||
self.transparent = transparent
|
||||
self.selectable = selectable
|
||||
self.getAudioLevel = getAudioLevel
|
||||
self.getVideo = getVideo
|
||||
|
@ -31,7 +31,11 @@ private final class VoiceChatTitleEditInputFieldNode: ASDisplayNode, ASEditableT
|
||||
set {
|
||||
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor)
|
||||
self.placeholderNode.isHidden = !newValue.isEmpty
|
||||
self.clearButton.isHidden = newValue.isEmpty
|
||||
if self.textInputNode.isFirstResponder() {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
let updatedText = (editableTextNode.textView.text as NSString).replacingCharacters(in: range, with: text)
|
||||
if updatedText.count > maxLength {
|
||||
@ -174,7 +186,6 @@ private final class VoiceChatTitleEditInputFieldNode: ASDisplayNode, ASEditableT
|
||||
self.clearButton.isHidden = true
|
||||
|
||||
self.textInputNode.attributedText = nil
|
||||
self.deactivateInput()
|
||||
self.updateHeight?()
|
||||
}
|
||||
}
|
||||
@ -453,8 +464,301 @@ func voiceChatTitleEditController(sharedContext: SharedAccountContext, account:
|
||||
controller.dismissed = {
|
||||
presentationDataDisposable.dispose()
|
||||
}
|
||||
dismissImpl = { [weak controller] animated in
|
||||
contentNode.inputFieldNode.deactivateInput()
|
||||
dismissImpl = { [weak controller, weak contentNode] animated in
|
||||
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 {
|
||||
controller?.dismissAnimated()
|
||||
} 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 TelegramUIPreferences
|
||||
import PeerInfoAvatarListNode
|
||||
import AnimationUI
|
||||
|
||||
enum PeerInfoHeaderButtonKey: Hashable {
|
||||
case message
|
||||
@ -53,7 +54,9 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
private let backgroundNode: ASImageNode
|
||||
private let backgroundWithIconNode: ASImageNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private var animationNode: AnimationNode?
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
private var icon: PeerInfoHeaderButtonIcon?
|
||||
@ -70,6 +73,11 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.isHidden = true
|
||||
|
||||
self.backgroundWithIconNode = ASImageNode()
|
||||
self.backgroundWithIconNode.displaysAsynchronously = false
|
||||
self.backgroundWithIconNode.displayWithoutProcessing = true
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
@ -80,6 +88,7 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
|
||||
self.containerNode.addSubnode(self.referenceNode)
|
||||
self.referenceNode.addSubnode(self.backgroundNode)
|
||||
self.referenceNode.addSubnode(self.backgroundWithIconNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
@ -105,10 +114,20 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
switch self.icon {
|
||||
case .voiceChat:
|
||||
self.animationNode?.playOnce()
|
||||
case .more:
|
||||
self.animationNode?.playOnce()
|
||||
default:
|
||||
break
|
||||
}
|
||||
self.action(self, nil)
|
||||
}
|
||||
|
||||
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 {
|
||||
self.theme = presentationData.theme
|
||||
self.icon = icon
|
||||
@ -121,12 +140,88 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
isGestureEnabled = true
|
||||
}
|
||||
self.containerNode.isGestureEnabled = isGestureEnabled
|
||||
|
||||
let animationName: String?
|
||||
var colors: [String: UIColor] = [:]
|
||||
var playOnce = false
|
||||
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 {
|
||||
if let snapshotView = self.backgroundNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.backgroundNode.view.frame
|
||||
self.view.addSubview(snapshotView)
|
||||
|
||||
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?.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.setFillColor(isActive ? presentationData.theme.list.itemAccentColor.cgColor : presentationData.theme.list.itemDisabledTextColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
context.setBlendMode(.normal)
|
||||
context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor)
|
||||
let imageName: String
|
||||
let imageName: String?
|
||||
switch icon {
|
||||
case .message:
|
||||
imageName = "Peer Info/ButtonMessage"
|
||||
@ -156,21 +252,21 @@ final class PeerInfoHeaderButtonNode: HighlightableButtonNode {
|
||||
case .videoCall:
|
||||
imageName = "Peer Info/ButtonVideo"
|
||||
case .voiceChat:
|
||||
imageName = "Peer Info/ButtonVoiceChat"
|
||||
imageName = nil
|
||||
case .mute:
|
||||
imageName = "Peer Info/ButtonMute"
|
||||
imageName = nil
|
||||
case .unmute:
|
||||
imageName = "Peer Info/ButtonUnmute"
|
||||
imageName = nil
|
||||
case .more:
|
||||
imageName = "Peer Info/ButtonMore"
|
||||
imageName = nil
|
||||
case .addMember:
|
||||
imageName = "Peer Info/ButtonAddMember"
|
||||
case .search:
|
||||
imageName = "Peer Info/ButtonSearch"
|
||||
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)
|
||||
context.clip(to: imageRect, mask: image.cgImage!)
|
||||
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.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.updateAlpha(node: self.textNode, alpha: isExpanded ? 0.0 : 1.0)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user