mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-06-08 11:03:55 +02:00
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:
@@ -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",
|
||||
],
|
||||
)
|
||||
+286
@@ -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)
|
||||
}
|
||||
}
|
||||
+139
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+212
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user