mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-14 10:20:11 +00:00
Added ability to download music without streaming Added progress indicators for various blocking tasks Fixed image gallery swipe to dismiss after zooming Added online member count indication in supergroups Fixed contact statuses in contact search
637 lines
34 KiB
Swift
637 lines
34 KiB
Swift
import Foundation
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import Postbox
|
|
import TelegramCore
|
|
import SwiftSignalKit
|
|
import LegacyComponents
|
|
|
|
enum ChatTitleContent {
|
|
case peer(peerView: PeerView, onlineMemberCount: Int32?)
|
|
case group([Peer])
|
|
}
|
|
|
|
private final class ChatTitleNetworkStatusNode: ASDisplayNode {
|
|
private var theme: PresentationTheme
|
|
|
|
private let titleNode: ImmediateTextNode
|
|
private let activityIndicator: ActivityIndicator
|
|
|
|
var title: String = "" {
|
|
didSet {
|
|
if self.title != oldValue {
|
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
|
}
|
|
}
|
|
}
|
|
|
|
init(theme: PresentationTheme) {
|
|
self.theme = theme
|
|
|
|
self.titleNode = ImmediateTextNode()
|
|
self.titleNode.isLayerBacked = true
|
|
self.titleNode.displaysAsynchronously = false
|
|
self.titleNode.maximumNumberOfLines = 1
|
|
self.titleNode.isOpaque = false
|
|
self.titleNode.isUserInteractionEnabled = false
|
|
|
|
self.activityIndicator = ActivityIndicator(type: .custom(theme.rootController.navigationBar.primaryTextColor, 22.0, 1.5), speed: .slow)
|
|
let activityIndicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0))
|
|
self.activityIndicator.frame = CGRect(origin: CGPoint(), size: activityIndicatorSize)
|
|
|
|
super.init()
|
|
|
|
self.addSubnode(self.titleNode)
|
|
self.addSubnode(self.activityIndicator)
|
|
}
|
|
|
|
func updateTheme(theme: PresentationTheme) {
|
|
self.theme = theme
|
|
|
|
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
|
self.activityIndicator.type = .custom(self.theme.rootController.navigationBar.primaryTextColor, 22.0, 1.5)
|
|
}
|
|
|
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
|
let indicatorSize = self.activityIndicator.bounds.size
|
|
let indicatorPadding = indicatorSize.width + 6.0
|
|
|
|
let titleSize = self.titleNode.updateLayout(CGSize(width: max(1.0, size.width - indicatorPadding), height: size.height))
|
|
let combinedHeight = titleSize.height
|
|
|
|
let titleFrame = CGRect(origin: CGPoint(x: indicatorPadding + floor((size.width - titleSize.width - indicatorPadding) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
|
|
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
|
|
|
transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(x: titleFrame.minX - indicatorSize.width - 6.0, y: titleFrame.minY - 1.0), size: indicatorSize))
|
|
}
|
|
}
|
|
|
|
private enum ChatTitleIcon {
|
|
case none
|
|
case lock
|
|
case mute
|
|
}
|
|
|
|
final class ChatTitleView: UIView, NavigationBarTitleView {
|
|
private let account: Account
|
|
|
|
private var theme: PresentationTheme
|
|
private var strings: PresentationStrings
|
|
private var dateTimeFormat: PresentationDateTimeFormat
|
|
|
|
private let contentContainer: ASDisplayNode
|
|
private let titleNode: ImmediateTextNode
|
|
private let titleLeftIconNode: ASImageNode
|
|
private let titleRightIconNode: ASImageNode
|
|
private let infoNode: ImmediateTextNode
|
|
private let typingNode: ImmediateTextNode
|
|
private var typingIndicator: TGModernConversationTitleActivityIndicator?
|
|
private let button: HighlightTrackingButtonNode
|
|
|
|
private var validLayout: (CGSize, CGRect)?
|
|
|
|
private var titleLeftIcon: ChatTitleIcon = .none
|
|
private var titleRightIcon: ChatTitleIcon = .none
|
|
|
|
private var networkStatusNode: ChatTitleNetworkStatusNode?
|
|
|
|
private var presenceManager: PeerPresenceStatusManager?
|
|
|
|
var inputActivities: (PeerId, [(Peer, PeerInputActivity)])? {
|
|
didSet {
|
|
if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty {
|
|
self.typingNode.isHidden = false
|
|
self.infoNode.isHidden = true
|
|
var stringValue = ""
|
|
var first = true
|
|
var mergedActivity = inputActivities[0].1
|
|
for (_, activity) in inputActivities {
|
|
if activity != mergedActivity {
|
|
mergedActivity = .typingText
|
|
break
|
|
}
|
|
}
|
|
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat {
|
|
switch mergedActivity {
|
|
case .typingText:
|
|
stringValue = strings.Conversation_typing
|
|
case .uploadingFile:
|
|
stringValue = strings.Activity_UploadingDocument
|
|
case .recordingVoice:
|
|
stringValue = strings.Activity_RecordingAudio
|
|
case .uploadingPhoto:
|
|
stringValue = strings.Activity_UploadingPhoto
|
|
case .uploadingVideo:
|
|
stringValue = strings.Activity_UploadingVideo
|
|
case .playingGame:
|
|
stringValue = strings.Activity_PlayingGame
|
|
case .recordingInstantVideo:
|
|
stringValue = strings.Activity_RecordingVideoMessage
|
|
case .uploadingInstantVideo:
|
|
stringValue = strings.Activity_UploadingVideoMessage
|
|
}
|
|
} else {
|
|
for (peer, _) in inputActivities {
|
|
let title = peer.compactDisplayTitle
|
|
if !title.isEmpty {
|
|
if first {
|
|
first = false
|
|
} else {
|
|
stringValue += ", "
|
|
}
|
|
stringValue += title
|
|
}
|
|
}
|
|
}
|
|
let string = NSAttributedString(string: stringValue, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.accentTextColor)
|
|
if self.typingNode.attributedText == nil || !self.typingNode.attributedText!.isEqual(to: string) {
|
|
self.typingNode.attributedText = string
|
|
self.setNeedsLayout()
|
|
}
|
|
if self.typingIndicator == nil {
|
|
let typingIndicator = TGModernConversationTitleActivityIndicator()
|
|
typingIndicator.setColor(self.theme.rootController.navigationBar.accentTextColor)
|
|
self.contentContainer.view.addSubview(typingIndicator)
|
|
self.typingIndicator = typingIndicator
|
|
}
|
|
switch mergedActivity {
|
|
case .typingText:
|
|
self.typingIndicator?.setTyping()
|
|
case .recordingVoice:
|
|
self.typingIndicator?.setAudioRecording()
|
|
case .uploadingFile:
|
|
self.typingIndicator?.setUploading()
|
|
case .playingGame:
|
|
self.typingIndicator?.setPlaying()
|
|
case .recordingInstantVideo:
|
|
self.typingIndicator?.setVideoRecording()
|
|
case .uploadingInstantVideo:
|
|
self.typingIndicator?.setUploading()
|
|
case .uploadingPhoto:
|
|
self.typingIndicator?.setUploading()
|
|
case .uploadingVideo:
|
|
self.typingIndicator?.setUploading()
|
|
}
|
|
} else {
|
|
self.typingNode.isHidden = true
|
|
self.infoNode.isHidden = false
|
|
self.typingNode.attributedText = nil
|
|
if let typingIndicator = self.typingIndicator {
|
|
typingIndicator.removeFromSuperview()
|
|
self.typingIndicator = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func updateNetworkStatusNode(networkState: AccountNetworkState, metrics: LayoutMetrics) {
|
|
var isOnline = false
|
|
if case .online = networkState {
|
|
isOnline = true
|
|
}
|
|
|
|
if isOnline || metrics.widthClass == .regular {
|
|
self.contentContainer.isHidden = false
|
|
if let networkStatusNode = self.networkStatusNode {
|
|
self.networkStatusNode = nil
|
|
networkStatusNode.removeFromSupernode()
|
|
}
|
|
} else {
|
|
self.contentContainer.isHidden = true
|
|
let statusNode: ChatTitleNetworkStatusNode
|
|
if let current = self.networkStatusNode {
|
|
statusNode = current
|
|
} else {
|
|
statusNode = ChatTitleNetworkStatusNode(theme: self.theme)
|
|
self.networkStatusNode = statusNode
|
|
self.insertSubview(statusNode.view, belowSubview: self.button.view)
|
|
}
|
|
switch self.networkState {
|
|
case .waitingForNetwork:
|
|
statusNode.title = self.strings.State_WaitingForNetwork
|
|
case .connecting:
|
|
statusNode.title = self.strings.State_Connecting
|
|
case .updating:
|
|
statusNode.title = self.strings.State_Updating
|
|
case .online:
|
|
break
|
|
}
|
|
}
|
|
|
|
self.setNeedsLayout()
|
|
}
|
|
|
|
var networkState: AccountNetworkState = .online(proxy: nil) {
|
|
didSet {
|
|
if self.networkState != oldValue {
|
|
updateNetworkStatusNode(networkState: self.networkState, metrics: self.layoutMetrics)
|
|
}
|
|
}
|
|
}
|
|
|
|
var layoutMetrics: LayoutMetrics = LayoutMetrics() {
|
|
didSet {
|
|
if self.layoutMetrics != oldValue {
|
|
updateNetworkStatusNode(networkState: self.networkState, metrics: self.layoutMetrics)
|
|
}
|
|
}
|
|
}
|
|
|
|
var pressed: (() -> Void)?
|
|
|
|
var titleContent: ChatTitleContent? {
|
|
didSet {
|
|
if let titleContent = self.titleContent {
|
|
var string: NSAttributedString?
|
|
var titleLeftIcon: ChatTitleIcon = .none
|
|
var titleRightIcon: ChatTitleIcon = .none
|
|
switch titleContent {
|
|
case let .peer(peerView, _):
|
|
if let peer = peerViewMainPeer(peerView) {
|
|
if peerView.peerId == self.account.peerId {
|
|
string = NSAttributedString(string: self.strings.Conversation_SavedMessages, font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
|
} else {
|
|
string = NSAttributedString(string: peer.displayTitle(strings: self.strings), font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
|
}
|
|
}
|
|
if peerView.peerId.namespace == Namespaces.Peer.SecretChat {
|
|
titleLeftIcon = .lock
|
|
}
|
|
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
|
|
if case .muted = notificationSettings.muteState {
|
|
titleRightIcon = .mute
|
|
}
|
|
}
|
|
case .group:
|
|
string = NSAttributedString(string: "Feed", font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
|
}
|
|
|
|
if let string = string, self.titleNode.attributedText == nil || !self.titleNode.attributedText!.isEqual(to: string) {
|
|
self.titleNode.attributedText = string
|
|
self.setNeedsLayout()
|
|
}
|
|
|
|
if titleLeftIcon != self.titleLeftIcon {
|
|
self.titleLeftIcon = titleLeftIcon
|
|
switch titleLeftIcon {
|
|
case .lock:
|
|
self.titleLeftIconNode.image = PresentationResourcesChat.chatTitleLockIcon(self.theme)
|
|
default:
|
|
self.titleLeftIconNode.image = nil
|
|
}
|
|
self.setNeedsLayout()
|
|
}
|
|
|
|
if titleRightIcon != self.titleRightIcon {
|
|
self.titleRightIcon = titleRightIcon
|
|
switch titleRightIcon {
|
|
case .mute:
|
|
self.titleRightIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(self.theme)
|
|
default:
|
|
self.titleRightIconNode.image = nil
|
|
}
|
|
self.setNeedsLayout()
|
|
}
|
|
|
|
self.updateStatus()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func updateStatus() {
|
|
var shouldUpdateLayout = false
|
|
if let titleContent = self.titleContent {
|
|
switch titleContent {
|
|
case let .peer(peerView, onlineMemberCount):
|
|
if let peer = peerViewMainPeer(peerView) {
|
|
if peer.id == self.account.peerId {
|
|
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
|
self.infoNode.attributedText = string
|
|
shouldUpdateLayout = true
|
|
}
|
|
} else if let user = peer as? TelegramUser {
|
|
if user.id.namespace == Namespaces.Peer.CloudUser && user.id.id == 777000 {
|
|
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
|
self.infoNode.attributedText = string
|
|
shouldUpdateLayout = true
|
|
}
|
|
} else if let _ = user.botInfo {
|
|
let string = NSAttributedString(string: self.strings.Bot_GenericBotStatus, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
|
self.infoNode.attributedText = string
|
|
shouldUpdateLayout = true
|
|
}
|
|
} else if let peer = peerViewMainPeer(peerView) {
|
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
|
let userPresence: TelegramUserPresence
|
|
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence {
|
|
userPresence = presence
|
|
self.presenceManager?.reset(presence: presence)
|
|
} else {
|
|
userPresence = TelegramUserPresence(status: .none)
|
|
}
|
|
let (string, activity) = stringAndActivityForUserPresence(strings: self.strings, dateTimeFormat: self.dateTimeFormat, presence: userPresence, relativeTo: Int32(timestamp))
|
|
let attributedString = NSAttributedString(string: string, font: Font.regular(13.0), textColor: activity ? self.theme.rootController.navigationBar.accentTextColor : self.theme.rootController.navigationBar.secondaryTextColor)
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: attributedString) {
|
|
self.infoNode.attributedText = attributedString
|
|
shouldUpdateLayout = true
|
|
}
|
|
} else {
|
|
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
|
self.infoNode.attributedText = string
|
|
shouldUpdateLayout = true
|
|
}
|
|
}
|
|
} else if let group = peer as? TelegramGroup {
|
|
var onlineCount = 0
|
|
if let cachedGroupData = peerView.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
|
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
|
for participant in participants.participants {
|
|
if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence {
|
|
let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: Int32(timestamp))
|
|
switch relativeStatus {
|
|
case .online:
|
|
onlineCount += 1
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if onlineCount > 1 {
|
|
let string = NSMutableAttributedString()
|
|
|
|
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(group.participantCount))), ", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
|
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
|
self.infoNode.attributedText = string
|
|
shouldUpdateLayout = true
|
|
}
|
|
} else {
|
|
let string = NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
|
self.infoNode.attributedText = string
|
|
shouldUpdateLayout = true
|
|
}
|
|
}
|
|
} else if let channel = peer as? TelegramChannel {
|
|
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
|
if case .group = channel.info, let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 {
|
|
let string = NSMutableAttributedString()
|
|
|
|
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
|
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineMemberCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
|
self.infoNode.attributedText = string
|
|
shouldUpdateLayout = true
|
|
}
|
|
} else {
|
|
let membersString: String
|
|
if case .group = channel.info {
|
|
membersString = strings.Conversation_StatusMembers(memberCount)
|
|
} else {
|
|
membersString = strings.Conversation_StatusSubscribers(memberCount)
|
|
}
|
|
let string = NSAttributedString(string: membersString, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
|
self.infoNode.attributedText = string
|
|
shouldUpdateLayout = true
|
|
}
|
|
}
|
|
} else {
|
|
switch channel.info {
|
|
case .group:
|
|
let string = NSAttributedString(string: strings.Group_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
|
self.infoNode.attributedText = string
|
|
shouldUpdateLayout = true
|
|
}
|
|
case .broadcast:
|
|
let string = NSAttributedString(string: strings.Channel_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
|
self.infoNode.attributedText = string
|
|
shouldUpdateLayout = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case .group:
|
|
break
|
|
}
|
|
|
|
if shouldUpdateLayout {
|
|
self.setNeedsLayout()
|
|
}
|
|
}
|
|
}
|
|
|
|
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) {
|
|
self.account = account
|
|
self.theme = theme
|
|
self.strings = strings
|
|
self.dateTimeFormat = dateTimeFormat
|
|
|
|
self.contentContainer = ASDisplayNode()
|
|
|
|
self.titleNode = ImmediateTextNode()
|
|
self.titleNode.displaysAsynchronously = false
|
|
self.titleNode.maximumNumberOfLines = 1
|
|
self.titleNode.isOpaque = false
|
|
|
|
self.titleLeftIconNode = ASImageNode()
|
|
self.titleLeftIconNode.isLayerBacked = true
|
|
self.titleLeftIconNode.displayWithoutProcessing = true
|
|
self.titleLeftIconNode.displaysAsynchronously = false
|
|
|
|
self.titleRightIconNode = ASImageNode()
|
|
self.titleRightIconNode.isLayerBacked = true
|
|
self.titleRightIconNode.displayWithoutProcessing = true
|
|
self.titleRightIconNode.displaysAsynchronously = false
|
|
|
|
self.infoNode = ImmediateTextNode()
|
|
self.infoNode.displaysAsynchronously = false
|
|
self.infoNode.maximumNumberOfLines = 1
|
|
self.infoNode.isOpaque = false
|
|
|
|
self.typingNode = ImmediateTextNode()
|
|
self.typingNode.displaysAsynchronously = false
|
|
self.typingNode.maximumNumberOfLines = 1
|
|
self.typingNode.isOpaque = false
|
|
|
|
self.button = HighlightTrackingButtonNode()
|
|
|
|
super.init(frame: CGRect())
|
|
|
|
self.addSubnode(self.contentContainer)
|
|
self.contentContainer.addSubnode(self.titleNode)
|
|
self.contentContainer.addSubnode(self.infoNode)
|
|
self.contentContainer.addSubnode(self.typingNode)
|
|
self.addSubnode(self.button)
|
|
|
|
self.presenceManager = PeerPresenceStatusManager(update: { [weak self] in
|
|
self?.updateStatus()
|
|
})
|
|
|
|
self.button.addTarget(self, action: #selector(buttonPressed), forControlEvents: [.touchUpInside])
|
|
self.button.highligthedChanged = { [weak self] highlighted in
|
|
if let strongSelf = self {
|
|
if highlighted {
|
|
strongSelf.titleNode.layer.removeAnimation(forKey: "opacity")
|
|
strongSelf.infoNode.layer.removeAnimation(forKey: "opacity")
|
|
strongSelf.typingNode.layer.removeAnimation(forKey: "opacity")
|
|
strongSelf.titleNode.alpha = 0.4
|
|
strongSelf.infoNode.alpha = 0.4
|
|
strongSelf.typingNode.alpha = 0.4
|
|
} else {
|
|
strongSelf.titleNode.alpha = 1.0
|
|
strongSelf.infoNode.alpha = 1.0
|
|
strongSelf.typingNode.alpha = 1.0
|
|
strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
|
strongSelf.infoNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
|
strongSelf.typingNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
|
|
if let (size, clearBounds) = self.validLayout {
|
|
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
|
|
}
|
|
}
|
|
|
|
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
|
self.theme = theme
|
|
self.strings = strings
|
|
|
|
self.networkStatusNode?.updateTheme(theme: theme)
|
|
let titleContent = self.titleContent
|
|
self.titleContent = titleContent
|
|
self.updateStatus()
|
|
|
|
if let (size, clearBounds) = self.validLayout {
|
|
self.updateLayout(size: size, clearBounds: clearBounds, transition: .immediate)
|
|
}
|
|
}
|
|
|
|
func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) {
|
|
self.validLayout = (size, clearBounds)
|
|
|
|
let transition: ContainedViewLayoutTransition = .immediate
|
|
|
|
self.button.frame = clearBounds
|
|
self.contentContainer.frame = clearBounds
|
|
|
|
var leftIconWidth: CGFloat = 0.0
|
|
var rightIconWidth: CGFloat = 0.0
|
|
|
|
if let image = self.titleLeftIconNode.image {
|
|
if self.titleLeftIconNode.supernode == nil {
|
|
self.contentContainer.addSubnode(titleLeftIconNode)
|
|
}
|
|
leftIconWidth = image.size.width + 6.0
|
|
} else if self.titleLeftIconNode.supernode != nil {
|
|
self.titleLeftIconNode.removeFromSupernode()
|
|
}
|
|
|
|
if let image = self.titleRightIconNode.image {
|
|
if self.titleRightIconNode.supernode == nil {
|
|
self.contentContainer.addSubnode(titleRightIconNode)
|
|
}
|
|
rightIconWidth = image.size.width + 3.0
|
|
} else if self.titleRightIconNode.supernode != nil {
|
|
self.titleRightIconNode.removeFromSupernode()
|
|
}
|
|
|
|
if size.height > 40.0 {
|
|
let titleSize = self.titleNode.updateLayout(CGSize(width: clearBounds.width - leftIconWidth - rightIconWidth, height: size.height))
|
|
let infoSize = self.infoNode.updateLayout(clearBounds.size)
|
|
let typingSize = self.typingNode.updateLayout(clearBounds.size)
|
|
let titleInfoSpacing: CGFloat = 0.0
|
|
|
|
var titleFrame: CGRect
|
|
|
|
if infoSize.width.isZero && typingSize.width.isZero {
|
|
titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
|
|
if titleFrame.size.width < size.width {
|
|
titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0)
|
|
}
|
|
self.titleNode.frame = titleFrame
|
|
} else {
|
|
let combinedHeight = titleSize.height + infoSize.height + titleInfoSpacing
|
|
|
|
titleFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - titleSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0)), size: titleSize)
|
|
if titleFrame.size.width < size.width {
|
|
titleFrame.origin.x = -clearBounds.minX + floor((size.width - titleFrame.width) / 2.0)
|
|
}
|
|
titleFrame.origin.x = max(titleFrame.origin.x, clearBounds.minX + leftIconWidth)
|
|
self.titleNode.frame = titleFrame
|
|
|
|
var infoFrame = CGRect(origin: CGPoint(x: floor((clearBounds.width - infoSize.width) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: infoSize)
|
|
if infoFrame.size.width < size.width {
|
|
infoFrame.origin.x = -clearBounds.minX + floor((size.width - infoFrame.width) / 2.0)
|
|
}
|
|
self.infoNode.frame = infoFrame
|
|
self.typingNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - typingSize.width + 14.0) / 2.0), y: floor((size.height - combinedHeight) / 2.0) + titleSize.height + titleInfoSpacing), size: typingSize)
|
|
|
|
if let typingIndicator = self.typingIndicator {
|
|
typingIndicator.frame = CGRect(x: self.typingNode.frame.origin.x - 24.0, y: self.typingNode.frame.origin.y, width: 24.0, height: 16.0)
|
|
}
|
|
}
|
|
|
|
if let image = self.titleLeftIconNode.image {
|
|
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX - image.size.width - 3.0 - UIScreenPixel, y: titleFrame.minY + 4.0), size: image.size)
|
|
}
|
|
if let image = self.titleRightIconNode.image {
|
|
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX + 3.0, y: titleFrame.minY + 7.0), size: image.size)
|
|
}
|
|
} else {
|
|
let titleSize = self.titleNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0 - leftIconWidth - rightIconWidth), height: size.height))
|
|
let infoSize = self.infoNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0), height: size.height))
|
|
let typingSize = self.typingNode.updateLayout(CGSize(width: floor(clearBounds.width / 2.0), height: size.height))
|
|
|
|
let titleInfoSpacing: CGFloat = 8.0
|
|
let combinedWidth = titleSize.width + leftIconWidth + rightIconWidth + infoSize.width + titleInfoSpacing
|
|
|
|
let titleFrame = CGRect(origin: CGPoint(x: leftIconWidth + floor((clearBounds.width - combinedWidth) / 2.0), y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
|
|
self.titleNode.frame = titleFrame
|
|
self.infoNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - infoSize.height) / 2.0)), size: infoSize)
|
|
self.typingNode.frame = CGRect(origin: CGPoint(x: floor((clearBounds.width - combinedWidth) / 2.0 + titleSize.width + leftIconWidth + rightIconWidth + titleInfoSpacing), y: floor((size.height - typingSize.height) / 2.0)), size: typingSize)
|
|
|
|
if let image = self.titleLeftIconNode.image {
|
|
self.titleLeftIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.minY + 4.0), size: image.size)
|
|
}
|
|
if let image = self.titleRightIconNode.image {
|
|
self.titleRightIconNode.frame = CGRect(origin: CGPoint(x: titleFrame.maxX - image.size.width - 1.0, y: titleFrame.minY + 6.0), size: image.size)
|
|
}
|
|
}
|
|
|
|
if let networkStatusNode = self.networkStatusNode {
|
|
transition.updateFrame(node: networkStatusNode, frame: CGRect(origin: CGPoint(), size: size))
|
|
networkStatusNode.updateLayout(size: size, transition: transition)
|
|
}
|
|
}
|
|
|
|
@objc func buttonPressed() {
|
|
if let pressed = self.pressed {
|
|
pressed()
|
|
}
|
|
}
|
|
|
|
func animateLayoutTransition() {
|
|
UIView.transition(with: self, duration: 0.25, options: [.transitionCrossDissolve], animations: {
|
|
|
|
}, completion: nil)
|
|
}
|
|
}
|