mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Voice Chats Improvements
This commit is contained in:
parent
52f9e9b42f
commit
025b09b8c0
@ -6185,10 +6185,24 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"Contacts.VoiceOver.AddContact" = "Add Contact";
|
"Contacts.VoiceOver.AddContact" = "Add Contact";
|
||||||
|
|
||||||
"VoiceChat.InviteLinks.Speaker" = "Speaker";
|
"VoiceChat.DisplayAs" = "Display Me As...";
|
||||||
"VoiceChat.InviteLinks.Listener" = "Listener";
|
"VoiceChat.DisplayAsInfo" = "Choose whether you want to be displayed as your personal account or as your channel.";
|
||||||
"VoiceChat.InviteLinks.CopySpeakerLink" = "Copy Speaker Link";
|
"VoiceChat.PersonalAccount" = "personal account";
|
||||||
"VoiceChat.InviteLinks.CopyListenerLink" = "Copy Listener Link";
|
"VoiceChat.EditTitle" = "Edit Voice Chat Title";
|
||||||
|
"VoiceChat.EditPermissions" = "Edit Permissions";
|
||||||
|
|
||||||
|
"VoiceChat.OpenChannel" = "Open Channel";
|
||||||
|
|
||||||
|
"VoiceChat.DisplayAsSuccess" = "Members of this voice chat will now see your as **%@**.";
|
||||||
|
|
||||||
|
"VoiceChat.EditTitleTitle" = "Voice Chat Title";
|
||||||
|
"VoiceChat.EditTitleText" = "Edit a title of this voice chat.";
|
||||||
|
"VoiceChat.EditTitleSuccess" = "Voice chat title changed to **%@**.";
|
||||||
|
|
||||||
|
"VoiceChat.InviteLink.Speaker" = "Speaker";
|
||||||
|
"VoiceChat.InviteLink.Listener" = "Listener";
|
||||||
|
"VoiceChat.InviteLink.CopySpeakerLink" = "Copy Speaker Link";
|
||||||
|
"VoiceChat.InviteLink.CopyListenerLink" = "Copy Listener Link";
|
||||||
|
|
||||||
"VoiceChat.InviteLink.InviteSpeakers_0" = "[%@] Invite Speakers";
|
"VoiceChat.InviteLink.InviteSpeakers_0" = "[%@] Invite Speakers";
|
||||||
"VoiceChat.InviteLink.InviteSpeakers_1" = "[%@] Invite Speaker";
|
"VoiceChat.InviteLink.InviteSpeakers_1" = "[%@] Invite Speaker";
|
||||||
@ -6203,3 +6217,5 @@ Sorry for the inconvenience.";
|
|||||||
"VoiceChat.InviteLink.InviteListeners_3_10" = "[%@] Invite Listeners";
|
"VoiceChat.InviteLink.InviteListeners_3_10" = "[%@] Invite Listeners";
|
||||||
"VoiceChat.InviteLink.InviteListeners_many" = "[%@] Invite Listeners";
|
"VoiceChat.InviteLink.InviteListeners_many" = "[%@] Invite Listeners";
|
||||||
"VoiceChat.InviteLink.InviteListeners_any" = "[%@] Invite Listeners";
|
"VoiceChat.InviteLink.InviteListeners_any" = "[%@] Invite Listeners";
|
||||||
|
|
||||||
|
"Conversation.JoinVoiceChat" = "JOIN VOICE CHAT";
|
||||||
|
@ -64,6 +64,33 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize) -> Signal<UIImage?, NoError> {
|
||||||
|
let iconSignal: Signal<UIImage?, NoError>
|
||||||
|
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, inset: 0.0, emptyColor: nil, synchronousLoad: false) {
|
||||||
|
iconSignal = signal
|
||||||
|
|> map { imageVersions -> UIImage? in
|
||||||
|
return imageVersions?.0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let peerId = peer.id
|
||||||
|
var displayLetters = peer.displayLetters
|
||||||
|
if displayLetters.count == 2 && displayLetters[0].isSingleEmoji && displayLetters[1].isSingleEmoji {
|
||||||
|
displayLetters = [displayLetters[0]]
|
||||||
|
}
|
||||||
|
iconSignal = Signal { subscriber in
|
||||||
|
let image = generateImage(size, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), font: avatarPlaceholderFont(size: 13.0), letters: displayLetters, peerId: peerId)
|
||||||
|
})?.withRenderingMode(.alwaysOriginal)
|
||||||
|
|
||||||
|
subscriber.putNext(image)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iconSignal
|
||||||
|
}
|
||||||
|
|
||||||
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
|
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
|
||||||
if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
|
if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
|
||||||
return imageData
|
return imageData
|
||||||
|
@ -68,6 +68,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
|||||||
titleFont = customFont
|
titleFont = customFont
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
|
||||||
|
|
||||||
self.textNode.attributedText = NSAttributedString(string: action.text, font: titleFont, textColor: textColor)
|
self.textNode.attributedText = NSAttributedString(string: action.text, font: titleFont, textColor: textColor)
|
||||||
|
|
||||||
switch action.textLayout {
|
switch action.textLayout {
|
||||||
@ -83,7 +85,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
|||||||
statusNode.isAccessibilityElement = false
|
statusNode.isAccessibilityElement = false
|
||||||
statusNode.isUserInteractionEnabled = false
|
statusNode.isUserInteractionEnabled = false
|
||||||
statusNode.displaysAsynchronously = false
|
statusNode.displaysAsynchronously = false
|
||||||
statusNode.attributedText = NSAttributedString(string: value, font: textFont, textColor: presentationData.theme.contextMenu.secondaryColor)
|
statusNode.attributedText = NSAttributedString(string: value, font: subtitleFont, textColor: presentationData.theme.contextMenu.secondaryColor)
|
||||||
statusNode.maximumNumberOfLines = 1
|
statusNode.maximumNumberOfLines = 1
|
||||||
self.statusNode = statusNode
|
self.statusNode = statusNode
|
||||||
}
|
}
|
||||||
@ -281,7 +283,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
|||||||
|
|
||||||
switch self.action.textLayout {
|
switch self.action.textLayout {
|
||||||
case let .secondLineWithValue(value):
|
case let .secondLineWithValue(value):
|
||||||
self.statusNode?.attributedText = NSAttributedString(string: value, font: textFont, textColor: presentationData.theme.contextMenu.secondaryColor)
|
let subtitleFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
|
||||||
|
self.statusNode?.attributedText = NSAttributedString(string: value, font: subtitleFont, textColor: presentationData.theme.contextMenu.secondaryColor)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,13 @@ public struct ShareControllerSegmentedValue {
|
|||||||
let subject: ShareControllerSubject
|
let subject: ShareControllerSubject
|
||||||
let actionTitle: String
|
let actionTitle: String
|
||||||
let formatSendTitle: (Int) -> String
|
let formatSendTitle: (Int) -> String
|
||||||
|
|
||||||
|
public init(title: String, subject: ShareControllerSubject, actionTitle: String, formatSendTitle: @escaping (Int) -> String) {
|
||||||
|
self.title = title
|
||||||
|
self.subject = subject
|
||||||
|
self.actionTitle = actionTitle
|
||||||
|
self.formatSendTitle = formatSendTitle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ShareControllerSubject {
|
public enum ShareControllerSubject {
|
||||||
|
@ -40,6 +40,7 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
|||||||
case headphones
|
case headphones
|
||||||
case accept
|
case accept
|
||||||
case end
|
case end
|
||||||
|
case cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
var appearance: Appearance
|
var appearance: Appearance
|
||||||
@ -228,6 +229,8 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode {
|
|||||||
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAcceptButton"), color: imageColor)
|
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAcceptButton"), color: imageColor)
|
||||||
case .end:
|
case .end:
|
||||||
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallDeclineButton"), color: imageColor)
|
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallDeclineButton"), color: imageColor)
|
||||||
|
case .cancel:
|
||||||
|
image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallCancelButton"), color: imageColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let image = image {
|
if let image = image {
|
||||||
|
@ -5,7 +5,7 @@ import Display
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
|
|
||||||
private let titleFont = Font.regular(17.0)
|
private let titleFont = Font.regular(15.0)
|
||||||
private let subtitleFont = Font.regular(13.0)
|
private let subtitleFont = Font.regular(13.0)
|
||||||
|
|
||||||
private let white = UIColor(rgb: 0xffffff)
|
private let white = UIColor(rgb: 0xffffff)
|
||||||
@ -18,8 +18,8 @@ private let activeBlue = UIColor(rgb: 0x00a0b9)
|
|||||||
private let purple = UIColor(rgb: 0x6b81f0)
|
private let purple = UIColor(rgb: 0x6b81f0)
|
||||||
private let pink = UIColor(rgb: 0xd75a76)
|
private let pink = UIColor(rgb: 0xd75a76)
|
||||||
|
|
||||||
private let areaSize = CGSize(width: 440.0, height: 440.0)
|
private let areaSize = CGSize(width: 300.0, height: 300.0)
|
||||||
private let blobSize = CGSize(width: 244.0, height: 244.0)
|
private let blobSize = CGSize(width: 190.0, height: 190.0)
|
||||||
|
|
||||||
final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
||||||
enum State: Equatable {
|
enum State: Equatable {
|
||||||
@ -188,7 +188,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
let subtitleSize = self.subtitleLabel.updateLayout(CGSize(width: size.width, height: .greatestFiniteMagnitude))
|
||||||
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
let totalHeight = titleSize.height + subtitleSize.height + 1.0
|
||||||
|
|
||||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height - totalHeight / 2.0) - 112.0), size: titleSize)
|
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: floor(size.height - totalHeight / 2.0) - 71.0), size: titleSize)
|
||||||
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
self.subtitleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: self.titleLabel.frame.maxY + 1.0), size: subtitleSize)
|
||||||
|
|
||||||
self.bottomNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.bottomNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
@ -210,11 +210,10 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if snap {
|
if snap {
|
||||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
||||||
transition.updateTransformScale(node: self.backgroundNode, scale: active ? 0.75 : 0.5)
|
transition.updateTransformScale(node: self.backgroundNode, scale: active ? 0.9 : 0.625)
|
||||||
transition.updateTransformScale(node: self.iconNode, scale: 0.5)
|
transition.updateTransformScale(node: self.iconNode, scale: 0.625)
|
||||||
transition.updateAlpha(node: self.titleLabel, alpha: 0.0)
|
transition.updateAlpha(node: self.titleLabel, alpha: 0.0)
|
||||||
transition.updateAlpha(node: self.subtitleLabel, alpha: 0.0)
|
transition.updateAlpha(node: self.subtitleLabel, alpha: 0.0)
|
||||||
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 0.0)
|
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 0.0)
|
||||||
@ -227,7 +226,7 @@ final class VoiceChatActionButton: HighlightTrackingButtonNode {
|
|||||||
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 1.0)
|
transition.updateAlpha(layer: self.backgroundNode.maskProgressLayer, alpha: 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
let iconSize = CGSize(width: 90.0, height: 90.0)
|
let iconSize = CGSize(width: 68.0, height: 68.0)
|
||||||
self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconSize)
|
self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconSize)
|
||||||
self.iconNode.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
self.iconNode.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
}
|
}
|
||||||
@ -406,7 +405,7 @@ extension UIBezierPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private let progressLineWidth: CGFloat = 3.0 + UIScreenPixel
|
private let progressLineWidth: CGFloat = 3.0 + UIScreenPixel
|
||||||
private let buttonSize = CGSize(width: 144.0, height: 144.0)
|
private let buttonSize = CGSize(width: 112.0, height: 112.0)
|
||||||
private let radius = buttonSize.width / 2.0
|
private let radius = buttonSize.width / 2.0
|
||||||
|
|
||||||
private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
private final class VoiceChatActionButtonBackgroundNode: ASDisplayNode {
|
||||||
|
@ -22,11 +22,14 @@ import AlertUI
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import DirectionalPanGesture
|
import DirectionalPanGesture
|
||||||
import PeerInfoUI
|
import PeerInfoUI
|
||||||
|
import AvatarNode
|
||||||
|
|
||||||
private let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
|
private let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||||
private let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
private let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
||||||
private let fullscreenBackgroundColor = UIColor(rgb: 0x000000)
|
private let fullscreenBackgroundColor = UIColor(rgb: 0x000000)
|
||||||
private let dimColor = UIColor(white: 0.0, alpha: 0.5)
|
private let dimColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
|
private let sideButtonSize = CGSize(width: 56.0, height: 56.0)
|
||||||
|
private let bottomAreaHeight: CGFloat = 175.0
|
||||||
|
|
||||||
private func cornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
|
private func cornersImage(top: Bool, bottom: Bool, dark: Bool) -> UIImage? {
|
||||||
if !top && !bottom {
|
if !top && !bottom {
|
||||||
@ -1114,8 +1117,17 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_OpenChat, icon: { theme in
|
let openTitle: String
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor)
|
let openIcon: UIImage?
|
||||||
|
if peer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||||
|
openTitle = strongSelf.presentationData.strings.VoiceChat_OpenChannel
|
||||||
|
openIcon = UIImage(bundleImageName: "Chat/Context Menu/Channels")
|
||||||
|
} else {
|
||||||
|
openTitle = strongSelf.presentationData.strings.VoiceChat_OpenChat
|
||||||
|
openIcon = UIImage(bundleImageName: "Chat/Context Menu/Message")
|
||||||
|
}
|
||||||
|
items.append(.action(ContextMenuActionItem(text: openTitle, icon: { theme in
|
||||||
|
return generateTintedImage(image: openIcon, color: theme.actionSheet.primaryTextColor)
|
||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
guard let strongSelf = self, let navigationController = strongSelf.controller?.parentNavigationController else {
|
guard let strongSelf = self, let navigationController = strongSelf.controller?.parentNavigationController else {
|
||||||
return
|
return
|
||||||
@ -1384,13 +1396,38 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
self.cameraButtonNode.addTarget(self, action: #selector(self.cameraPressed), forControlEvents: .touchUpInside)
|
self.cameraButtonNode.addTarget(self, action: #selector(self.cameraPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
|
let avatarSize = CGSize(width: 28.0, height: 28.0)
|
||||||
self.optionsButton.contextAction = { [weak self] sourceNode, gesture in
|
self.optionsButton.contextAction = { [weak self] sourceNode, gesture in
|
||||||
guard let strongSelf = self, let controller = strongSelf.controller else {
|
guard let strongSelf = self, let controller = strongSelf.controller else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var items: [ContextMenuItem] = []
|
var mainItemsImpl: (() -> Signal<[ContextMenuItem], NoError>)?
|
||||||
|
|
||||||
|
let displayAsItems: () -> Signal<[ContextMenuItem], NoError> = {
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
items.append(.custom(VoiceChatInfoContextItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAsInfo, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}), true))
|
||||||
|
|
||||||
|
if let accountPeer = strongSelf.accountPeer {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: accountPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: .secondLineWithValue(strongSelf.presentationData.strings.VoiceChat_PersonalAccount), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: accountPeer, size: avatarSize)), action: { _, f in
|
||||||
|
f(.default)
|
||||||
|
})))
|
||||||
|
items.append(.separator)
|
||||||
|
}
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { (c, _) in
|
||||||
|
if let mainItems = mainItemsImpl {
|
||||||
|
c.setItems(mainItems())
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
return .single(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
let permissionItems: () -> Signal<[ContextMenuItem], NoError> = {
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
if let callState = strongSelf.callState, callState.canManageCall, let defaultParticipantMuteState = callState.defaultParticipantMuteState {
|
if let callState = strongSelf.callState, callState.canManageCall, let defaultParticipantMuteState = callState.defaultParticipantMuteState {
|
||||||
let isMuted = defaultParticipantMuteState == .muted
|
let isMuted = defaultParticipantMuteState == .muted
|
||||||
|
|
||||||
@ -1422,12 +1459,98 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
strongSelf.call.updateDefaultParticipantsAreMuted(isMuted: true)
|
strongSelf.call.updateDefaultParticipantsAreMuted(isMuted: true)
|
||||||
})))
|
})))
|
||||||
|
items.append(.separator)
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { (c, _) in
|
||||||
|
if let mainItems = mainItemsImpl {
|
||||||
|
c.setItems(mainItems())
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
return .single(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !items.isEmpty {
|
mainItemsImpl = {
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
|
if let accountPeer = strongSelf.accountPeer {
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAs, textLayout: .secondLineWithValue(accountPeer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: strongSelf.context.account, peer: accountPeer, size: avatarSize)), action: { c, _ in
|
||||||
|
c.setItems(displayAsItems())
|
||||||
|
})))
|
||||||
items.append(.separator)
|
items.append(.separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EditTitle, icon: { theme -> UIImage? in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { [weak self] _, f in
|
||||||
|
f(.default)
|
||||||
|
|
||||||
|
let controller = voiceChatTitleEditController(sharedContext: context.sharedContext, account: context.account, forceTheme: self?.darkTheme, title: nil, apply: { title in
|
||||||
|
|
||||||
|
})
|
||||||
|
self?.controller?.present(controller, in: .window(.root))
|
||||||
|
})))
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EditPermissions, icon: { theme -> UIImage? in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Restrict"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { c, _ in
|
||||||
|
c.setItems(permissionItems())
|
||||||
|
})))
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_Share, icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { [weak self] _, f in
|
||||||
|
f(.default)
|
||||||
|
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (strongSelf.context.account.postbox.transaction { transaction -> String? in
|
||||||
|
if let peer = transaction.getPeer(call.peerId), let addressName = peer.addressName, !addressName.isEmpty {
|
||||||
|
return "https://t.me/\(addressName)"
|
||||||
|
} else if let cachedData = transaction.getPeerCachedData(peerId: call.peerId) {
|
||||||
|
if let cachedData = cachedData as? CachedChannelData {
|
||||||
|
return cachedData.exportedInvitation?.link
|
||||||
|
} else if let cachedData = cachedData as? CachedGroupData {
|
||||||
|
return cachedData.exportedInvitation?.link
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} |> deliverOnMainQueue).start(next: { link in
|
||||||
|
if let link = link {
|
||||||
|
if let strongSelf = self {
|
||||||
|
let formatSendTitle: (String) -> String = { string in
|
||||||
|
var string = string
|
||||||
|
if string.contains("[") && string.contains("]") {
|
||||||
|
if let startIndex = string.firstIndex(of: "["), let endIndex = string.firstIndex(of: "]") {
|
||||||
|
string.removeSubrange(startIndex ... endIndex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
string = string.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,."))
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
let segmentedValues = [ShareControllerSegmentedValue(title: strongSelf.presentationData.strings.VoiceChat_InviteLink_Speaker, subject: .url(link), actionTitle: strongSelf.presentationData.strings.VoiceChat_InviteLink_CopySpeakerLink, formatSendTitle: { count in
|
||||||
|
return formatSendTitle(strongSelf.presentationData.strings.VoiceChat_InviteLink_InviteSpeakers(Int32(count)))
|
||||||
|
}), ShareControllerSegmentedValue(title: strongSelf.presentationData.strings.VoiceChat_InviteLink_Listener, subject: .url(link), actionTitle: strongSelf.presentationData.strings.VoiceChat_InviteLink_CopyListenerLink, formatSendTitle: { count in
|
||||||
|
return formatSendTitle(strongSelf.presentationData.strings.VoiceChat_InviteLink_InviteListeners(Int32(count)))
|
||||||
|
})]
|
||||||
|
let shareController = ShareController(context: strongSelf.context, subject: .url(link), segmentedValues: segmentedValues, forcedTheme: strongSelf.darkTheme, forcedActionTitle: strongSelf.presentationData.strings.VoiceChat_CopyInviteLink)
|
||||||
|
strongSelf.controller?.present(shareController, in: .window(.root))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_StartRecording, icon: { theme -> UIImage? in
|
||||||
|
return generateStartRecordingIcon(color: theme.actionSheet.primaryTextColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
})))
|
||||||
|
|
||||||
if let callState = strongSelf.callState, callState.canManageCall {
|
if let callState = strongSelf.callState, callState.canManageCall {
|
||||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EndVoiceChat, textColor: .destructive, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Clear"), color: theme.actionSheet.destructiveActionTextColor)
|
||||||
@ -1457,19 +1580,11 @@ public final class VoiceChatController: ViewController {
|
|||||||
strongSelf.controller?.present(alert, in: .window(.root))
|
strongSelf.controller?.present(alert, in: .window(.root))
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
return .single(items)
|
||||||
if items.isEmpty {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let optionsButton: VoiceChatHeaderButton
|
let optionsButton: VoiceChatHeaderButton = !strongSelf.recButton.isHidden ? strongSelf.recButton : strongSelf.optionsButton
|
||||||
if !strongSelf.recButton.isHidden {
|
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: optionsButton.extractedContainerNode, keepInPlace: false, blurBackground: false)), items: mainItemsImpl?() ?? .single([]), reactionItems: [], gesture: gesture)
|
||||||
optionsButton = strongSelf.recButton
|
|
||||||
} else {
|
|
||||||
optionsButton = strongSelf.optionsButton
|
|
||||||
}
|
|
||||||
|
|
||||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: optionsButton.extractedContainerNode, keepInPlace: false, blurBackground: false)), items: .single(items), reactionItems: [], gesture: gesture)
|
|
||||||
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
strongSelf.controller?.presentInGlobalOverlay(contextController)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1841,7 +1956,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||||
let topPanelHeight: CGFloat = 63.0
|
let topPanelHeight: CGFloat = 63.0
|
||||||
let listTopInset = layoutTopInset + topPanelHeight
|
let listTopInset = layoutTopInset + topPanelHeight
|
||||||
let bottomAreaHeight: CGFloat = 268.0
|
|
||||||
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom
|
||||||
|
|
||||||
var size = layout.size
|
var size = layout.size
|
||||||
@ -2088,14 +2202,13 @@ public final class VoiceChatController: ViewController {
|
|||||||
soundTitle = self.presentationData.strings.Call_Audio
|
soundTitle = self.presentationData.strings.Call_Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
let sideButtonSize = CGSize(width: 60.0, height: 60.0)
|
|
||||||
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: soundTitle, transition: .animated(duration: 0.3, curve: .linear))
|
self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: soundTitle, transition: .animated(duration: 0.3, curve: .linear))
|
||||||
|
|
||||||
let cameraButtonSize = CGSize(width: 40.0, height: 40.0)
|
let cameraButtonSize = CGSize(width: 40.0, height: 40.0)
|
||||||
|
|
||||||
self.cameraButtonNode.update(size: cameraButtonSize, content: CallControllerButtonItemNode.Content(appearance: CallControllerButtonItemNode.Content.Appearance.blurred(isFilled: false), image: .camera), text: " ", transition: .animated(duration: 0.3, curve: .linear))
|
self.cameraButtonNode.update(size: cameraButtonSize, content: CallControllerButtonItemNode.Content(appearance: CallControllerButtonItemNode.Content.Appearance.blurred(isFilled: false), image: .camera), text: " ", transition: .animated(duration: 0.3, curve: .linear))
|
||||||
|
|
||||||
self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .end), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
|
self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.custom(0xff3b30, 0.3)), image: .cancel), text: self.presentationData.strings.VoiceChat_Leave, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
@ -2117,7 +2230,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
transition.updateFrame(node: self.contentContainer, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: 0.0), size: size))
|
transition.updateFrame(node: self.contentContainer, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: 0.0), size: size))
|
||||||
|
|
||||||
let bottomAreaHeight: CGFloat = 268.0
|
|
||||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
@ -2177,9 +2289,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: size.width, height: bottomPanelHeight))
|
let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomPanelHeight), size: CGSize(width: size.width, height: bottomPanelHeight))
|
||||||
transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame)
|
transition.updateFrame(node: self.bottomPanelNode, frame: bottomPanelFrame)
|
||||||
|
|
||||||
let sideButtonSize = CGSize(width: 60.0, height: 60.0)
|
|
||||||
let cameraButtonSize = CGSize(width: 40.0, height: 40.0)
|
let cameraButtonSize = CGSize(width: 40.0, height: 40.0)
|
||||||
let centralButtonSize = CGSize(width: 440.0, height: 440.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 actionButtonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - centralButtonSize.width) / 2.0), y: floorToScreenPixels((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize)
|
||||||
|
|
||||||
@ -2200,7 +2311,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
actionButtonState = .active(state: .muted)
|
actionButtonState = .active(state: .muted)
|
||||||
|
|
||||||
actionButtonTitle = self.presentationData.strings.VoiceChat_Unmute
|
actionButtonTitle = self.presentationData.strings.VoiceChat_Unmute
|
||||||
actionButtonSubtitle = self.presentationData.strings.VoiceChat_UnmuteHelp
|
actionButtonSubtitle = ""
|
||||||
} else {
|
} else {
|
||||||
actionButtonState = .active(state: .cantSpeak)
|
actionButtonState = .active(state: .cantSpeak)
|
||||||
|
|
||||||
@ -2222,7 +2333,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.actionButton.isDisabled = !actionButtonEnabled
|
self.actionButton.isDisabled = !actionButtonEnabled
|
||||||
self.actionButton.update(size: centralButtonSize, buttonSize: CGSize(width: 144.0, height: 144.0), state: actionButtonState, title: actionButtonTitle, subtitle: actionButtonSubtitle, dark: self.isFullscreen, small: size.width < 330.0, 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: false, animated: true)
|
||||||
|
|
||||||
if self.actionButton.supernode === self.bottomPanelNode {
|
if self.actionButton.supernode === self.bottomPanelNode {
|
||||||
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
transition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
|
||||||
@ -2248,8 +2359,8 @@ public final class VoiceChatController: ViewController {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
let sideButtonMinimalInset: CGFloat = 16.0
|
let sideButtonMinimalInset: CGFloat = 16.0
|
||||||
let sideButtonOffset = min(36.0, floor((((size.width - 144.0) / 2.0) - sideButtonSize.width) / 2.0))
|
let sideButtonOffset = min(42.0, floor((((size.width - 112.0) / 2.0) - sideButtonSize.width) / 2.0))
|
||||||
let sideButtonOrigin = max(sideButtonMinimalInset, floor((size.width - 144.0) / 2.0) - sideButtonOffset - sideButtonSize.width)
|
let sideButtonOrigin = max(sideButtonMinimalInset, floor((size.width - 112.0) / 2.0) - sideButtonOffset - sideButtonSize.width)
|
||||||
|
|
||||||
if self.audioOutputNode.supernode === self.bottomPanelNode {
|
if self.audioOutputNode.supernode === self.bottomPanelNode {
|
||||||
if true {
|
if true {
|
||||||
@ -2378,7 +2489,6 @@ public final class VoiceChatController: ViewController {
|
|||||||
}
|
}
|
||||||
itemsHeight += CGFloat(itemsCount) * 56.0
|
itemsHeight += CGFloat(itemsCount) * 56.0
|
||||||
|
|
||||||
let bottomAreaHeight: CGFloat = 268.0
|
|
||||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||||
|
|
||||||
let sideInset: CGFloat = 16.0
|
let sideInset: CGFloat = 16.0
|
||||||
|
@ -0,0 +1,100 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AppBundle
|
||||||
|
import ContextUI
|
||||||
|
import TelegramStringFormatting
|
||||||
|
|
||||||
|
final class VoiceChatInfoContextItem: ContextMenuCustomItem {
|
||||||
|
let text: String
|
||||||
|
let icon: (PresentationTheme) -> UIImage?
|
||||||
|
|
||||||
|
init(text: String, icon: @escaping (PresentationTheme) -> UIImage?) {
|
||||||
|
self.text = text
|
||||||
|
self.icon = icon
|
||||||
|
}
|
||||||
|
|
||||||
|
func node(presentationData: PresentationData, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||||
|
return VoiceChatInfoContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class VoiceChatInfoContextItemNode: ASDisplayNode, ContextMenuCustomNode {
|
||||||
|
private let item: VoiceChatInfoContextItem
|
||||||
|
private let presentationData: PresentationData
|
||||||
|
private let getController: () -> ContextController?
|
||||||
|
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||||
|
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
|
private let textNode: ImmediateTextNode
|
||||||
|
private let iconNode: ASImageNode
|
||||||
|
|
||||||
|
init(presentationData: PresentationData, item: VoiceChatInfoContextItem, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||||
|
self.item = item
|
||||||
|
self.presentationData = presentationData
|
||||||
|
self.getController = getController
|
||||||
|
self.actionSelected = actionSelected
|
||||||
|
|
||||||
|
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
|
||||||
|
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.isAccessibilityElement = false
|
||||||
|
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||||
|
|
||||||
|
self.textNode = ImmediateTextNode()
|
||||||
|
self.textNode.isAccessibilityElement = false
|
||||||
|
self.textNode.isUserInteractionEnabled = false
|
||||||
|
self.textNode.displaysAsynchronously = false
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: item.text, font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||||
|
self.textNode.maximumNumberOfLines = 0
|
||||||
|
|
||||||
|
self.iconNode = ASImageNode()
|
||||||
|
self.iconNode.displaysAsynchronously = false
|
||||||
|
self.iconNode.displayWithoutProcessing = true
|
||||||
|
self.iconNode.image = item.icon(presentationData.theme)
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
self.addSubnode(self.textNode)
|
||||||
|
self.addSubnode(self.iconNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(constrainedWidth: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||||
|
let sideInset: CGFloat = 16.0
|
||||||
|
let iconSideInset: CGFloat = 12.0
|
||||||
|
let verticalInset: CGFloat = 12.0
|
||||||
|
|
||||||
|
let iconSize = self.iconNode.image.flatMap({ $0.size }) ?? CGSize()
|
||||||
|
|
||||||
|
let standardIconWidth: CGFloat = 32.0
|
||||||
|
var rightTextInset: CGFloat = sideInset
|
||||||
|
if !iconSize.width.isZero {
|
||||||
|
rightTextInset = max(iconSize.width, standardIconWidth) + iconSideInset + sideInset - 8.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - rightTextInset, height: .greatestFiniteMagnitude))
|
||||||
|
|
||||||
|
return (CGSize(width: textSize.width + sideInset + rightTextInset, height: verticalInset * 2.0 + textSize.height), { size, transition in
|
||||||
|
let verticalOrigin = floor((size.height - textSize.height) / 2.0)
|
||||||
|
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)
|
||||||
|
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||||
|
|
||||||
|
if !iconSize.width.isZero {
|
||||||
|
transition.updateFrameAdditive(node: self.iconNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTheme(presentationData: PresentationData) {
|
||||||
|
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
|
||||||
|
|
||||||
|
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
|
||||||
|
}
|
||||||
|
}
|
@ -161,7 +161,7 @@ final class VoiceChatMicrophoneNode: ASDisplayNode {
|
|||||||
var clearLineWidth: CGFloat = 4.0
|
var clearLineWidth: CGFloat = 4.0
|
||||||
var lineWidth: CGFloat = 1.0 + UIScreenPixel
|
var lineWidth: CGFloat = 1.0 + UIScreenPixel
|
||||||
if bounds.size.width > 36.0 {
|
if bounds.size.width > 36.0 {
|
||||||
context.scaleBy(x: 2.5, y: 2.5)
|
context.scaleBy(x: 2.0, y: 2.0)
|
||||||
} else if bounds.size.width < 30.0 {
|
} else if bounds.size.width < 30.0 {
|
||||||
clearLineWidth = 3.0
|
clearLineWidth = 3.0
|
||||||
lineWidth = 1.0
|
lineWidth = 1.0
|
||||||
@ -171,7 +171,7 @@ final class VoiceChatMicrophoneNode: ASDisplayNode {
|
|||||||
context.translateBy(x: 4.0, y: 3.0)
|
context.translateBy(x: 4.0, y: 3.0)
|
||||||
let _ = try? drawSvgPath(context, path: "M14,8.335 C14.36727,8.335 14.665,8.632731 14.665,9 C14.665,11.903515 12.48064,14.296846 9.665603,14.626311 L9.665,16 C9.665,16.367269 9.367269,16.665 9,16.665 C8.666119,16.665 8.389708,16.418942 8.34221,16.098269 L8.335,16 L8.3354,14.626428 C5.519879,14.297415 3.335,11.90386 3.335,9 C3.335,8.632731 3.632731,8.335 4,8.335 C4.367269,8.335 4.665,8.632731 4.665,9 C4.665,11.394154 6.605846,13.335 9,13.335 C11.39415,13.335 13.335,11.394154 13.335,9 C13.335,8.632731 13.63273,8.335 14,8.335 Z ")
|
let _ = try? drawSvgPath(context, path: "M14,8.335 C14.36727,8.335 14.665,8.632731 14.665,9 C14.665,11.903515 12.48064,14.296846 9.665603,14.626311 L9.665,16 C9.665,16.367269 9.367269,16.665 9,16.665 C8.666119,16.665 8.389708,16.418942 8.34221,16.098269 L8.335,16 L8.3354,14.626428 C5.519879,14.297415 3.335,11.90386 3.335,9 C3.335,8.632731 3.632731,8.335 4,8.335 C4.367269,8.335 4.665,8.632731 4.665,9 C4.665,11.394154 6.605846,13.335 9,13.335 C11.39415,13.335 13.335,11.394154 13.335,9 C13.335,8.632731 13.63273,8.335 14,8.335 Z ")
|
||||||
} else {
|
} else {
|
||||||
context.translateBy(x: 18.0, y: 18.0)
|
context.translateBy(x: 17.0, y: 18.0)
|
||||||
let _ = try? drawSvgPath(context, path: "M-0.004000000189989805,-9.86400032043457 C2.2960000038146973,-9.86400032043457 4.165999889373779,-8.053999900817871 4.25600004196167,-5.77400016784668 C4.25600004196167,-5.77400016784668 4.265999794006348,-5.604000091552734 4.265999794006348,-5.604000091552734 C4.265999794006348,-5.604000091552734 4.265999794006348,-0.8040000200271606 4.265999794006348,-0.8040000200271606 C4.265999794006348,1.555999994277954 2.3559999465942383,3.4660000801086426 -0.004000000189989805,3.4660000801086426 C-2.2939999103546143,3.4660000801086426 -4.164000034332275,1.6460000276565552 -4.263999938964844,-0.6240000128746033 C-4.263999938964844,-0.6240000128746033 -4.263999938964844,-0.8040000200271606 -4.263999938964844,-0.8040000200271606 C-4.263999938964844,-0.8040000200271606 -4.263999938964844,-5.604000091552734 -4.263999938964844,-5.604000091552734 C-4.263999938964844,-7.953999996185303 -2.3540000915527344,-9.86400032043457 -0.004000000189989805,-9.86400032043457 Z ")
|
let _ = try? drawSvgPath(context, path: "M-0.004000000189989805,-9.86400032043457 C2.2960000038146973,-9.86400032043457 4.165999889373779,-8.053999900817871 4.25600004196167,-5.77400016784668 C4.25600004196167,-5.77400016784668 4.265999794006348,-5.604000091552734 4.265999794006348,-5.604000091552734 C4.265999794006348,-5.604000091552734 4.265999794006348,-0.8040000200271606 4.265999794006348,-0.8040000200271606 C4.265999794006348,1.555999994277954 2.3559999465942383,3.4660000801086426 -0.004000000189989805,3.4660000801086426 C-2.2939999103546143,3.4660000801086426 -4.164000034332275,1.6460000276565552 -4.263999938964844,-0.6240000128746033 C-4.263999938964844,-0.6240000128746033 -4.263999938964844,-0.8040000200271606 -4.263999938964844,-0.8040000200271606 C-4.263999938964844,-0.8040000200271606 -4.263999938964844,-5.604000091552734 -4.263999938964844,-5.604000091552734 C-4.263999938964844,-7.953999996185303 -2.3540000915527344,-9.86400032043457 -0.004000000189989805,-9.86400032043457 Z ")
|
||||||
}
|
}
|
||||||
if bounds.width > 30.0 && !parameters.filled {
|
if bounds.width > 30.0 && !parameters.filled {
|
||||||
|
@ -37,12 +37,9 @@ func closeButtonImage(dark: Bool) -> UIImage? {
|
|||||||
context.setLineCap(.round)
|
context.setLineCap(.round)
|
||||||
context.setStrokeColor(UIColor.white.cgColor)
|
context.setStrokeColor(UIColor.white.cgColor)
|
||||||
|
|
||||||
context.move(to: CGPoint(x: 9.0, y: 9.0))
|
context.move(to: CGPoint(x: 7.0 + UIScreenPixel, y: 16.0 + UIScreenPixel))
|
||||||
context.addLine(to: CGPoint(x: 19.0, y: 19.0))
|
context.addLine(to: CGPoint(x: 14.0, y: 10.0))
|
||||||
context.strokePath()
|
context.addLine(to: CGPoint(x: 21.0 - UIScreenPixel, y: 16.0 + UIScreenPixel))
|
||||||
|
|
||||||
context.move(to: CGPoint(x: 19.0, y: 9.0))
|
|
||||||
context.addLine(to: CGPoint(x: 9.0, y: 19.0))
|
|
||||||
context.strokePath()
|
context.strokePath()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -169,13 +169,13 @@ public final class VoiceChatOverlayController: ViewController {
|
|||||||
|
|
||||||
if reclaim {
|
if reclaim {
|
||||||
self.dismissed = true
|
self.dismissed = true
|
||||||
let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height - layout.intrinsicInsets.bottom - 268.0 / 2.0)
|
let targetPosition = CGPoint(x: layout.size.width / 2.0, y: layout.size.height - layout.intrinsicInsets.bottom - 175.0 / 2.0)
|
||||||
if self.isSlidOffscreen {
|
if self.isSlidOffscreen {
|
||||||
self.isSlidOffscreen = false
|
self.isSlidOffscreen = false
|
||||||
self.isButtonHidden = true
|
self.isButtonHidden = true
|
||||||
actionButton.layer.sublayerTransform = CATransform3DIdentity
|
actionButton.layer.sublayerTransform = CATransform3DIdentity
|
||||||
actionButton.update(snap: false, animated: false)
|
actionButton.update(snap: false, animated: false)
|
||||||
actionButton.position = CGPoint(x: targetPosition.x, y: 268.0 / 2.0)
|
actionButton.position = CGPoint(x: targetPosition.x, y: 175.0 / 2.0)
|
||||||
|
|
||||||
leftButton.isHidden = false
|
leftButton.isHidden = false
|
||||||
rightButton.isHidden = false
|
rightButton.isHidden = false
|
||||||
@ -191,7 +191,7 @@ public final class VoiceChatOverlayController: ViewController {
|
|||||||
actionButton.layer.removeAllAnimations()
|
actionButton.layer.removeAllAnimations()
|
||||||
actionButton.layer.sublayerTransform = CATransform3DIdentity
|
actionButton.layer.sublayerTransform = CATransform3DIdentity
|
||||||
actionButton.update(snap: false, animated: false)
|
actionButton.update(snap: false, animated: false)
|
||||||
actionButton.position = CGPoint(x: targetPosition.x, y: 268.0 / 2.0)
|
actionButton.position = CGPoint(x: targetPosition.x, y: 175.0 / 2.0)
|
||||||
|
|
||||||
leftButton.isHidden = false
|
leftButton.isHidden = false
|
||||||
rightButton.isHidden = false
|
rightButton.isHidden = false
|
||||||
|
@ -8,6 +8,18 @@ import AppBundle
|
|||||||
import ContextUI
|
import ContextUI
|
||||||
import TelegramStringFormatting
|
import TelegramStringFormatting
|
||||||
|
|
||||||
|
func generateStartRecordingIcon(color: UIColor) -> UIImage? {
|
||||||
|
return generateImage(CGSize(width: 18.0, height: 18.0), opaque: false, rotatedContext: { size, context in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.clear(bounds)
|
||||||
|
context.setLineWidth(1.0 + UIScreenPixel)
|
||||||
|
context.setStrokeColor(color.cgColor)
|
||||||
|
context.strokeEllipse(in: bounds.insetBy(dx: 1.0, dy: 1.0))
|
||||||
|
context.setFillColor(color.cgColor)
|
||||||
|
context.fillEllipse(in: bounds.insetBy(dx: 5.0, dy: 5.0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
final class VoiceChatRecordingContextItem: ContextMenuCustomItem {
|
final class VoiceChatRecordingContextItem: ContextMenuCustomItem {
|
||||||
fileprivate let timestamp: Double
|
fileprivate let timestamp: Double
|
||||||
fileprivate let action: (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void
|
fileprivate let action: (ContextController, @escaping (ContextMenuActionResult) -> Void) -> Void
|
||||||
|
@ -0,0 +1,428 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import SwiftSignalKit
|
||||||
|
import AsyncDisplayKit
|
||||||
|
import Display
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
import SyncCore
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AccountContext
|
||||||
|
import UrlEscaping
|
||||||
|
|
||||||
|
private final class VoiceChatTitleEditInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
|
||||||
|
private var theme: PresentationTheme
|
||||||
|
private let backgroundNode: ASImageNode
|
||||||
|
private let textInputNode: EditableTextNode
|
||||||
|
private let placeholderNode: ASTextNode
|
||||||
|
|
||||||
|
var updateHeight: (() -> Void)?
|
||||||
|
var complete: (() -> Void)?
|
||||||
|
var textChanged: ((String) -> Void)?
|
||||||
|
|
||||||
|
private let backgroundInsets = UIEdgeInsets(top: 8.0, left: 16.0, bottom: 15.0, right: 16.0)
|
||||||
|
private let inputInsets = UIEdgeInsets(top: 5.0, left: 12.0, bottom: 5.0, right: 12.0)
|
||||||
|
|
||||||
|
var text: String {
|
||||||
|
get {
|
||||||
|
return self.textInputNode.attributedText?.string ?? ""
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputTextColor)
|
||||||
|
self.placeholderNode.isHidden = !newValue.isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var placeholder: String = "" {
|
||||||
|
didSet {
|
||||||
|
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init(theme: PresentationTheme, placeholder: String) {
|
||||||
|
self.theme = theme
|
||||||
|
|
||||||
|
self.backgroundNode = ASImageNode()
|
||||||
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
self.backgroundNode.displaysAsynchronously = false
|
||||||
|
self.backgroundNode.displayWithoutProcessing = true
|
||||||
|
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||||
|
|
||||||
|
self.textInputNode = EditableTextNode()
|
||||||
|
self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor]
|
||||||
|
self.textInputNode.clipsToBounds = true
|
||||||
|
self.textInputNode.hitTestSlop = UIEdgeInsets(top: -5.0, left: -5.0, bottom: -5.0, right: -5.0)
|
||||||
|
self.textInputNode.textContainerInset = UIEdgeInsets(top: self.inputInsets.top, left: 0.0, bottom: self.inputInsets.bottom, right: 0.0)
|
||||||
|
self.textInputNode.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
|
||||||
|
self.textInputNode.keyboardType = .URL
|
||||||
|
self.textInputNode.autocapitalizationType = .none
|
||||||
|
self.textInputNode.returnKeyType = .done
|
||||||
|
self.textInputNode.autocorrectionType = .no
|
||||||
|
self.textInputNode.tintColor = theme.actionSheet.controlAccentColor
|
||||||
|
|
||||||
|
self.placeholderNode = ASTextNode()
|
||||||
|
self.placeholderNode.isUserInteractionEnabled = false
|
||||||
|
self.placeholderNode.displaysAsynchronously = false
|
||||||
|
self.placeholderNode.attributedText = NSAttributedString(string: placeholder, font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.textInputNode.delegate = self
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
|
self.addSubnode(self.textInputNode)
|
||||||
|
self.addSubnode(self.placeholderNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTheme(_ theme: PresentationTheme) {
|
||||||
|
self.theme = theme
|
||||||
|
|
||||||
|
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||||
|
self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
|
||||||
|
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||||
|
self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
|
let backgroundInsets = self.backgroundInsets
|
||||||
|
let inputInsets = self.inputInsets
|
||||||
|
|
||||||
|
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
|
||||||
|
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
||||||
|
|
||||||
|
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: width - backgroundInsets.left - backgroundInsets.right, height: panelHeight - backgroundInsets.top - backgroundInsets.bottom))
|
||||||
|
transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
|
||||||
|
|
||||||
|
let placeholderSize = self.placeholderNode.measure(backgroundFrame.size)
|
||||||
|
transition.updateFrame(node: self.placeholderNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY + floor((backgroundFrame.size.height - placeholderSize.height) / 2.0)), size: placeholderSize))
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right, height: backgroundFrame.size.height)))
|
||||||
|
|
||||||
|
return panelHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func activateInput() {
|
||||||
|
self.textInputNode.becomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
func deactivateInput() {
|
||||||
|
self.textInputNode.resignFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||||
|
self.updateTextNodeText(animated: true)
|
||||||
|
self.textChanged?(editableTextNode.textView.text)
|
||||||
|
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
func editableTextNode(_ editableTextNode: ASEditableTextNode, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||||
|
if text == "\n" {
|
||||||
|
self.complete?()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func calculateTextFieldMetrics(width: CGFloat) -> CGFloat {
|
||||||
|
let backgroundInsets = self.backgroundInsets
|
||||||
|
let inputInsets = self.inputInsets
|
||||||
|
|
||||||
|
let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right, height: CGFloat.greatestFiniteMagnitude)).height))
|
||||||
|
|
||||||
|
return min(61.0, max(33.0, unboundTextFieldHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateTextNodeText(animated: Bool) {
|
||||||
|
let backgroundInsets = self.backgroundInsets
|
||||||
|
|
||||||
|
let textFieldHeight = self.calculateTextFieldMetrics(width: self.bounds.size.width)
|
||||||
|
|
||||||
|
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
||||||
|
if !self.bounds.size.height.isEqual(to: panelHeight) {
|
||||||
|
self.updateHeight?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func clearPressed() {
|
||||||
|
self.textInputNode.attributedText = nil
|
||||||
|
self.deactivateInput()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class VoiceChatTitleEditAlertContentNode: AlertContentNode {
|
||||||
|
private let strings: PresentationStrings
|
||||||
|
|
||||||
|
private let titleNode: ASTextNode
|
||||||
|
private let textNode: ASTextNode
|
||||||
|
let inputFieldNode: 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.inputFieldNode.complete = self.complete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override var dismissOnOutsideTap: Bool {
|
||||||
|
return self.isUserInteractionEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
init(theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, actions: [TextAlertAction], title: String?) {
|
||||||
|
self.strings = strings
|
||||||
|
|
||||||
|
self.titleNode = ASTextNode()
|
||||||
|
self.titleNode.maximumNumberOfLines = 2
|
||||||
|
self.textNode = ASTextNode()
|
||||||
|
self.textNode.maximumNumberOfLines = 2
|
||||||
|
|
||||||
|
self.inputFieldNode = VoiceChatTitleEditInputFieldNode(theme: ptheme, placeholder: strings.VoiceChat_Title)
|
||||||
|
self.inputFieldNode.text = title ?? ""
|
||||||
|
|
||||||
|
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.textNode)
|
||||||
|
|
||||||
|
self.addSubnode(self.inputFieldNode)
|
||||||
|
|
||||||
|
self.addSubnode(self.actionNodesSeparator)
|
||||||
|
|
||||||
|
for actionNode in self.actionNodes {
|
||||||
|
self.addSubnode(actionNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
for separatorNode in self.actionVerticalSeparators {
|
||||||
|
self.addSubnode(separatorNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inputFieldNode.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 title: String {
|
||||||
|
return self.inputFieldNode.text
|
||||||
|
}
|
||||||
|
|
||||||
|
override func updateTheme(_ theme: AlertControllerTheme) {
|
||||||
|
self.titleNode.attributedText = NSAttributedString(string: self.strings.VoiceChat_EditTitleTitle, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||||
|
self.textNode.attributedText = NSAttributedString(string: self.strings.VoiceChat_EditTitleText, font: Font.regular(13.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 = 5.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 textSize = self.textNode.measure(measureSize)
|
||||||
|
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||||
|
origin.y += textSize.height + 6.0 + spacing
|
||||||
|
|
||||||
|
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 inputFieldHeight = self.inputFieldNode.updateLayout(width: inputFieldWidth, transition: transition)
|
||||||
|
let inputHeight = inputFieldHeight
|
||||||
|
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
|
||||||
|
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
|
||||||
|
|
||||||
|
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + 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.inputFieldNode.activateInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func animateError() {
|
||||||
|
self.inputFieldNode.layer.addShakeAnimation()
|
||||||
|
self.hapticFeedback.error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func voiceChatTitleEditController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String?, apply: @escaping (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)
|
||||||
|
apply(nil)
|
||||||
|
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: {
|
||||||
|
applyImpl?()
|
||||||
|
})]
|
||||||
|
|
||||||
|
let contentNode = VoiceChatTitleEditAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: presentationData.strings, actions: actions, title: title)
|
||||||
|
contentNode.complete = {
|
||||||
|
applyImpl?()
|
||||||
|
}
|
||||||
|
applyImpl = { [weak contentNode] in
|
||||||
|
guard let contentNode = contentNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dismissImpl?(true)
|
||||||
|
apply(contentNode.title)
|
||||||
|
}
|
||||||
|
|
||||||
|
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?.inputFieldNode.updateTheme(presentationData.theme)
|
||||||
|
})
|
||||||
|
controller.dismissed = {
|
||||||
|
presentationDataDisposable.dispose()
|
||||||
|
}
|
||||||
|
dismissImpl = { [weak controller] animated in
|
||||||
|
if animated {
|
||||||
|
controller?.dismissAnimated()
|
||||||
|
} else {
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return controller
|
||||||
|
}
|
@ -526,7 +526,7 @@ public func makeDefaultDarkPresentationTheme(extendingThemeReference: Presentati
|
|||||||
itemBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.0),
|
itemBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.0),
|
||||||
itemHighlightedBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.15),
|
itemHighlightedBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.15),
|
||||||
primaryColor: UIColor(rgb: 0xffffff, alpha: 1.0),
|
primaryColor: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||||
secondaryColor: UIColor(rgb: 0xffffff, alpha: 0.8),
|
secondaryColor: UIColor(rgb: 0xffffff, alpha: 0.48),
|
||||||
destructiveColor: UIColor(rgb: 0xeb5545),
|
destructiveColor: UIColor(rgb: 0xeb5545),
|
||||||
badgeFillColor: UIColor(rgb: 0xffffff),
|
badgeFillColor: UIColor(rgb: 0xffffff),
|
||||||
badgeForegroundColor: UIColor(rgb: 0x000000),
|
badgeForegroundColor: UIColor(rgb: 0x000000),
|
||||||
|
@ -781,7 +781,7 @@ public func makeDefaultDarkTintedPresentationTheme(extendingThemeReference: Pres
|
|||||||
itemBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.0),
|
itemBackgroundColor: UIColor(rgb: 0x000000, alpha: 0.0),
|
||||||
itemHighlightedBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.15),
|
itemHighlightedBackgroundColor: UIColor(rgb: 0xffffff, alpha: 0.15),
|
||||||
primaryColor: UIColor(rgb: 0xffffff, alpha: 1.0),
|
primaryColor: UIColor(rgb: 0xffffff, alpha: 1.0),
|
||||||
secondaryColor: UIColor(rgb: 0xffffff, alpha: 0.8),
|
secondaryColor: UIColor(rgb: 0xffffff, alpha: 0.5),
|
||||||
destructiveColor: UIColor(rgb: 0xff6767),
|
destructiveColor: UIColor(rgb: 0xff6767),
|
||||||
badgeFillColor: accentColor,
|
badgeFillColor: accentColor,
|
||||||
badgeForegroundColor: secondaryBadgeTextColor,
|
badgeForegroundColor: secondaryBadgeTextColor,
|
||||||
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -46,7 +46,7 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
|||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
self.backgroundNode.displaysAsynchronously = false
|
self.backgroundNode.displaysAsynchronously = false
|
||||||
self.backgroundNode.displayWithoutProcessing = true
|
self.backgroundNode.displayWithoutProcessing = true
|
||||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 33.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: theme.actionSheet.inputHollowBackgroundColor, strokeColor: theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||||
|
|
||||||
self.textInputNode = EditableTextNode()
|
self.textInputNode = EditableTextNode()
|
||||||
self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor]
|
self.textInputNode.typingAttributes = [NSAttributedString.Key.font.rawValue: Font.regular(17.0), NSAttributedString.Key.foregroundColor.rawValue: theme.actionSheet.inputTextColor]
|
||||||
@ -77,7 +77,7 @@ private final class ChatTextLinkEditInputFieldNode: ASDisplayNode, ASEditableTex
|
|||||||
func updateTheme(_ theme: PresentationTheme) {
|
func updateTheme(_ theme: PresentationTheme) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
|
||||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 33.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: self.theme.actionSheet.inputHollowBackgroundColor, strokeColor: self.theme.actionSheet.inputBorderColor, strokeWidth: 1.0)
|
||||||
self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
|
self.textInputNode.keyboardAppearance = self.theme.rootController.keyboardColor.keyboardAppearance
|
||||||
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
self.placeholderNode.attributedText = NSAttributedString(string: self.placeholderNode.attributedText?.string ?? "", font: Font.regular(17.0), textColor: self.theme.actionSheet.inputPlaceholderColor)
|
||||||
self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor
|
self.textInputNode.tintColor = self.theme.actionSheet.controlAccentColor
|
||||||
@ -276,6 +276,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
|||||||
self.validLayout = size
|
self.validLayout = size
|
||||||
|
|
||||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||||
|
let spacing: CGFloat = 5.0
|
||||||
|
|
||||||
let titleSize = self.titleNode.measure(measureSize)
|
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))
|
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||||
@ -283,7 +284,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
|||||||
|
|
||||||
let textSize = self.textNode.measure(measureSize)
|
let textSize = self.textNode.measure(measureSize)
|
||||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||||
origin.y += textSize.height + 6.0
|
origin.y += textSize.height + 6.0 + spacing
|
||||||
|
|
||||||
let actionButtonHeight: CGFloat = 44.0
|
let actionButtonHeight: CGFloat = 44.0
|
||||||
var minActionsWidth: CGFloat = 0.0
|
var minActionsWidth: CGFloat = 0.0
|
||||||
@ -304,7 +305,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 9.0, right: 18.0)
|
||||||
|
|
||||||
var contentWidth = max(titleSize.width, minActionsWidth)
|
var contentWidth = max(titleSize.width, minActionsWidth)
|
||||||
contentWidth = max(contentWidth, 234.0)
|
contentWidth = max(contentWidth, 234.0)
|
||||||
@ -325,7 +326,7 @@ private final class ChatTextLinkEditAlertContentNode: AlertContentNode {
|
|||||||
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
|
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(x: 0.0, y: origin.y, width: resultWidth, height: inputFieldHeight))
|
||||||
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
|
transition.updateAlpha(node: self.inputFieldNode, alpha: inputHeight > 0.0 ? 1.0 : 0.0)
|
||||||
|
|
||||||
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + actionsHeight + inputHeight + insets.top + insets.bottom)
|
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + spacing + inputHeight + 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)))
|
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||||
|
|
||||||
|
@ -6155,36 +6155,9 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountIconSignal(account: Account, peer: Peer, size: CGSize) -> Signal<UIImage?, NoError> {
|
|
||||||
let iconSignal: Signal<UIImage?, NoError>
|
|
||||||
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, inset: 0.0, emptyColor: nil, synchronousLoad: false) {
|
|
||||||
iconSignal = signal
|
|
||||||
|> map { imageVersions -> UIImage? in
|
|
||||||
return imageVersions?.0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let peerId = peer.id
|
|
||||||
var displayLetters = peer.displayLetters
|
|
||||||
if displayLetters.count == 2 && displayLetters[0].isSingleEmoji && displayLetters[1].isSingleEmoji {
|
|
||||||
displayLetters = [displayLetters[0]]
|
|
||||||
}
|
|
||||||
iconSignal = Signal { subscriber in
|
|
||||||
let image = generateImage(size, rotatedContext: { size, context in
|
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
||||||
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), font: avatarPlaceholderFont(size: 13.0), letters: displayLetters, peerId: peerId)
|
|
||||||
})?.withRenderingMode(.alwaysOriginal)
|
|
||||||
|
|
||||||
subscriber.putNext(image)
|
|
||||||
subscriber.putCompletion()
|
|
||||||
return EmptyDisposable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return iconSignal
|
|
||||||
}
|
|
||||||
|
|
||||||
let avatarSize = CGSize(width: 28.0, height: 28.0)
|
let avatarSize = CGSize(width: 28.0, height: 28.0)
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: primary.1.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: accountIconSignal(account: primary.0, peer: primary.1, size: avatarSize)), action: { _, f in
|
items.append(.action(ContextMenuActionItem(text: primary.1.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: primary.0, peer: primary.1, size: avatarSize)), action: { _, f in
|
||||||
f(.default)
|
f(.default)
|
||||||
})))
|
})))
|
||||||
|
|
||||||
@ -6194,7 +6167,7 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen {
|
|||||||
|
|
||||||
for account in other {
|
for account in other {
|
||||||
let id = account.0.id
|
let id = account.0.id
|
||||||
items.append(.action(ContextMenuActionItem(text: account.1.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), badge: account.2 != 0 ? ContextMenuActionBadge(value: "\(account.2)", color: .accent) : nil, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: accountIconSignal(account: account.0, peer: account.1, size: avatarSize)), action: { [weak self] _, f in
|
items.append(.action(ContextMenuActionItem(text: account.1.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), badge: account.2 != 0 ? ContextMenuActionBadge(value: "\(account.2)", color: .accent) : nil, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: peerAvatarCompleteImage(account: account.0, peer: account.1, size: avatarSize)), action: { [weak self] _, f in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user