chore: migrate to new version + fixed several critical bugs

- Migrated project to latest Telegram iOS base (v12.3.2+)
- Fixed circular dependency between GhostModeManager and MiscSettingsManager
- Fixed multiple Bazel build configuration errors (select() default conditions)
- Fixed duplicate type definitions in PeerInfoScreen
- Fixed swiftmodule directory resolution in build scripts
- Added Ghostgram Settings tab in main Settings menu with all 5 features
- Cleared sensitive credentials from config.json (template-only now)
- Excluded bazel-cache from version control
This commit is contained in:
ichmagmaus 812
2026-02-23 23:04:32 +01:00
parent 703e291bcb
commit db53826061
1017 changed files with 62337 additions and 40559 deletions
@@ -0,0 +1,34 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "LiveLocationHeaderPanelComponent",
module_name = "LiveLocationHeaderPanelComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/Display",
"//submodules/AsyncDisplayKit",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/TelegramCore",
"//submodules/Postbox",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/TelegramUIPreferences",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/GlobalControlPanelsContext",
"//submodules/PresentationDataUtils",
"//submodules/TextFormat",
"//submodules/Markdown",
"//submodules/LocalizedPeerData",
"//submodules/LiveLocationTimerNode",
"//submodules/AvatarNode",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,286 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import ComponentDisplayAdapters
import AccountContext
import TelegramCore
import GlobalControlPanelsContext
import SwiftSignalKit
import Postbox
import PresentationDataUtils
private func presentLiveLocationController(context: AccountContext, peerId: PeerId, controller: ViewController) {
let presentImpl: (EngineMessage?) -> Void = { [weak controller] message in
if let message = message, let strongController = controller {
let _ = context.sharedContext.openChatMessage(OpenChatMessageParams(context: context, chatLocation: nil, chatFilterTag: nil, chatLocationContextHolder: nil, message: message._asMessage(), standalone: false, reverseMessageGalleryOrder: false, navigationController: strongController.navigationController as? NavigationController, modal: true, dismissInput: {
controller?.view.endEditing(true)
}, present: { c, a, _ in
controller?.present(c, in: .window(.root), with: a, blockInteraction: true)
}, transitionNode: { _, _, _ in
return nil
}, addToTransitionSurface: { _ in
}, openUrl: { _ in
}, openPeer: { peer, navigation in
}, callPeer: { _, _ in
}, openConferenceCall: { _ in
}, enqueueMessage: { message in
let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start()
}, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in
}, chatAvatarHiddenMedia: { _, _ in
}))
}
}
if let id = context.liveLocationManager?.internalMessageForPeerId(peerId) {
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Messages.Message(id: id))
|> deliverOnMainQueue).start(next: presentImpl)
} else if let liveLocationManager = context.liveLocationManager {
let _ = (liveLocationManager.summaryManager.peersBroadcastingTo(peerId: peerId)
|> take(1)
|> map { peersAndMessages -> EngineMessage? in
return peersAndMessages?.first?.1
} |> deliverOnMainQueue).start(next: presentImpl)
}
}
public final class LiveLocationHeaderPanelComponent: Component {
public let context: AccountContext
public let theme: PresentationTheme
public let strings: PresentationStrings
public let data: GlobalControlPanelsContext.LiveLocation
public let controller: () -> ViewController?
public init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
data: GlobalControlPanelsContext.LiveLocation,
controller: @escaping () -> ViewController?
) {
self.context = context
self.theme = theme
self.strings = strings
self.data = data
self.controller = controller
}
public static func ==(lhs: LiveLocationHeaderPanelComponent, rhs: LiveLocationHeaderPanelComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.data != rhs.data {
return false
}
return true
}
public final class View: UIView {
private var panel: LocationBroadcastNavigationAccessoryPanel?
private var component: LiveLocationHeaderPanelComponent?
private weak var state: EmptyComponentState?
public override init(frame: CGRect) {
super.init(frame: frame)
}
required public init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
}
func update(component: LiveLocationHeaderPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
let themeUpdated = self.component?.theme !== component.theme
self.component = component
self.state = state
let panel: LocationBroadcastNavigationAccessoryPanel
if let current = self.panel {
panel = current
} else {
panel = LocationBroadcastNavigationAccessoryPanel(
accountPeerId: component.context.account.peerId,
theme: component.theme,
strings: component.strings,
nameDisplayOrder: component.context.sharedContext.currentPresentationData.with({ $0 }).nameDisplayOrder,
tapAction: { [weak self] in
guard let self, let component = self.component, let controller = component.controller() else {
return
}
switch component.data.mode {
case .all:
let messages = component.data.messages.values.sorted(by: { $0.index > $1.index })
if messages.count == 1 {
presentLiveLocationController(context: component.context, peerId: messages[0].id.peerId, controller: controller)
} else {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak actionSheet] in
actionSheet?.dismissAnimated()
}
var items: [ActionSheetItem] = []
if !messages.isEmpty {
items.append(ActionSheetTextItem(title: presentationData.strings.LiveLocation_MenuChatsCount(Int32(messages.count))))
for message in messages {
if let peer = message.peers[message.id.peerId] {
var beginTimeAndTimeout: (Double, Double)?
for media in message.media {
if let media = media as? TelegramMediaMap, let timeout = media.liveBroadcastingTimeout {
beginTimeAndTimeout = (Double(message.timestamp), Double(timeout))
}
}
if let beginTimeAndTimeout {
items.append(LocationBroadcastActionSheetItem(context: component.context, peer: peer, title: EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), beginTimestamp: beginTimeAndTimeout.0, timeout: beginTimeAndTimeout.1, strings: presentationData.strings, action: { [weak self] in
dismissAction()
guard let self, let component = self.component, let controller = component.controller() else {
return
}
presentLiveLocationController(context: component.context, peerId: peer.id, controller: controller)
}))
}
}
}
items.append(ActionSheetButtonItem(title: presentationData.strings.LiveLocation_MenuStopAll, color: .destructive, action: { [weak self] in
dismissAction()
guard let self, let component = self.component else {
return
}
for peer in component.data.peers {
component.context.liveLocationManager?.cancelLiveLocation(peerId: peer.id)
}
}))
}
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
self.window?.endEditing(true)
controller.present(actionSheet, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
case let .peer(peerId):
presentLiveLocationController(context: component.context, peerId: peerId, controller: controller)
}
},
close: { [weak self] in
guard let self, let component = self.component, let controller = component.controller() else {
return
}
var closePeers: [EnginePeer]?
var closePeerId: EnginePeer.Id?
switch component.data.mode {
case .all:
if component.data.peers.count > 1 {
closePeers = component.data.peers
} else {
closePeerId = component.data.peers.first?.id
}
case let .peer(peerId):
closePeerId = peerId
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
let dismissAction: () -> Void = { [weak actionSheet] in
actionSheet?.dismissAnimated()
}
var items: [ActionSheetItem] = []
if let closePeers = closePeers, !closePeers.isEmpty {
items.append(ActionSheetTextItem(title: presentationData.strings.LiveLocation_MenuChatsCount(Int32(closePeers.count))))
for peer in closePeers {
items.append(ActionSheetButtonItem(title: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), action: { [weak self] in
dismissAction()
guard let self, let component = self.component, let controller = component.controller() else {
return
}
presentLiveLocationController(context: component.context, peerId: peer.id, controller: controller)
}))
}
items.append(ActionSheetButtonItem(title: presentationData.strings.LiveLocation_MenuStopAll, color: .destructive, action: { [weak self] in
dismissAction()
guard let self, let component = self.component else {
return
}
for peer in closePeers {
component.context.liveLocationManager?.cancelLiveLocation(peerId: peer.id)
}
}))
} else if let closePeerId {
items.append(ActionSheetButtonItem(title: presentationData.strings.Map_StopLiveLocation, color: .destructive, action: { [weak self] in
dismissAction()
guard let self, let component = self.component else {
return
}
component.context.liveLocationManager?.cancelLiveLocation(peerId: closePeerId)
}))
}
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
self.window?.endEditing(true)
controller.present(actionSheet, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}
)
self.panel = panel
self.addSubview(panel.view)
}
let size = CGSize(width: availableSize.width, height: 40.0)
let panelFrame = CGRect(origin: CGPoint(), size: size)
transition.setFrame(view: panel.view, frame: panelFrame)
panel.updateLayout(size: panelFrame.size, leftInset: 0.0, rightInset: 0.0, transition: transition.containedViewLayoutTransition)
let mappedMode: LocationBroadcastNavigationAccessoryPanelMode
switch component.data.mode {
case .all:
mappedMode = .summary
case .peer:
mappedMode = .peer
}
panel.update(peers: component.data.peers, mode: mappedMode, canClose: component.data.canClose)
if themeUpdated {
panel.updatePresentationData(PresentationData(
strings: component.strings,
theme: component.theme,
autoNightModeTriggered: false,
chatWallpaper: .builtin(WallpaperSettings()),
chatFontSize: .regular,
chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: true),
listsFontSize: .regular,
dateTimeFormat: PresentationDateTimeFormat(),
nameDisplayOrder: .firstLast,
nameSortOrder: .firstLast,
reduceMotion: false,
largeEmoji: false
))
}
return size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
@@ -0,0 +1,139 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import Postbox
import TelegramPresentationData
import AccountContext
import LiveLocationTimerNode
import AvatarNode
public class LocationBroadcastActionSheetItem: ActionSheetItem {
public let context: AccountContext
public let peer: Peer
public let title: String
public let beginTimestamp: Double
public let timeout: Double
public let strings: PresentationStrings
public let action: () -> Void
public init(context: AccountContext, peer: Peer, title: String, beginTimestamp: Double, timeout: Double, strings: PresentationStrings, action: @escaping () -> Void) {
self.context = context
self.peer = peer
self.title = title
self.beginTimestamp = beginTimestamp
self.timeout = timeout
self.strings = strings
self.action = action
}
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
let node = LocationBroadcastActionSheetItemNode(theme: theme)
node.setItem(self)
return node
}
public func updateNode(_ node: ActionSheetItemNode) {
guard let node = node as? LocationBroadcastActionSheetItemNode else {
assertionFailure()
return
}
node.setItem(self)
node.requestLayoutUpdate()
}
}
private let avatarFont = avatarPlaceholderFont(size: 15.0)
public class LocationBroadcastActionSheetItemNode: ActionSheetItemNode {
private let theme: ActionSheetControllerTheme
private let defaultFont: UIFont
private var item: LocationBroadcastActionSheetItem?
private let button: HighlightTrackingButton
private let avatarNode: AvatarNode
private let label: ImmediateTextNode
private let timerNode: ChatMessageLiveLocationTimerNode
override public init(theme: ActionSheetControllerTheme) {
self.theme = theme
self.defaultFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0))
self.button = HighlightTrackingButton()
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isLayerBacked = !smartInvertColorsEnabled()
self.label = ImmediateTextNode()
self.label.isUserInteractionEnabled = false
self.label.displaysAsynchronously = false
self.label.maximumNumberOfLines = 1
self.timerNode = ChatMessageLiveLocationTimerNode()
super.init(theme: theme)
self.view.addSubview(self.button)
self.addSubnode(self.avatarNode)
self.addSubnode(self.label)
self.addSubnode(self.timerNode)
self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor
} else {
UIView.animate(withDuration: 0.3, animations: {
strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor
})
}
}
}
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
}
func setItem(_ item: LocationBroadcastActionSheetItem) {
self.item = item
let defaultFont = Font.regular(floor(theme.baseFontSize * 20.0 / 17.0))
let textColor: UIColor = self.theme.primaryTextColor
self.label.attributedText = NSAttributedString(string: item.title, font: defaultFont, textColor: textColor)
self.avatarNode.setPeer(context: item.context, theme: (item.context.sharedContext.currentPresentationData.with { $0 }).theme, peer: EnginePeer(item.peer))
self.timerNode.update(backgroundColor: self.theme.controlAccentColor.withAlphaComponent(0.4), foregroundColor: self.theme.controlAccentColor, textColor: self.theme.controlAccentColor, beginTimestamp: item.beginTimestamp, timeout: Int32(item.timeout) == liveLocationIndefinitePeriod ? -1.0 : item.timeout, strings: item.strings)
}
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
let size = CGSize(width: constrainedSize.width, height: 57.0)
self.button.frame = CGRect(origin: CGPoint(), size: size)
let avatarInset: CGFloat = 42.0
let avatarSize: CGFloat = 32.0
self.avatarNode.frame = CGRect(origin: CGPoint(x: 16.0, y: floor((size.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
let labelSize = self.label.updateLayout(CGSize(width: max(1.0, size.width - avatarInset - 16.0 - 16.0 - 30.0), height: size.height))
self.label.frame = CGRect(origin: CGPoint(x: 16.0 + avatarInset, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
let timerSize = CGSize(width: 28.0, height: 28.0)
self.timerNode.frame = CGRect(origin: CGPoint(x: size.width - 16.0 - timerSize.width, y: floorToScreenPixels((size.height - timerSize.height) / 2.0)), size: timerSize)
self.updateInternalLayout(size, constrainedSize: constrainedSize)
return size
}
@objc func buttonPressed() {
if let item = self.item {
item.action()
}
}
}
@@ -0,0 +1,212 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import TextFormat
import Markdown
import LocalizedPeerData
import LiveLocationTimerNode
private let titleFont = Font.regular(12.0)
private let subtitleFont = Font.regular(10.0)
enum LocationBroadcastNavigationAccessoryPanelMode {
case summary
case peer
}
final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode {
private let accountPeerId: EnginePeer.Id
private var theme: PresentationTheme
private var strings: PresentationStrings
private var nameDisplayOrder: PresentationPersonNameOrder
private let tapAction: () -> Void
private let close: () -> Void
private let contentNode: ASDisplayNode
private let iconNode: ASImageNode
private let wavesNode: LiveLocationWavesNode
private let titleNode: TextNode
private let subtitleNode: TextNode
private let closeButton: HighlightableButtonNode
private var validLayout: (CGSize, CGFloat, CGFloat)?
private var peersAndMode: ([EnginePeer], LocationBroadcastNavigationAccessoryPanelMode, Bool)?
init(accountPeerId: EnginePeer.Id, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, tapAction: @escaping () -> Void, close: @escaping () -> Void) {
self.accountPeerId = accountPeerId
self.theme = theme
self.strings = strings
self.nameDisplayOrder = nameDisplayOrder
self.tapAction = tapAction
self.close = close
self.contentNode = ASDisplayNode()
self.iconNode = ASImageNode()
self.iconNode.isLayerBacked = true
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/LiveLocationPanelIcon"), color: theme.chat.inputPanel.panelControlColor)
self.wavesNode = LiveLocationWavesNode(color: self.theme.chat.inputPanel.panelControlColor)
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.subtitleNode = TextNode()
self.subtitleNode.isUserInteractionEnabled = false
self.closeButton = HighlightableButtonNode()
self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.chat.inputPanel.panelControlColor.cgColor)
context.setLineWidth(1.33)
context.setLineCap(.round)
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0))
context.strokePath()
context.move(to: CGPoint(x: size.width - 1.0, y: 1.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
}), for: [])
self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
self.closeButton.displaysAsynchronously = false
super.init()
self.clipsToBounds = true
self.addSubnode(self.contentNode)
self.contentNode.addSubnode(self.iconNode)
self.contentNode.addSubnode(self.wavesNode)
self.contentNode.addSubnode(self.titleNode)
self.contentNode.addSubnode(self.subtitleNode)
self.contentNode.addSubnode(self.closeButton)
self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: .touchUpInside)
}
override func didLoad() {
super.didLoad()
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
self.view.addGestureRecognizer(tapRecognizer)
}
func updatePresentationData(_ presentationData: PresentationData) {
self.theme = presentationData.theme
self.strings = presentationData.strings
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat List/LiveLocationPanelIcon"), color: theme.chat.inputPanel.panelControlColor)
self.wavesNode.color = self.theme.chat.inputPanel.panelControlColor
self.closeButton.setImage(generateImage(CGSize(width: 12.0, height: 12.0), contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(theme.chat.inputPanel.panelControlColor.cgColor)
context.setLineWidth(1.33)
context.setLineCap(.round)
context.move(to: CGPoint(x: 1.0, y: 1.0))
context.addLine(to: CGPoint(x: size.width - 1.0, y: size.height - 1.0))
context.strokePath()
context.move(to: CGPoint(x: size.width - 1.0, y: 1.0))
context.addLine(to: CGPoint(x: 1.0, y: size.height - 1.0))
context.strokePath()
}), for: [])
}
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (size, leftInset, rightInset)
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
transition.updateAlpha(node: self.contentNode, alpha: isHidden ? 0.0 : 1.0)
let titleString = NSAttributedString(string: self.strings.Conversation_LiveLocation, font: titleFont, textColor: self.theme.rootController.navigationBar.primaryTextColor)
var subtitleString: NSAttributedString?
if let (peers, mode, canClose) = self.peersAndMode {
switch mode {
case .summary:
let text: String
if peers.count == 1 {
text = self.strings.DialogList_LiveLocationSharingTo(peers[0].displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder)).string
} else {
text = self.strings.DialogList_LiveLocationChatsCount(Int32(peers.count))
}
subtitleString = NSAttributedString(string: text, font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor)
case .peer:
self.closeButton.isHidden = !canClose
let filteredPeers = peers.filter {
$0.id != self.accountPeerId
}
if filteredPeers.count == 0 {
subtitleString = NSAttributedString(string: self.strings.Conversation_LiveLocationYou, font: subtitleFont, textColor: self.theme.chat.inputPanel.panelControlColor)
} else {
let otherString: String
if filteredPeers.count == 1 {
otherString = peers[0].compactDisplayTitle.replacingOccurrences(of: "*", with: "")
} else {
otherString = self.strings.Conversation_LiveLocationMembersCount(Int32(peers.count))
}
let rawText: String
if filteredPeers.count != peers.count {
rawText = self.strings.Conversation_LiveLocationYouAndOther(otherString).string
} else {
rawText = otherString
}
let body = MarkdownAttributeSet(font: subtitleFont, textColor: self.theme.rootController.navigationBar.secondaryTextColor)
let accent = MarkdownAttributeSet(font: subtitleFont, textColor: self.theme.chat.inputPanel.panelControlColor)
subtitleString = parseMarkdownIntoAttributedString(rawText, attributes: MarkdownAttributes(body: body, bold: accent, link: body, linkAttribute: { _ in nil }))
}
}
}
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - 80.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: size.width - 80.0, height: 100.0), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let _ = titleApply()
let _ = subtitleApply()
let minimizedTitleOffset: CGFloat = subtitleString == nil ? 6.0 : 0.0
let minimizedTitleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.size.width) / 2.0), y: 4.0 + minimizedTitleOffset), size: titleLayout.size)
let minimizedSubtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleLayout.size.width) / 2.0), y: 20.0), size: subtitleLayout.size)
if let image = self.iconNode.image {
transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: 7.0 + leftInset, y: 9.0), size: image.size))
transition.updateFrame(node: self.wavesNode, frame: CGRect(origin: CGPoint(x: -2.0 + leftInset, y: -3.0), size: CGSize(width: 48.0, height: 48.0)))
}
transition.updateFrame(node: self.titleNode, frame: minimizedTitleFrame)
transition.updateFrame(node: self.subtitleNode, frame: minimizedSubtitleFrame)
let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: bounds.size.width - 18.0 - closeButtonSize.width - rightInset, y: minimizedTitleFrame.minY + 10.0), size: closeButtonSize))
}
func update(peers: [EnginePeer], mode: LocationBroadcastNavigationAccessoryPanelMode, canClose: Bool) {
self.peersAndMode = (peers, mode, canClose)
if let (size, leftInset, rightInset) = self.validLayout {
self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
}
}
@objc func closePressed() {
self.close()
}
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.tapAction()
}
}
}