Various Improvements

This commit is contained in:
Ilya Laktyushin 2021-03-30 21:47:10 +03:00
parent 2c75698e90
commit 5704920d45
22 changed files with 5078 additions and 4549 deletions

View File

@ -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";

View File

@ -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()
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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":[]}

View File

@ -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)