mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
no message
This commit is contained in:
parent
bcecb46e94
commit
702d19a07d
@ -40,6 +40,8 @@
|
|||||||
D0B843D51DA95427005F29E1 /* GroupInfoEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843D41DA95427005F29E1 /* GroupInfoEntries.swift */; };
|
D0B843D51DA95427005F29E1 /* GroupInfoEntries.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843D41DA95427005F29E1 /* GroupInfoEntries.swift */; };
|
||||||
D0B843D91DAAAA0C005F29E1 /* PeerInfoPeerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843D81DAAAA0C005F29E1 /* PeerInfoPeerItem.swift */; };
|
D0B843D91DAAAA0C005F29E1 /* PeerInfoPeerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843D81DAAAA0C005F29E1 /* PeerInfoPeerItem.swift */; };
|
||||||
D0B843DB1DAAB138005F29E1 /* PeerInfoPeerActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843DA1DAAB138005F29E1 /* PeerInfoPeerActionItem.swift */; };
|
D0B843DB1DAAB138005F29E1 /* PeerInfoPeerActionItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843DA1DAAB138005F29E1 /* PeerInfoPeerActionItem.swift */; };
|
||||||
|
D0B844561DAC3AEE005F29E1 /* PresenceStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */; };
|
||||||
|
D0B844581DAC44E8005F29E1 /* PeerPresenceStatusManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */; };
|
||||||
D0BA6F831D784C520034826E /* ChatInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F821D784C520034826E /* ChatInputPanelNode.swift */; };
|
D0BA6F831D784C520034826E /* ChatInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F821D784C520034826E /* ChatInputPanelNode.swift */; };
|
||||||
D0BA6F851D784ECD0034826E /* ChatInterfaceStateInputPanels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F841D784ECD0034826E /* ChatInterfaceStateInputPanels.swift */; };
|
D0BA6F851D784ECD0034826E /* ChatInterfaceStateInputPanels.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F841D784ECD0034826E /* ChatInterfaceStateInputPanels.swift */; };
|
||||||
D0BA6F881D784F880034826E /* ChatMessageSelectionInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */; };
|
D0BA6F881D784F880034826E /* ChatMessageSelectionInputPanelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */; };
|
||||||
@ -252,6 +254,8 @@
|
|||||||
D0B843D41DA95427005F29E1 /* GroupInfoEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupInfoEntries.swift; sourceTree = "<group>"; };
|
D0B843D41DA95427005F29E1 /* GroupInfoEntries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupInfoEntries.swift; sourceTree = "<group>"; };
|
||||||
D0B843D81DAAAA0C005F29E1 /* PeerInfoPeerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInfoPeerItem.swift; sourceTree = "<group>"; };
|
D0B843D81DAAAA0C005F29E1 /* PeerInfoPeerItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInfoPeerItem.swift; sourceTree = "<group>"; };
|
||||||
D0B843DA1DAAB138005F29E1 /* PeerInfoPeerActionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInfoPeerActionItem.swift; sourceTree = "<group>"; };
|
D0B843DA1DAAB138005F29E1 /* PeerInfoPeerActionItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerInfoPeerActionItem.swift; sourceTree = "<group>"; };
|
||||||
|
D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceStrings.swift; sourceTree = "<group>"; };
|
||||||
|
D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerPresenceStatusManager.swift; sourceTree = "<group>"; };
|
||||||
D0BA6F821D784C520034826E /* ChatInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputPanelNode.swift; sourceTree = "<group>"; };
|
D0BA6F821D784C520034826E /* ChatInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInputPanelNode.swift; sourceTree = "<group>"; };
|
||||||
D0BA6F841D784ECD0034826E /* ChatInterfaceStateInputPanels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceStateInputPanels.swift; sourceTree = "<group>"; };
|
D0BA6F841D784ECD0034826E /* ChatInterfaceStateInputPanels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceStateInputPanels.swift; sourceTree = "<group>"; };
|
||||||
D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageSelectionInputPanelNode.swift; sourceTree = "<group>"; };
|
D0BA6F871D784F880034826E /* ChatMessageSelectionInputPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatMessageSelectionInputPanelNode.swift; sourceTree = "<group>"; };
|
||||||
@ -544,6 +548,14 @@
|
|||||||
name = "Peer Media Collection";
|
name = "Peer Media Collection";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
D0B844541DAC3ADF005F29E1 /* Strings */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D0B844551DAC3AEE005F29E1 /* PresenceStrings.swift */,
|
||||||
|
);
|
||||||
|
name = Strings;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
D0BA6F811D784C3A0034826E /* Input Panels */ = {
|
D0BA6F811D784C3A0034826E /* Input Panels */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -1007,8 +1019,10 @@
|
|||||||
D0F69E911D6B8C8E0046BCD6 /* Utils */ = {
|
D0F69E911D6B8C8E0046BCD6 /* Utils */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D0B844541DAC3ADF005F29E1 /* Strings */,
|
||||||
D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */,
|
D0F69E931D6B8C9B0046BCD6 /* ProgressiveImage.swift */,
|
||||||
D0F69E941D6B8C9B0046BCD6 /* WebP.swift */,
|
D0F69E941D6B8C9B0046BCD6 /* WebP.swift */,
|
||||||
|
D0B844571DAC44E8005F29E1 /* PeerPresenceStatusManager.swift */,
|
||||||
);
|
);
|
||||||
name = Utils;
|
name = Utils;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1197,6 +1211,7 @@
|
|||||||
D0F69EA21D6B8E380046BCD6 /* PhotoResources.swift in Sources */,
|
D0F69EA21D6B8E380046BCD6 /* PhotoResources.swift in Sources */,
|
||||||
D0BA6F831D784C520034826E /* ChatInputPanelNode.swift in Sources */,
|
D0BA6F831D784C520034826E /* ChatInputPanelNode.swift in Sources */,
|
||||||
D0F69DC71D6B89E70046BCD6 /* TransformImageNode.swift in Sources */,
|
D0F69DC71D6B89E70046BCD6 /* TransformImageNode.swift in Sources */,
|
||||||
|
D0B844581DAC44E8005F29E1 /* PeerPresenceStatusManager.swift in Sources */,
|
||||||
D0D2686C1D788F8200C422DA /* NavigationAccessoryPanelNode.swift in Sources */,
|
D0D2686C1D788F8200C422DA /* NavigationAccessoryPanelNode.swift in Sources */,
|
||||||
D07A7DA51D95783C005BCD27 /* ListMessageNode.swift in Sources */,
|
D07A7DA51D95783C005BCD27 /* ListMessageNode.swift in Sources */,
|
||||||
D0F69E341D6B8B030046BCD6 /* ChatMessageForwardInfoNode.swift in Sources */,
|
D0F69E341D6B8B030046BCD6 /* ChatMessageForwardInfoNode.swift in Sources */,
|
||||||
@ -1247,6 +1262,7 @@
|
|||||||
D0F69E761D6B8C340046BCD6 /* ContactsSearchContainerNode.swift in Sources */,
|
D0F69E761D6B8C340046BCD6 /* ContactsSearchContainerNode.swift in Sources */,
|
||||||
D0B843CF1DA922AD005F29E1 /* PeerInfoEntries.swift in Sources */,
|
D0B843CF1DA922AD005F29E1 /* PeerInfoEntries.swift in Sources */,
|
||||||
D0DF0CA61D82BCE0008AEB01 /* MentionsTableCell.swift in Sources */,
|
D0DF0CA61D82BCE0008AEB01 /* MentionsTableCell.swift in Sources */,
|
||||||
|
D0B844561DAC3AEE005F29E1 /* PresenceStrings.swift in Sources */,
|
||||||
D0F69E011D6B8A880046BCD6 /* ChatListEmptyItem.swift in Sources */,
|
D0F69E011D6B8A880046BCD6 /* ChatListEmptyItem.swift in Sources */,
|
||||||
D0F69E591D6B8BDA0046BCD6 /* GalleryPagerNode.swift in Sources */,
|
D0F69E591D6B8BDA0046BCD6 /* GalleryPagerNode.swift in Sources */,
|
||||||
D0F69E391D6B8B030046BCD6 /* ChatMessageMediaBubbleContentNode.swift in Sources */,
|
D0F69E391D6B8B030046BCD6 /* ChatMessageMediaBubbleContentNode.swift in Sources */,
|
||||||
@ -1600,6 +1616,7 @@
|
|||||||
"$(PROJECT_DIR)/third-party/FFmpeg-iOS/lib",
|
"$(PROJECT_DIR)/third-party/FFmpeg-iOS/lib",
|
||||||
);
|
);
|
||||||
OTHER_LDFLAGS = "-ObjC";
|
OTHER_LDFLAGS = "-ObjC";
|
||||||
|
OTHER_SWIFT_FLAGS = "-DDEBUG";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramUI;
|
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.TelegramUI;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
|
@ -44,8 +44,8 @@ enum ChannelInfoEntry: PeerInfoEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var stableId: Int {
|
var stableId: PeerInfoEntryStableId {
|
||||||
return self.sortIndex
|
return IntPeerInfoEntryStableId(value: self.sortIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEqual(to: PeerInfoEntry) -> Bool {
|
func isEqual(to: PeerInfoEntry) -> Bool {
|
||||||
|
@ -15,13 +15,13 @@ private enum SubscriberAction {
|
|||||||
private func titleAndColorForAction(_ action: SubscriberAction) -> (String, UIColor) {
|
private func titleAndColorForAction(_ action: SubscriberAction) -> (String, UIColor) {
|
||||||
switch action {
|
switch action {
|
||||||
case .join:
|
case .join:
|
||||||
return ("Join", UIColor(0x1195f2))
|
return ("Join", UIColor(0x007ee5))
|
||||||
case .kicked:
|
case .kicked:
|
||||||
return ("Join", UIColor.gray)
|
return ("Join", UIColor.gray)
|
||||||
case .muteNotifications:
|
case .muteNotifications:
|
||||||
return ("Mute", UIColor(0x1195f2))
|
return ("Mute", UIColor(0x007ee5))
|
||||||
case .unmuteNotifications:
|
case .unmuteNotifications:
|
||||||
return ("Unmute", UIColor(0x1195f2))
|
return ("Unmute", UIColor(0x007ee5))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class ChatHoleItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
super.init(layerBacked: true)
|
super.init(layerBacked: true)
|
||||||
|
|
||||||
self.backgroundNode.image = backgroundImage(color: UIColor(0x1195f2))
|
self.backgroundNode.image = backgroundImage(color: UIColor(0x007ee5))
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
|
|
||||||
self.addSubnode(self.labelNode)
|
self.addSubnode(self.labelNode)
|
||||||
|
@ -94,7 +94,7 @@ private func generateBadgeBackgroundImage(active: Bool) -> UIImage? {
|
|||||||
return generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 20.0, height: 20.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
if active {
|
if active {
|
||||||
context.setFillColor(UIColor(0x1195f2).cgColor)
|
context.setFillColor(UIColor(0x007ee5).cgColor)
|
||||||
} else {
|
} else {
|
||||||
context.setFillColor(UIColor(0xbbbbbb).cgColor)
|
context.setFillColor(UIColor(0xbbbbbb).cgColor)
|
||||||
}
|
}
|
||||||
@ -116,6 +116,7 @@ class ChatListItemNode: ListViewItemNode {
|
|||||||
var combinedReadState: CombinedPeerReadState?
|
var combinedReadState: CombinedPeerReadState?
|
||||||
var notificationSettings: PeerNotificationSettings?
|
var notificationSettings: PeerNotificationSettings?
|
||||||
|
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
|
||||||
let avatarNode: AvatarNode
|
let avatarNode: AvatarNode
|
||||||
@ -132,6 +133,11 @@ class ChatListItemNode: ListViewItemNode {
|
|||||||
var relativePosition: (first: Bool, last: Bool) = (false, false)
|
var relativePosition: (first: Bool, last: Bool) = (false, false)
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
self.backgroundNode.displaysAsynchronously = false
|
||||||
|
self.backgroundNode.backgroundColor = .white
|
||||||
|
|
||||||
self.avatarNode = AvatarNode(font: Font.regular(24.0))
|
self.avatarNode = AvatarNode(font: Font.regular(24.0))
|
||||||
self.avatarNode.isLayerBacked = true
|
self.avatarNode.isLayerBacked = true
|
||||||
|
|
||||||
@ -185,6 +191,7 @@ class ChatListItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.separatorNode)
|
self.addSubnode(self.separatorNode)
|
||||||
self.addSubnode(self.avatarNode)
|
self.addSubnode(self.avatarNode)
|
||||||
self.addSubnode(self.contentNode)
|
self.addSubnode(self.contentNode)
|
||||||
@ -220,6 +227,7 @@ class ChatListItemNode: ListViewItemNode {
|
|||||||
let size = self.bounds.size
|
let size = self.bounds.size
|
||||||
let insets = self.insets
|
let insets = self.insets
|
||||||
|
|
||||||
|
self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top - separatorHeight), size: CGSize(width: size.width, height: size.height + separatorHeight))
|
self.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -insets.top - separatorHeight), size: CGSize(width: size.width, height: size.height + separatorHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,7 +477,10 @@ class ChatListItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||||
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
|
||||||
self.avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ private final class ChatMediaActionSheetRollItemNode: ActionSheetItemNode, PHPho
|
|||||||
self.label = UILabel()
|
self.label = UILabel()
|
||||||
self.label.backgroundColor = nil
|
self.label.backgroundColor = nil
|
||||||
self.label.isOpaque = false
|
self.label.isOpaque = false
|
||||||
self.label.textColor = UIColor(0x1195f2)
|
self.label.textColor = UIColor(0x007ee5)
|
||||||
self.label.text = "Photo or Video"
|
self.label.text = "Photo or Video"
|
||||||
self.label.font = Font.regular(20.0)
|
self.label.font = Font.regular(20.0)
|
||||||
self.label.sizeToFit()
|
self.label.sizeToFit()
|
||||||
|
@ -33,7 +33,7 @@ class ChatMessageActionItemNode: ChatMessageItemView {
|
|||||||
|
|
||||||
super.init(layerBacked: false)
|
super.init(layerBacked: false)
|
||||||
|
|
||||||
self.backgroundNode.image = backgroundImage(color: UIColor(0x1195f2))
|
self.backgroundNode.image = backgroundImage(color: UIColor(0x007ee5))
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.labelNode)
|
self.addSubnode(self.labelNode)
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,9 @@ class ChatMessageBubbleContentNode: ASDisplayNode {
|
|||||||
func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
}
|
||||||
|
|
||||||
func animateInsertionIntoBubble(_ duration: Double) {
|
func animateInsertionIntoBubble(_ duration: Double) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,6 +187,16 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
super.animateRemoved(currentTimestamp, duration: duration)
|
||||||
|
|
||||||
|
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
|
||||||
|
for contentNode in self.contentNodes {
|
||||||
|
contentNode.animateRemoved(currentTimestamp, duration: duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||||
super.animateAdded(currentTimestamp, duration: duration)
|
super.animateAdded(currentTimestamp, duration: duration)
|
||||||
|
|
||||||
@ -347,7 +357,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView {
|
|||||||
headerSize.height += 4.0
|
headerSize.height += 4.0
|
||||||
}
|
}
|
||||||
|
|
||||||
let inlineBotNameColor = incoming ? UIColor(0x1195f2) : UIColor(0x00a700)
|
let inlineBotNameColor = incoming ? UIColor(0x007ee5) : UIColor(0x00a700)
|
||||||
|
|
||||||
let attributedString: NSAttributedString
|
let attributedString: NSAttributedString
|
||||||
if let authorNameString = authorNameString, let authorNameColor = authorNameColor, let inlineBotNameString = inlineBotNameString {
|
if let authorNameString = authorNameString, let authorNameColor = authorNameColor, let inlineBotNameString = inlineBotNameString {
|
||||||
|
@ -190,10 +190,10 @@ class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
|||||||
|
|
||||||
let checkSize = checkFullImage!.size
|
let checkSize = checkFullImage!.size
|
||||||
|
|
||||||
checkSentFrame = CGRect(origin: CGPoint(x: leftInset + date.size.width + 5.0 + statusWidth - checkSize.width - (read ? 0.0 : 2.5), y: 3.0), size: checkSize)
|
|
||||||
if read {
|
if read {
|
||||||
checkReadFrame = CGRect(origin: CGPoint(x: checkSentFrame!.origin.x - 6.0, y: checkSentFrame!.origin.y), size: checkSize)
|
checkReadFrame = CGRect(origin: CGPoint(x: leftInset + date.size.width + 5.0 + statusWidth - checkSize.width, y: 3.0), size: checkSize)
|
||||||
}
|
}
|
||||||
|
checkSentFrame = CGRect(origin: CGPoint(x: leftInset + date.size.width + 5.0 + statusWidth - checkSize.width - 6.0, y: 3.0), size: checkSize)
|
||||||
case .Failed:
|
case .Failed:
|
||||||
statusWidth = 0.0
|
statusWidth = 0.0
|
||||||
|
|
||||||
@ -248,15 +248,11 @@ class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
|||||||
if let checkSentNode = checkSentNode, let checkReadNode = checkReadNode {
|
if let checkSentNode = checkSentNode, let checkReadNode = checkReadNode {
|
||||||
var animateSentNode = false
|
var animateSentNode = false
|
||||||
if strongSelf.checkSentNode == nil {
|
if strongSelf.checkSentNode == nil {
|
||||||
|
checkSentNode.image = loadedCheckFullImage
|
||||||
strongSelf.checkSentNode = checkSentNode
|
strongSelf.checkSentNode = checkSentNode
|
||||||
strongSelf.addSubnode(checkSentNode)
|
strongSelf.addSubnode(checkSentNode)
|
||||||
animateSentNode = animated
|
animateSentNode = animated
|
||||||
}
|
}
|
||||||
if checkReadFrame != nil {
|
|
||||||
checkSentNode.image = loadedCheckPartialImage
|
|
||||||
} else {
|
|
||||||
checkSentNode.image = loadedCheckFullImage
|
|
||||||
}
|
|
||||||
|
|
||||||
if let checkSentFrame = checkSentFrame {
|
if let checkSentFrame = checkSentFrame {
|
||||||
if checkSentNode.isHidden {
|
if checkSentNode.isHidden {
|
||||||
@ -271,10 +267,10 @@ class ChatMessageDateAndStatusNode: ASTransformLayerNode {
|
|||||||
var animateReadNode = false
|
var animateReadNode = false
|
||||||
if strongSelf.checkReadNode == nil {
|
if strongSelf.checkReadNode == nil {
|
||||||
animateReadNode = animated
|
animateReadNode = animated
|
||||||
|
checkReadNode.image = loadedCheckPartialImage
|
||||||
strongSelf.checkReadNode = checkReadNode
|
strongSelf.checkReadNode = checkReadNode
|
||||||
strongSelf.addSubnode(checkReadNode)
|
strongSelf.addSubnode(checkReadNode)
|
||||||
}
|
}
|
||||||
checkReadNode.image = loadedCheckFullImage
|
|
||||||
|
|
||||||
if let checkReadFrame = checkReadFrame {
|
if let checkReadFrame = checkReadFrame {
|
||||||
if checkReadNode.isHidden {
|
if checkReadNode.isHidden {
|
||||||
|
@ -70,4 +70,8 @@ class ChatMessageFileBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||||
self.interactiveFileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.interactiveFileNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.interactiveFileNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode {
|
|||||||
strongSelf.fetchStatus = status
|
strongSelf.fetchStatus = status
|
||||||
|
|
||||||
if strongSelf.progressNode == nil {
|
if strongSelf.progressNode == nil {
|
||||||
let progressNode = RadialProgressNode(theme: RadialProgressTheme(backgroundColor: UIColor(incoming ? 0x1195f2 : 0x3fc33b), foregroundColor: incoming ? UIColor.white : UIColor(0xe1ffc7), icon: incoming ? fileIconIncomingImage : fileIconOutgoingImage))
|
let progressNode = RadialProgressNode(theme: RadialProgressTheme(backgroundColor: UIColor(incoming ? 0x007ee5 : 0x3fc33b), foregroundColor: incoming ? UIColor.white : UIColor(0xe1ffc7), icon: incoming ? fileIconIncomingImage : fileIconOutgoingImage))
|
||||||
strongSelf.progressNode = progressNode
|
strongSelf.progressNode = progressNode
|
||||||
progressNode.frame = progressFrame
|
progressNode.frame = progressFrame
|
||||||
strongSelf.addSubnode(progressNode)
|
strongSelf.addSubnode(progressNode)
|
||||||
|
@ -81,6 +81,10 @@ class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.interactiveImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.interactiveImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.interactiveImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
|
|
||||||
override func transitionNode(media: Media) -> ASDisplayNode? {
|
override func transitionNode(media: Media) -> ASDisplayNode? {
|
||||||
if let currentMedia = self.media, currentMedia.isEqual(media) {
|
if let currentMedia = self.media, currentMedia.isEqual(media) {
|
||||||
return self.interactiveImageNode
|
return self.interactiveImageNode
|
||||||
|
@ -46,9 +46,9 @@ final class ChatMessageSelectionInputPanelNode: ChatInputPanelNode {
|
|||||||
self.deleteButton = UIButton()
|
self.deleteButton = UIButton()
|
||||||
self.forwardButton = UIButton()
|
self.forwardButton = UIButton()
|
||||||
|
|
||||||
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionThrash"), color: UIColor(0x1195f2)), for: [.normal])
|
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionThrash"), color: UIColor(0x007ee5)), for: [.normal])
|
||||||
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionThrash"), color: UIColor(0xdededf)), for: [.disabled])
|
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionThrash"), color: UIColor(0xdededf)), for: [.disabled])
|
||||||
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionForward"), color: UIColor(0x1195f2)), for: [.normal])
|
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionForward"), color: UIColor(0x007ee5)), for: [.normal])
|
||||||
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionForward"), color: UIColor(0xdededf)), for: [.disabled])
|
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Acessory Panels/MessageSelectionForward"), color: UIColor(0xdededf)), for: [.disabled])
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
@ -180,4 +180,9 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,11 +381,24 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||||
self.lineNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.lineNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.inlineImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
override func animateAdded(_ currentTimestamp: Double, duration: Double) {
|
||||||
self.lineNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.lineNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||||
|
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.inlineImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.lineNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||||
|
self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||||
|
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.inlineImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func animateInsertionIntoBubble(_ duration: Double) {
|
override func animateInsertionIntoBubble(_ duration: Double) {
|
||||||
|
@ -107,7 +107,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
|
|
||||||
self.sendButton.titleLabel?.font = Font.medium(17.0)
|
self.sendButton.titleLabel?.font = Font.medium(17.0)
|
||||||
self.sendButton.contentEdgeInsets = UIEdgeInsets(top: 8.0, left: 6.0, bottom: 8.0, right: 6.0)
|
self.sendButton.contentEdgeInsets = UIEdgeInsets(top: 8.0, left: 6.0, bottom: 8.0, right: 6.0)
|
||||||
self.sendButton.setTitleColor(UIColor(0x1195f2), for: [])
|
self.sendButton.setTitleColor(UIColor(0x007ee5), for: [])
|
||||||
self.sendButton.setTitleColor(UIColor.gray, for: [.highlighted])
|
self.sendButton.setTitleColor(UIColor.gray, for: [.highlighted])
|
||||||
self.sendButton.setTitle("Send", for: [])
|
self.sendButton.setTitle("Send", for: [])
|
||||||
self.sendButton.sizeToFit()
|
self.sendButton.sizeToFit()
|
||||||
|
@ -3,33 +3,110 @@ import AsyncDisplayKit
|
|||||||
import Display
|
import Display
|
||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
final class ChatTitleView: UIView {
|
final class ChatTitleView: UIView {
|
||||||
private let titleNode: ASTextNode
|
private let titleNode: ASTextNode
|
||||||
private let infoNode: ASTextNode
|
private let infoNode: ASTextNode
|
||||||
|
private let button: HighlightTrackingButton
|
||||||
|
|
||||||
|
private var presenceManager: PeerPresenceStatusManager?
|
||||||
|
|
||||||
|
var pressed: (() -> Void)?
|
||||||
|
|
||||||
var peerView: PeerView? {
|
var peerView: PeerView? {
|
||||||
didSet {
|
didSet {
|
||||||
if let peerView = self.peerView, let peer = peerView.peers[peerView.peerId] {
|
if let peerView = self.peerView, let peer = peerView.peers[peerView.peerId] {
|
||||||
self.titleNode.attributedText = NSAttributedString(string: peer.displayTitle, font: Font.medium(17.0), textColor: UIColor.black)
|
let string = NSAttributedString(string: peer.displayTitle, font: Font.medium(17.0), textColor: UIColor.black)
|
||||||
|
|
||||||
|
if self.titleNode.attributedText == nil || !self.titleNode.attributedText!.isEqual(to: string) {
|
||||||
|
self.titleNode.attributedText = string
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.updateStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateStatus() {
|
||||||
|
var shouldUpdateLayout = false
|
||||||
|
if let peerView = self.peerView, let peer = peerView.peers[peerView.peerId] {
|
||||||
if let user = peer as? TelegramUser {
|
if let user = peer as? TelegramUser {
|
||||||
self.infoNode.attributedText = NSAttributedString(string: "last seen recently", font: Font.regular(13.0), textColor: UIColor(0x787878))
|
if let presence = peerView.peerPresences[peerView.peerId] as? TelegramUserPresence {
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||||
|
let (string, activity) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp))
|
||||||
|
let attributedString = NSAttributedString(string: string, font: Font.regular(13.0), textColor: activity ? UIColor(0x007ee5) : UIColor(0x787878))
|
||||||
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: attributedString) {
|
||||||
|
self.infoNode.attributedText = attributedString
|
||||||
|
shouldUpdateLayout = true
|
||||||
|
}
|
||||||
|
|
||||||
|
self.presenceManager?.reset(presence: presence)
|
||||||
|
} else {
|
||||||
|
let string = NSAttributedString(string: "offline", font: Font.regular(13.0), textColor: UIColor(0x787878))
|
||||||
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
||||||
|
self.infoNode.attributedText = string
|
||||||
|
shouldUpdateLayout = true
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if let group = peer as? TelegramGroup {
|
} else if let group = peer as? TelegramGroup {
|
||||||
self.infoNode.attributedText = NSAttributedString(string: "\(group.participantCount) members", font: Font.regular(13.0), textColor: UIColor(0x787878))
|
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: "\(group.participantCount) members, ", font: Font.regular(13.0), textColor: UIColor(0x787878)))
|
||||||
|
string.append(NSAttributedString(string: "\(onlineCount) online", font: Font.regular(13.0), textColor: UIColor(0x007ee5)))
|
||||||
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
||||||
|
self.infoNode.attributedText = string
|
||||||
|
shouldUpdateLayout = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let string = NSAttributedString(string: "\(group.participantCount) members", font: Font.regular(13.0), textColor: UIColor(0x787878))
|
||||||
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
||||||
|
self.infoNode.attributedText = string
|
||||||
|
shouldUpdateLayout = true
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if let channel = peer as? TelegramChannel {
|
} else if let channel = peer as? TelegramChannel {
|
||||||
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
||||||
self.infoNode.attributedText = NSAttributedString(string: "\(memberCount) members", font: Font.regular(13.0), textColor: UIColor(0x787878))
|
let string = NSAttributedString(string: "\(memberCount) members", font: Font.regular(13.0), textColor: UIColor(0x787878))
|
||||||
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
||||||
|
self.infoNode.attributedText = string
|
||||||
|
shouldUpdateLayout = true
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
switch channel.info {
|
switch channel.info {
|
||||||
case .group:
|
case .group:
|
||||||
self.infoNode.attributedText = NSAttributedString(string: "group", font: Font.regular(13.0), textColor: UIColor(0x787878))
|
let string = NSAttributedString(string: "group", font: Font.regular(13.0), textColor: UIColor(0x787878))
|
||||||
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
||||||
|
self.infoNode.attributedText = string
|
||||||
|
shouldUpdateLayout = true
|
||||||
|
}
|
||||||
case .broadcast:
|
case .broadcast:
|
||||||
self.infoNode.attributedText = NSAttributedString(string: "channel", font: Font.regular(13.0), textColor: UIColor(0x787878))
|
let string = NSAttributedString(string: "channel", font: Font.regular(13.0), textColor: UIColor(0x787878))
|
||||||
|
if self.infoNode.attributedText == nil || !self.infoNode.attributedText!.isEqual(to: string) {
|
||||||
|
self.infoNode.attributedText = string
|
||||||
|
shouldUpdateLayout = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldUpdateLayout {
|
||||||
self.setNeedsLayout()
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,10 +125,34 @@ final class ChatTitleView: UIView {
|
|||||||
self.infoNode.truncationMode = .byTruncatingTail
|
self.infoNode.truncationMode = .byTruncatingTail
|
||||||
self.infoNode.isOpaque = false
|
self.infoNode.isOpaque = false
|
||||||
|
|
||||||
|
self.button = HighlightTrackingButton()
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.infoNode)
|
self.addSubnode(self.infoNode)
|
||||||
|
self.addSubview(self.button)
|
||||||
|
|
||||||
|
self.presenceManager = PeerPresenceStatusManager(update: { [weak self] in
|
||||||
|
self?.updateStatus()
|
||||||
|
})
|
||||||
|
|
||||||
|
self.button.addTarget(self, action: #selector(buttonPressed), for: [.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.titleNode.alpha = 0.4
|
||||||
|
strongSelf.infoNode.alpha = 0.4
|
||||||
|
} else {
|
||||||
|
strongSelf.titleNode.alpha = 1.0
|
||||||
|
strongSelf.infoNode.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
@ -63,6 +164,8 @@ final class ChatTitleView: UIView {
|
|||||||
|
|
||||||
let size = self.bounds.size
|
let size = self.bounds.size
|
||||||
|
|
||||||
|
self.button.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
if size.height > 40.0 {
|
if size.height > 40.0 {
|
||||||
let titleSize = self.titleNode.measure(size)
|
let titleSize = self.titleNode.measure(size)
|
||||||
let infoSize = self.infoNode.measure(size)
|
let infoSize = self.infoNode.measure(size)
|
||||||
@ -83,4 +186,10 @@ final class ChatTitleView: UIView {
|
|||||||
self.infoNode.frame = CGRect(origin: CGPoint(x: floor((size.width - combinedWidth) / 2.0 + titleSize.width + titleInfoSpacing), y: floor((size.height - infoSize.height) / 2.0)), size: infoSize)
|
self.infoNode.frame = CGRect(origin: CGPoint(x: floor((size.width - combinedWidth) / 2.0 + titleSize.width + titleInfoSpacing), y: floor((size.height - infoSize.height) / 2.0)), size: infoSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func buttonPressed() {
|
||||||
|
if let pressed = self.pressed {
|
||||||
|
pressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ private func ==(lhs: ContactsControllerEntryId, rhs: ContactsControllerEntryId)
|
|||||||
private enum ContactsEntry: Comparable, Identifiable {
|
private enum ContactsEntry: Comparable, Identifiable {
|
||||||
case search
|
case search
|
||||||
case vcard(Peer)
|
case vcard(Peer)
|
||||||
case peer(Peer)
|
case peer(Peer, PeerPresence?)
|
||||||
|
|
||||||
var stableId: ContactsControllerEntryId {
|
var stableId: ContactsControllerEntryId {
|
||||||
switch self {
|
switch self {
|
||||||
@ -63,10 +63,27 @@ private enum ContactsEntry: Comparable, Identifiable {
|
|||||||
return .search
|
return .search
|
||||||
case .vcard:
|
case .vcard:
|
||||||
return .vcard
|
return .vcard
|
||||||
case let .peer(peer):
|
case let .peer(peer, _):
|
||||||
return .peerId(peer.id.toInt64())
|
return .peerId(peer.id.toInt64())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func item(account: Account, index: PeerNameIndex, interaction: ContactsControllerInteraction) -> ListViewItem {
|
||||||
|
switch self {
|
||||||
|
case .search:
|
||||||
|
return ChatListSearchItem(placeholder: "Search contacts", activate: {
|
||||||
|
interaction.activateSearch()
|
||||||
|
})
|
||||||
|
case let .vcard(peer):
|
||||||
|
return ContactsVCardItem(account: account, peer: peer, action: { peer in
|
||||||
|
interaction.openPeer(peer.id)
|
||||||
|
})
|
||||||
|
case let .peer(peer, presence):
|
||||||
|
return ContactsPeerItem(account: account, peer: peer, presence: presence, index: nil, action: { _ in
|
||||||
|
interaction.openPeer(peer.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func ==(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool {
|
private func ==(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool {
|
||||||
@ -85,10 +102,20 @@ private func ==(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool {
|
|||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .peer(lhsPeer):
|
case let .peer(lhsPeer, lhsPresence):
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case let .peer(rhsPeer):
|
case let .peer(rhsPeer, rhsPresence):
|
||||||
return lhsPeer.id == rhsPeer.id
|
if lhsPeer.id != rhsPeer.id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
|
||||||
|
if !lhsPresence.isEqual(to: rhsPresence) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (lhsPresence != nil) != (rhsPresence != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -96,28 +123,83 @@ private func ==(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func <(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool {
|
private func <(lhs: ContactsEntry, rhs: ContactsEntry) -> Bool {
|
||||||
return lhs.stableId < rhs.stableId
|
switch lhs {
|
||||||
|
case .search:
|
||||||
|
return true
|
||||||
|
case .vcard:
|
||||||
|
switch rhs {
|
||||||
|
case .search, .vcard:
|
||||||
|
return false
|
||||||
|
case .peer:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case let .peer(lhsPeer, lhsPresence):
|
||||||
|
switch rhs {
|
||||||
|
case .search:
|
||||||
|
return false
|
||||||
|
case .vcard:
|
||||||
|
return false
|
||||||
|
case let .peer(rhsPeer, rhsPresence):
|
||||||
|
if let lhsPresence = lhsPresence as? TelegramUserPresence, let rhsPresence = rhsPresence as? TelegramUserPresence {
|
||||||
|
if lhsPresence.status < rhsPresence.status {
|
||||||
|
return false
|
||||||
|
} else if lhsPresence.status > rhsPresence.status {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if let _ = lhsPresence {
|
||||||
|
return true
|
||||||
|
} else if let _ = rhsPresence {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return lhsPeer.id < rhsPeer.id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func entriesForView(_ view: ContactPeersView) -> [ContactsEntry] {
|
private func contactListEntries(_ view: ContactPeersView) -> [ContactsEntry] {
|
||||||
var entries: [ContactsEntry] = []
|
var entries: [ContactsEntry] = []
|
||||||
entries.append(.search)
|
entries.append(.search)
|
||||||
if let peer = view.accountPeer {
|
if let peer = view.accountPeer {
|
||||||
entries.append(.vcard(peer))
|
entries.append(.vcard(peer))
|
||||||
}
|
}
|
||||||
for peer in view.peers {
|
for peer in view.peers {
|
||||||
entries.append(.peer(peer))
|
entries.append(.peer(peer, view.peerPresences[peer.id]))
|
||||||
}
|
}
|
||||||
|
entries.sort()
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct ContactsListTransition {
|
||||||
|
let deletions: [ListViewDeleteItem]
|
||||||
|
let insertions: [ListViewInsertItem]
|
||||||
|
let updates: [ListViewUpdateItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ContactsControllerInteraction {
|
||||||
|
let openPeer: (PeerId) -> Void
|
||||||
|
let activateSearch: () -> Void
|
||||||
|
|
||||||
|
init(openPeer: @escaping (PeerId) -> Void, activateSearch: @escaping () -> Void) {
|
||||||
|
self.openPeer = openPeer
|
||||||
|
self.activateSearch = activateSearch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func preparedContactsListTransition(account: Account, index: PeerNameIndex, from fromEntries: [ContactsEntry], to toEntries: [ContactsEntry], interaction: ContactsControllerInteraction) -> ContactsListTransition {
|
||||||
|
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
|
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||||
|
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, index: index, interaction: interaction), directionHint: nil) }
|
||||||
|
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, index: index, interaction: interaction), directionHint: nil) }
|
||||||
|
|
||||||
|
return ContactsListTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
|
}
|
||||||
|
|
||||||
public class ContactsController: ViewController {
|
public class ContactsController: ViewController {
|
||||||
private let queue = Queue()
|
private let queue = Queue()
|
||||||
|
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let disposable = MetaDisposable()
|
private let transitionDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var entries: [ContactsEntry] = []
|
|
||||||
|
|
||||||
private var contactsNode: ContactsControllerNode {
|
private var contactsNode: ContactsControllerNode {
|
||||||
return self.displayNode as! ContactsControllerNode
|
return self.displayNode as! ContactsControllerNode
|
||||||
@ -125,6 +207,14 @@ public class ContactsController: ViewController {
|
|||||||
|
|
||||||
private let index: PeerNameIndex = .lastNameFirst
|
private let index: PeerNameIndex = .lastNameFirst
|
||||||
|
|
||||||
|
private var _ready = Promise<Bool>()
|
||||||
|
override public var ready: Promise<Bool> {
|
||||||
|
return self._ready
|
||||||
|
}
|
||||||
|
private var didSetReady = false
|
||||||
|
|
||||||
|
private let previousEntries = Atomic<[ContactsEntry]?>(value: nil)
|
||||||
|
|
||||||
public init(account: Account) {
|
public init(account: Account) {
|
||||||
self.account = account
|
self.account = account
|
||||||
|
|
||||||
@ -135,12 +225,8 @@ public class ContactsController: ViewController {
|
|||||||
self.tabBarItem.image = UIImage(bundleImageName: "Chat List/Tabs/IconContacts")
|
self.tabBarItem.image = UIImage(bundleImageName: "Chat List/Tabs/IconContacts")
|
||||||
self.tabBarItem.selectedImage = UIImage(bundleImageName: "Chat List/Tabs/IconContactsSelected")
|
self.tabBarItem.selectedImage = UIImage(bundleImageName: "Chat List/Tabs/IconContactsSelected")
|
||||||
|
|
||||||
self.disposable.set((account.postbox.contactPeersView(index: self.index, accountPeerId: account.peerId) |> deliverOn(self.queue)).start(next: { [weak self] view in
|
|
||||||
self?.updateView(view)
|
|
||||||
}))
|
|
||||||
|
|
||||||
self.scrollToTop = { [weak self] in
|
self.scrollToTop = { [weak self] in
|
||||||
if let strongSelf = self, !strongSelf.entries.isEmpty {
|
if let strongSelf = self {
|
||||||
strongSelf.contactsNode.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .Top, animated: true, curve: .Default, directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, completion: { _ in })
|
strongSelf.contactsNode.listView.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .Top, animated: true, curve: .Default, directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, completion: { _ in })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,7 +237,7 @@ public class ContactsController: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.disposable.dispose()
|
self.transitionDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func loadDisplayNode() {
|
override public func loadDisplayNode() {
|
||||||
@ -172,69 +258,62 @@ public class ContactsController: ViewController {
|
|||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func viewWillAppear(_ animated: Bool) {
|
||||||
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
let interaction = ContactsControllerInteraction(openPeer: { [weak self] peerId in
|
||||||
|
if let strongSelf = self {
|
||||||
|
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, peerId: peerId))
|
||||||
|
}
|
||||||
|
}, activateSearch: { [weak self] in
|
||||||
|
self?.activateSearch()
|
||||||
|
})
|
||||||
|
|
||||||
|
let account = self.account
|
||||||
|
let index = self.index
|
||||||
|
let previousEntries = self.previousEntries
|
||||||
|
let transition = account.postbox.contactPeersView(index: self.index, accountPeerId: account.peerId)
|
||||||
|
|> map { view -> (ContactsListTransition, Bool, Bool) in
|
||||||
|
let entries = contactListEntries(view)
|
||||||
|
let previous = previousEntries.swap(entries)
|
||||||
|
return (preparedContactsListTransition(account: account, index: index, from: previous ?? [], to: entries, interaction: interaction), previous == nil, previous != nil)
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|
||||||
|
self.transitionDisposable.set(transition.start(next: { [weak self] (transition, firstTime, animated) in
|
||||||
|
self?.enqueueTransition(transition, firstTime: firstTime, animated: animated)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func viewDidDisappear(_ animated: Bool) {
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
|
self.transitionDisposable.set(nil)
|
||||||
|
}
|
||||||
|
|
||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
self.contactsNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationBar.frame.maxY, transition: transition)
|
self.contactsNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationBar.frame.maxY, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateView(_ view: ContactPeersView) {
|
private func enqueueTransition(_ transition: ContactsListTransition, firstTime: Bool, animated: Bool) {
|
||||||
assert(self.queue.isCurrent())
|
var options = ListViewDeleteAndInsertOptions()
|
||||||
|
if firstTime {
|
||||||
let previousEntries = self.entries
|
options.insert(.Synchronous)
|
||||||
let updatedEntries = entriesForView(view)
|
options.insert(.LowLatency)
|
||||||
|
} else if animated {
|
||||||
let (deleteIndices, indicesAndItems) = mergeListsStable(leftList: previousEntries, rightList: updatedEntries)
|
options.insert(.AnimateInsertion)
|
||||||
|
|
||||||
self.entries = updatedEntries
|
|
||||||
|
|
||||||
var adjustedDeleteIndices: [ListViewDeleteItem] = []
|
|
||||||
if deleteIndices.count != 0 {
|
|
||||||
for index in deleteIndices {
|
|
||||||
adjustedDeleteIndices.append(ListViewDeleteItem(index: index, directionHint: nil))
|
|
||||||
}
|
}
|
||||||
}
|
self.contactsNode.listView.deleteAndInsertItems(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, completion: { [weak self] _ in
|
||||||
|
|
||||||
var adjustedIndicesAndItems: [ListViewInsertItem] = []
|
|
||||||
for (index, entry, previousIndex) in indicesAndItems {
|
|
||||||
switch entry {
|
|
||||||
case .search:
|
|
||||||
adjustedIndicesAndItems.append(ListViewInsertItem(index: index, previousIndex: previousIndex, item: ChatListSearchItem(placeholder: "Search contacts", activate: { [weak self] in
|
|
||||||
self?.activateSearch()
|
|
||||||
}), directionHint: nil))
|
|
||||||
case let .vcard(peer):
|
|
||||||
adjustedIndicesAndItems.append(ListViewInsertItem(index: index, previousIndex: previousIndex, item: ContactsVCardItem(account: self.account, peer: peer, action: { [weak self] _ in
|
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.entrySelected(entry)
|
if !strongSelf.didSetReady {
|
||||||
strongSelf.contactsNode.listView.clearHighlightAnimated(true)
|
strongSelf.didSetReady = true
|
||||||
}
|
strongSelf._ready.set(.single(true))
|
||||||
}), directionHint: nil))
|
|
||||||
case let .peer(peer):
|
|
||||||
adjustedIndicesAndItems.append(ListViewInsertItem(index: index, previousIndex: previousIndex, item: ContactsPeerItem(account: self.account, peer: peer, index: self.index, action: { [weak self] _ in
|
|
||||||
if let strongSelf = self {
|
|
||||||
strongSelf.entrySelected(entry)
|
|
||||||
strongSelf.contactsNode.listView.clearHighlightAnimated(true)
|
|
||||||
}
|
|
||||||
}), directionHint: nil))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
let options: ListViewDeleteAndInsertOptions = []
|
|
||||||
|
|
||||||
self.contactsNode.listView.deleteAndInsertItems(deleteIndices: adjustedDeleteIndices, insertIndicesAndItems: adjustedIndicesAndItems, updateIndicesAndItems: [], options: options, scrollToItem: nil, completion: { _ in
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private func entrySelected(_ entry: ContactsEntry) {
|
|
||||||
if case let .peer(peer) = entry {
|
|
||||||
(self.navigationController as? NavigationController)?.pushViewController(ChatController(account: self.account, peerId: peer.id))
|
|
||||||
}
|
|
||||||
if case let .vcard(peer) = entry {
|
|
||||||
(self.navigationController as? NavigationController)?.pushViewController(ChatController(account: self.account, peerId: peer.id))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func activateSearch() {
|
private func activateSearch() {
|
||||||
if self.displayNavigationBar {
|
if self.displayNavigationBar {
|
||||||
|
@ -13,14 +13,16 @@ private let statusFont = Font.regular(13.0)
|
|||||||
class ContactsPeerItem: ListViewItem {
|
class ContactsPeerItem: ListViewItem {
|
||||||
let account: Account
|
let account: Account
|
||||||
let peer: Peer
|
let peer: Peer
|
||||||
|
let presence: PeerPresence?
|
||||||
let action: (Peer) -> Void
|
let action: (Peer) -> Void
|
||||||
let selectable: Bool = true
|
let selectable: Bool = true
|
||||||
|
|
||||||
let headerAccessoryItem: ListViewAccessoryItem?
|
let headerAccessoryItem: ListViewAccessoryItem?
|
||||||
|
|
||||||
init(account: Account, peer: Peer, index: PeerNameIndex?, action: @escaping (Peer) -> Void) {
|
init(account: Account, peer: Peer, presence: PeerPresence?, index: PeerNameIndex?, action: @escaping (Peer) -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
self.presence = presence
|
||||||
self.action = action
|
self.action = action
|
||||||
|
|
||||||
if let index = index {
|
if let index = index {
|
||||||
@ -72,7 +74,7 @@ class ContactsPeerItem: ListViewItem {
|
|||||||
last = false
|
last = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (nodeLayout, nodeApply) = makeLayout(self.account, self.peer, width, first, last)
|
let (nodeLayout, nodeApply) = makeLayout(self, width, first, last)
|
||||||
node.contentSize = nodeLayout.contentSize
|
node.contentSize = nodeLayout.contentSize
|
||||||
node.insets = nodeLayout.insets
|
node.insets = nodeLayout.insets
|
||||||
|
|
||||||
@ -101,7 +103,7 @@ class ContactsPeerItem: ListViewItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (nodeLayout, apply) = layout(self.account, self.peer, width, first, last)
|
let (nodeLayout, apply) = layout(self, width, first, last)
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(nodeLayout, {
|
completion(nodeLayout, {
|
||||||
apply()
|
apply()
|
||||||
@ -120,6 +122,7 @@ class ContactsPeerItem: ListViewItem {
|
|||||||
private let separatorHeight = 1.0 / UIScreen.main.scale
|
private let separatorHeight = 1.0 / UIScreen.main.scale
|
||||||
|
|
||||||
class ContactsPeerItemNode: ListViewItemNode {
|
class ContactsPeerItemNode: ListViewItemNode {
|
||||||
|
private let backgroundNode: ASDisplayNode
|
||||||
private let separatorNode: ASDisplayNode
|
private let separatorNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
|
||||||
@ -127,11 +130,16 @@ class ContactsPeerItemNode: ListViewItemNode {
|
|||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let statusNode: TextNode
|
private let statusNode: TextNode
|
||||||
|
|
||||||
private var account: Account?
|
|
||||||
private var peer: Peer?
|
|
||||||
private var avatarState: (Account, Peer)?
|
private var avatarState: (Account, Peer)?
|
||||||
|
|
||||||
|
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||||
|
private var layoutParams: (ContactsPeerItem, CGFloat, Bool, Bool)?
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
|
self.backgroundNode = ASDisplayNode()
|
||||||
|
self.backgroundNode.backgroundColor = .white
|
||||||
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
|
||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
self.separatorNode.backgroundColor = UIColor(0xc8c7cc)
|
self.separatorNode.backgroundColor = UIColor(0xc8c7cc)
|
||||||
self.separatorNode.isLayerBacked = true
|
self.separatorNode.isLayerBacked = true
|
||||||
@ -148,19 +156,30 @@ class ContactsPeerItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.separatorNode)
|
self.addSubnode(self.separatorNode)
|
||||||
self.addSubnode(self.avatarNode)
|
self.addSubnode(self.avatarNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.statusNode)
|
self.addSubnode(self.statusNode)
|
||||||
|
|
||||||
|
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
|
||||||
|
if let strongSelf = self, let layoutParams = strongSelf.layoutParams {
|
||||||
|
let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.1, layoutParams.2, layoutParams.3)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutForWidth(_ width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
override func layoutForWidth(_ width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
||||||
|
if let (item, _, _, _) = self.layoutParams {
|
||||||
|
self.layoutParams = (item, width, previousItem != nil, nextItem != nil)
|
||||||
let makeLayout = self.asyncLayout()
|
let makeLayout = self.asyncLayout()
|
||||||
let (nodeLayout, nodeApply) = makeLayout(self.account, self.peer, width, previousItem != nil, nextItem != nil)
|
let (nodeLayout, nodeApply) = makeLayout(item, width, previousItem != nil, nextItem != nil)
|
||||||
self.contentSize = nodeLayout.contentSize
|
self.contentSize = nodeLayout.contentSize
|
||||||
self.insets = nodeLayout.insets
|
self.insets = nodeLayout.insets
|
||||||
nodeApply()
|
nodeApply()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
||||||
super.setHighlighted(highlighted, animated: animated)
|
super.setHighlighted(highlighted, animated: animated)
|
||||||
@ -198,18 +217,19 @@ class ContactsPeerItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ account: Account?, _ peer: Peer?, _ width: CGFloat, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: ContactsPeerItem, _ width: CGFloat, _ first: Bool, _ last: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||||
|
|
||||||
return { [weak self] account, peer, width, first, last in
|
return { [weak self] item, width, first, last in
|
||||||
let leftInset: CGFloat = 65.0
|
let leftInset: CGFloat = 65.0
|
||||||
let rightInset: CGFloat = 10.0
|
let rightInset: CGFloat = 10.0
|
||||||
|
|
||||||
var titleAttributedString: NSAttributedString?
|
var titleAttributedString: NSAttributedString?
|
||||||
var statusAttributedString: NSAttributedString?
|
var statusAttributedString: NSAttributedString?
|
||||||
|
|
||||||
if let peer = peer {
|
let peer = item.peer
|
||||||
|
|
||||||
if let user = peer as? TelegramUser {
|
if let user = peer as? TelegramUser {
|
||||||
if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
|
if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
|
||||||
let string = NSMutableAttributedString()
|
let string = NSMutableAttributedString()
|
||||||
@ -225,13 +245,18 @@ class ContactsPeerItemNode: ListViewItemNode {
|
|||||||
titleAttributedString = NSAttributedString(string: "Deleted User", font: titleBoldFont, textColor: UIColor(0xa6a6a6))
|
titleAttributedString = NSAttributedString(string: "Deleted User", font: titleBoldFont, textColor: UIColor(0xa6a6a6))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let presence = item.presence as? TelegramUserPresence {
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||||
|
let (string, activity) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp))
|
||||||
|
statusAttributedString = NSAttributedString(string: string, font: statusFont, textColor: activity ? UIColor(0x007ee5) : UIColor(0xa6a6a6))
|
||||||
|
} else {
|
||||||
statusAttributedString = NSAttributedString(string: "last seen recently", font: statusFont, textColor: UIColor(0xa6a6a6))
|
statusAttributedString = NSAttributedString(string: "last seen recently", font: statusFont, textColor: UIColor(0xa6a6a6))
|
||||||
|
}
|
||||||
} else if let group = peer as? TelegramGroup {
|
} else if let group = peer as? TelegramGroup {
|
||||||
titleAttributedString = NSAttributedString(string: group.title, font: titleBoldFont, textColor: UIColor.black)
|
titleAttributedString = NSAttributedString(string: group.title, font: titleBoldFont, textColor: UIColor.black)
|
||||||
} else if let channel = peer as? TelegramChannel {
|
} else if let channel = peer as? TelegramChannel {
|
||||||
titleAttributedString = NSAttributedString(string: channel.title, font: titleBoldFont, textColor: UIColor.black)
|
titleAttributedString = NSAttributedString(string: channel.title, font: titleBoldFont, textColor: UIColor.black)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(titleAttributedString, nil, 1, .end, CGSize(width: max(0.0, width - leftInset - rightInset), height: CGFloat.infinity), nil)
|
let (titleLayout, titleApply) = makeTitleLayout(titleAttributedString, nil, 1, .end, CGSize(width: max(0.0, width - leftInset - rightInset), height: CGFloat.infinity), nil)
|
||||||
|
|
||||||
@ -241,11 +266,10 @@ class ContactsPeerItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
return (nodeLayout, { [weak self] in
|
return (nodeLayout, { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.peer = peer
|
strongSelf.layoutParams = (item, width, first, last)
|
||||||
strongSelf.account = account
|
|
||||||
|
|
||||||
if let peer = peer, let account = account, strongSelf.avatarState == nil || strongSelf.avatarState!.0 !== account || !strongSelf.avatarState!.1.isEqual(peer) {
|
if strongSelf.avatarState == nil || strongSelf.avatarState!.0 !== item.account || !strongSelf.avatarState!.1.isEqual(peer) {
|
||||||
strongSelf.avatarNode.setPeer(account: account, peer: peer)
|
strongSelf.avatarNode.setPeer(account: item.account, peer: item.peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 14.0, y: 4.0), size: CGSize(width: 40.0, height: 40.0))
|
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 14.0, y: 4.0), size: CGSize(width: 40.0, height: 40.0))
|
||||||
@ -257,9 +281,14 @@ class ContactsPeerItemNode: ListViewItemNode {
|
|||||||
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 25.0), size: statusLayout.size)
|
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 25.0), size: statusLayout.size)
|
||||||
|
|
||||||
let topHighlightInset: CGFloat = first ? 0.0 : separatorHeight
|
let topHighlightInset: CGFloat = first ? 0.0 : separatorHeight
|
||||||
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: nodeLayout.contentSize.width, height: nodeLayout.contentSize.height))
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: nodeLayout.size.width, height: nodeLayout.size.height + topHighlightInset))
|
||||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 65.0, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: max(0.0, nodeLayout.size.width - 65.0), height: separatorHeight))
|
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 65.0, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: max(0.0, nodeLayout.size.width - 65.0), height: separatorHeight))
|
||||||
strongSelf.separatorNode.isHidden = last
|
strongSelf.separatorNode.isHidden = last
|
||||||
|
|
||||||
|
if let presence = item.presence as? TelegramUserPresence {
|
||||||
|
strongSelf.peerPresenceManager?.reset(presence: presence)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -269,4 +298,12 @@ class ContactsPeerItemNode: ListViewItemNode {
|
|||||||
let bounds = self.bounds
|
let bounds = self.bounds
|
||||||
accessoryItemNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -29.0), size: CGSize(width: bounds.size.width, height: 29.0))
|
accessoryItemNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -29.0), size: CGSize(width: bounds.size.width, height: 29.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration * 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration * 0.5, removeOnCompletion: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
|
|||||||
for item in items {
|
for item in items {
|
||||||
switch item {
|
switch item {
|
||||||
case let .peer(peer):
|
case let .peer(peer):
|
||||||
listItems.append(ContactsPeerItem(account: account, peer: peer, index: nil, action: { [weak self] peer in
|
listItems.append(ContactsPeerItem(account: account, peer: peer, presence: nil, index: nil, action: { [weak self] peer in
|
||||||
if let openPeer = self?.openPeer {
|
if let openPeer = self?.openPeer {
|
||||||
self?.listNode.clearHighlightAnimated(true)
|
self?.listNode.clearHighlightAnimated(true)
|
||||||
openPeer(peer.id)
|
openPeer(peer.id)
|
||||||
|
@ -215,7 +215,7 @@ class GalleryController: ViewController {
|
|||||||
strongSelf.statusBar.style = .Black
|
strongSelf.statusBar.style = .Black
|
||||||
strongSelf.navigationBar.backgroundColor = UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0)
|
strongSelf.navigationBar.backgroundColor = UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0)
|
||||||
strongSelf.navigationBar.foregroundColor = UIColor.black
|
strongSelf.navigationBar.foregroundColor = UIColor.black
|
||||||
strongSelf.navigationBar.accentColor = UIColor(0x1195f2)
|
strongSelf.navigationBar.accentColor = UIColor(0x007ee5)
|
||||||
strongSelf.navigationBar.stripeColor = UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0)
|
strongSelf.navigationBar.stripeColor = UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0)
|
||||||
strongSelf.galleryNode.backgroundNode.backgroundColor = UIColor(0xbdbdc2)
|
strongSelf.galleryNode.backgroundNode.backgroundColor = UIColor(0xbdbdc2)
|
||||||
strongSelf.galleryNode.isBackgroundExtendedOverNavigationBar = false
|
strongSelf.galleryNode.isBackgroundExtendedOverNavigationBar = false
|
||||||
|
@ -28,6 +28,27 @@ private enum GroupInfoSection: UInt32, PeerInfoSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum GroupInfoMemberStatus {
|
||||||
|
case member
|
||||||
|
case admin
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct GroupPeerEntryStableId: PeerInfoEntryStableId {
|
||||||
|
let peerId: PeerId
|
||||||
|
|
||||||
|
func isEqual(to: PeerInfoEntryStableId) -> Bool {
|
||||||
|
if let to = to as? GroupPeerEntryStableId, to.peerId == self.peerId {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashValue: Int {
|
||||||
|
return self.peerId.hashValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum GroupInfoEntry: PeerInfoEntry {
|
enum GroupInfoEntry: PeerInfoEntry {
|
||||||
case info(peer: Peer?, cachedData: CachedPeerData?)
|
case info(peer: Peer?, cachedData: CachedPeerData?)
|
||||||
case setGroupPhoto
|
case setGroupPhoto
|
||||||
@ -37,7 +58,7 @@ enum GroupInfoEntry: PeerInfoEntry {
|
|||||||
case notifications(settings: PeerNotificationSettings?)
|
case notifications(settings: PeerNotificationSettings?)
|
||||||
case usersHeader
|
case usersHeader
|
||||||
case addMember
|
case addMember
|
||||||
case member(index: Int, peer: Peer?)
|
case member(index: Int, peerId: PeerId, peer: Peer?, presence: PeerPresence?, memberStatus: GroupInfoMemberStatus)
|
||||||
case leave
|
case leave
|
||||||
|
|
||||||
var section: PeerInfoSection {
|
var section: PeerInfoSection {
|
||||||
@ -132,11 +153,17 @@ enum GroupInfoEntry: PeerInfoEntry {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .member(lhsIndex, lhsPeer):
|
case let .member(lhsIndex, lhsPeerId, lhsPeer, lhsPresence, lhsMemberStatus):
|
||||||
if case let .member(rhsIndex, rhsPeer) = entry {
|
if case let .member(rhsIndex, rhsPeerId, rhsPeer, rhsPresence, rhsMemberStatus) = entry {
|
||||||
if lhsIndex != rhsIndex {
|
if lhsIndex != rhsIndex {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhsMemberStatus != rhsMemberStatus {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhsPeerId != rhsPeerId {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer {
|
if let lhsPeer = lhsPeer, let rhsPeer = rhsPeer {
|
||||||
if !lhsPeer.isEqual(rhsPeer) {
|
if !lhsPeer.isEqual(rhsPeer) {
|
||||||
return false
|
return false
|
||||||
@ -144,6 +171,13 @@ enum GroupInfoEntry: PeerInfoEntry {
|
|||||||
} else if (lhsPeer != nil) != (rhsPeer != nil) {
|
} else if (lhsPeer != nil) != (rhsPeer != nil) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
|
||||||
|
if !lhsPresence.isEqual(to: rhsPresence) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (lhsPresence != nil) != (rhsPresence != nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@ -157,8 +191,13 @@ enum GroupInfoEntry: PeerInfoEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var stableId: Int {
|
var stableId: PeerInfoEntryStableId {
|
||||||
return self.sortIndex
|
switch self {
|
||||||
|
case let .member(_, peerId, _, _, _):
|
||||||
|
return GroupPeerEntryStableId(peerId: peerId)
|
||||||
|
default:
|
||||||
|
return IntPeerInfoEntryStableId(value: self.sortIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var sortIndex: Int {
|
private var sortIndex: Int {
|
||||||
@ -179,7 +218,7 @@ enum GroupInfoEntry: PeerInfoEntry {
|
|||||||
return 6
|
return 6
|
||||||
case .addMember:
|
case .addMember:
|
||||||
return 7
|
return 7
|
||||||
case let .member(index, _):
|
case let .member(index, _, _, _, _):
|
||||||
return 10 + index
|
return 10 + index
|
||||||
case .leave:
|
case .leave:
|
||||||
return 1000000
|
return 1000000
|
||||||
@ -202,7 +241,14 @@ enum GroupInfoEntry: PeerInfoEntry {
|
|||||||
return PeerInfoActionItem(title: "Set Group Photo", kind: .generic, alignment: .natural, sectionId: self.section.rawValue, style: .blocks, action: {
|
return PeerInfoActionItem(title: "Set Group Photo", kind: .generic, alignment: .natural, sectionId: self.section.rawValue, style: .blocks, action: {
|
||||||
})
|
})
|
||||||
case let .notifications(settings):
|
case let .notifications(settings):
|
||||||
return PeerInfoDisclosureItem(title: "Notifications", label: "Enabled", sectionId: self.section.rawValue, style: .blocks, action: {
|
let label: String
|
||||||
|
if let settings = settings as? TelegramPeerNotificationSettings, case .muted = settings.muteState {
|
||||||
|
label = "Disabled"
|
||||||
|
} else {
|
||||||
|
label = "Enabled"
|
||||||
|
}
|
||||||
|
return PeerInfoDisclosureItem(title: "Notifications", label: label, sectionId: self.section.rawValue, style: .blocks, action: {
|
||||||
|
interaction.changeNotificationNoteSettings()
|
||||||
})
|
})
|
||||||
case .sharedMedia:
|
case .sharedMedia:
|
||||||
return PeerInfoDisclosureItem(title: "Shared Media", label: "", sectionId: self.section.rawValue, style: .blocks, action: {
|
return PeerInfoDisclosureItem(title: "Shared Media", label: "", sectionId: self.section.rawValue, style: .blocks, action: {
|
||||||
@ -212,8 +258,15 @@ enum GroupInfoEntry: PeerInfoEntry {
|
|||||||
return PeerInfoPeerActionItem(icon: addMemberPlusIcon, title: "Add Member", sectionId: self.section.rawValue, action: {
|
return PeerInfoPeerActionItem(icon: addMemberPlusIcon, title: "Add Member", sectionId: self.section.rawValue, action: {
|
||||||
|
|
||||||
})
|
})
|
||||||
case let .member(_, peer):
|
case let .member(_, _, peer, presence, memberStatus):
|
||||||
return PeerInfoPeerItem(account: account, peer: peer, sectionId: self.section.rawValue, action: {
|
let label: String?
|
||||||
|
switch memberStatus {
|
||||||
|
case .admin:
|
||||||
|
label = "admin"
|
||||||
|
case .member:
|
||||||
|
label = nil
|
||||||
|
}
|
||||||
|
return PeerInfoPeerItem(account: account, peer: peer, presence: presence, label: label, sectionId: self.section.rawValue, action: {
|
||||||
if let peer = peer {
|
if let peer = peer {
|
||||||
interaction.openPeerInfo(peer.id)
|
interaction.openPeerInfo(peer.id)
|
||||||
}
|
}
|
||||||
@ -230,15 +283,58 @@ enum GroupInfoEntry: PeerInfoEntry {
|
|||||||
func groupInfoEntries(view: PeerView) -> [PeerInfoEntry] {
|
func groupInfoEntries(view: PeerView) -> [PeerInfoEntry] {
|
||||||
var entries: [PeerInfoEntry] = []
|
var entries: [PeerInfoEntry] = []
|
||||||
entries.append(GroupInfoEntry.info(peer: view.peers[view.peerId], cachedData: view.cachedData))
|
entries.append(GroupInfoEntry.info(peer: view.peers[view.peerId], cachedData: view.cachedData))
|
||||||
|
|
||||||
|
var highlightAdmins = false
|
||||||
|
var canManageGroup = false
|
||||||
|
if let group = view.peers[view.peerId] as? TelegramGroup {
|
||||||
|
if group.flags.contains(.adminsEnabled) {
|
||||||
|
highlightAdmins = true
|
||||||
|
switch group.role {
|
||||||
|
case .admin, .creator:
|
||||||
|
canManageGroup = true
|
||||||
|
case .member:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
canManageGroup = true
|
||||||
|
}
|
||||||
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
||||||
|
highlightAdmins = true
|
||||||
|
switch channel.role {
|
||||||
|
case .creator:
|
||||||
|
canManageGroup = true
|
||||||
|
case .editor, .moderator, .member:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if canManageGroup {
|
||||||
entries.append(GroupInfoEntry.setGroupPhoto)
|
entries.append(GroupInfoEntry.setGroupPhoto)
|
||||||
|
}
|
||||||
|
|
||||||
entries.append(GroupInfoEntry.notifications(settings: view.notificationSettings))
|
entries.append(GroupInfoEntry.notifications(settings: view.notificationSettings))
|
||||||
entries.append(GroupInfoEntry.sharedMedia)
|
entries.append(GroupInfoEntry.sharedMedia)
|
||||||
|
|
||||||
|
if canManageGroup {
|
||||||
entries.append(GroupInfoEntry.addMember)
|
entries.append(GroupInfoEntry.addMember)
|
||||||
|
}
|
||||||
|
|
||||||
if let cachedGroupData = view.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
|
if let cachedGroupData = view.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
|
||||||
let sortedParticipants = participants.participants.sorted(by: { lhs, rhs in
|
let sortedParticipants = participants.participants.sorted(by: { lhs, rhs in
|
||||||
|
let lhsPresence = view.peerPresences[lhs.peerId] as? TelegramUserPresence
|
||||||
|
let rhsPresence = view.peerPresences[rhs.peerId] as? TelegramUserPresence
|
||||||
|
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
|
||||||
|
if lhsPresence.status < rhsPresence.status {
|
||||||
|
return false
|
||||||
|
} else if lhsPresence.status > rhsPresence.status {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if let _ = lhsPresence {
|
||||||
|
return true
|
||||||
|
} else if let _ = rhsPresence {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case .creator:
|
case .creator:
|
||||||
return false
|
return false
|
||||||
@ -278,7 +374,18 @@ func groupInfoEntries(view: PeerView) -> [PeerInfoEntry] {
|
|||||||
|
|
||||||
for i in 0 ..< sortedParticipants.count {
|
for i in 0 ..< sortedParticipants.count {
|
||||||
if let peer = view.peers[sortedParticipants[i].peerId] {
|
if let peer = view.peers[sortedParticipants[i].peerId] {
|
||||||
entries.append(GroupInfoEntry.member(index: i, peer: peer))
|
let memberStatus: GroupInfoMemberStatus
|
||||||
|
if highlightAdmins {
|
||||||
|
switch sortedParticipants[i] {
|
||||||
|
case .admin, .creator:
|
||||||
|
memberStatus = .admin
|
||||||
|
case .member:
|
||||||
|
memberStatus = .member
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memberStatus = .member
|
||||||
|
}
|
||||||
|
entries.append(GroupInfoEntry.member(index: i, peerId: peer.id, peer: peer, presence: view.peerPresences[peer.id], memberStatus: memberStatus))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -287,6 +394,10 @@ func groupInfoEntries(view: PeerView) -> [PeerInfoEntry] {
|
|||||||
if case .Member = group.membership {
|
if case .Member = group.membership {
|
||||||
entries.append(GroupInfoEntry.leave)
|
entries.append(GroupInfoEntry.leave)
|
||||||
}
|
}
|
||||||
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel {
|
||||||
|
if case .member = channel.participationStatus {
|
||||||
|
entries.append(GroupInfoEntry.leave)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
|
@ -108,11 +108,11 @@ private let titleFont = Font.medium(16.0)
|
|||||||
private let descriptionFont = Font.regular(13.0)
|
private let descriptionFont = Font.regular(13.0)
|
||||||
private let extensionFont = Font.medium(13.0)
|
private let extensionFont = Font.medium(13.0)
|
||||||
|
|
||||||
private let downloadFileStartIcon = generateTintedImage(image: UIImage(bundleImageName: "List Menu/ListDownloadStartIcon"), color: UIColor(0x1195f2))
|
private let downloadFileStartIcon = generateTintedImage(image: UIImage(bundleImageName: "List Menu/ListDownloadStartIcon"), color: UIColor(0x007ee5))
|
||||||
private let downloadFilePauseIcon = generateImage(CGSize(width: 11.0, height: 11.0), contextGenerator: { size, context in
|
private let downloadFilePauseIcon = generateImage(CGSize(width: 11.0, height: 11.0), contextGenerator: { size, context in
|
||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setFillColor(UIColor(0x1195f2).cgColor)
|
context.setFillColor(UIColor(0x007ee5).cgColor)
|
||||||
|
|
||||||
context.fill(CGRect(x: 2.0, y: 0.0, width: 2.0, height: 11.0 - 1.0))
|
context.fill(CGRect(x: 2.0, y: 0.0, width: 2.0, height: 11.0 - 1.0))
|
||||||
context.fill(CGRect(x: 2.0 + 2.0 + 2.0, y: 0.0, width: 2.0, height: 11.0 - 1.0))
|
context.fill(CGRect(x: 2.0 + 2.0 + 2.0, y: 0.0, width: 2.0, height: 11.0 - 1.0))
|
||||||
@ -177,7 +177,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
|||||||
self.downloadStatusIconNode.displayWithoutProcessing = true
|
self.downloadStatusIconNode.displayWithoutProcessing = true
|
||||||
|
|
||||||
self.progressNode = ASDisplayNode()
|
self.progressNode = ASDisplayNode()
|
||||||
self.progressNode.backgroundColor = UIColor(0x1195f2)
|
self.progressNode.backgroundColor = UIColor(0x007ee5)
|
||||||
self.progressNode.isLayerBacked = true
|
self.progressNode.isLayerBacked = true
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
@ -134,7 +134,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
|
|||||||
mutableDescriptionText.append(NSAttributedString(string: text + "\n", font: descriptionFont, textColor: UIColor.black))
|
mutableDescriptionText.append(NSAttributedString(string: text + "\n", font: descriptionFont, textColor: UIColor.black))
|
||||||
}
|
}
|
||||||
|
|
||||||
mutableDescriptionText.append(NSAttributedString(string: content.displayUrl, font: descriptionFont, textColor: UIColor(0x1195f2)))
|
mutableDescriptionText.append(NSAttributedString(string: content.displayUrl, font: descriptionFont, textColor: UIColor(0x007ee5)))
|
||||||
|
|
||||||
let style = NSMutableParagraphStyle()
|
let style = NSMutableParagraphStyle()
|
||||||
style.lineSpacing = 4.0
|
style.lineSpacing = 4.0
|
||||||
|
@ -77,7 +77,7 @@ class PeerInfoActionItemNode: ListViewItemNode {
|
|||||||
private let bottomStripeNode: ASDisplayNode
|
private let bottomStripeNode: ASDisplayNode
|
||||||
private let highlightedBackgroundNode: ASDisplayNode
|
private let highlightedBackgroundNode: ASDisplayNode
|
||||||
|
|
||||||
let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
@ -112,7 +112,7 @@ class PeerInfoActionItemNode: ListViewItemNode {
|
|||||||
return { item, width, neighbors in
|
return { item, width, neighbors in
|
||||||
let sectionInset: CGFloat = 22.0
|
let sectionInset: CGFloat = 22.0
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(NSAttributedString(string: item.title, font: titleFont, textColor: item.kind == .destructive ? UIColor(0xff3b30) : UIColor(0x1195f2)), nil, 1, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), nil)
|
let (titleLayout, titleApply) = makeTitleLayout(NSAttributedString(string: item.title, font: titleFont, textColor: item.kind == .destructive ? UIColor(0xff3b30) : UIColor(0x007ee5)), nil, 1, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
|
||||||
let contentSize: CGSize
|
let contentSize: CGSize
|
||||||
let insets: UIEdgeInsets
|
let insets: UIEdgeInsets
|
||||||
@ -245,4 +245,18 @@ class PeerInfoActionItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.topStripeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.bottomStripeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.topStripeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.bottomStripeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.titleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ class PeerInfoAvatarAndNameItemNode: ListViewItemNode {
|
|||||||
let statusColor: UIColor
|
let statusColor: UIColor
|
||||||
if let user = item.peer as? TelegramUser {
|
if let user = item.peer as? TelegramUser {
|
||||||
statusText = "online"
|
statusText = "online"
|
||||||
statusColor = UIColor(0x1195f2)
|
statusColor = UIColor(0x007ee5)
|
||||||
} else if let channel = item.peer as? TelegramChannel {
|
} else if let channel = item.peer as? TelegramChannel {
|
||||||
if let cachedChannelData = item.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
if let cachedChannelData = item.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
||||||
statusText = "\(memberCount) members"
|
statusText = "\(memberCount) members"
|
||||||
@ -163,8 +163,11 @@ class PeerInfoAvatarAndNameItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
return (layout, { [weak self] in
|
return (layout, { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
let avatarOriginY: CGFloat
|
||||||
switch item.style {
|
switch item.style {
|
||||||
case .plain:
|
case .plain:
|
||||||
|
avatarOriginY = 15.0
|
||||||
|
|
||||||
if strongSelf.backgroundNode.supernode != nil {
|
if strongSelf.backgroundNode.supernode != nil {
|
||||||
strongSelf.backgroundNode.removeFromSupernode()
|
strongSelf.backgroundNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
@ -175,6 +178,8 @@ class PeerInfoAvatarAndNameItemNode: ListViewItemNode {
|
|||||||
strongSelf.bottomStripeNode.removeFromSupernode()
|
strongSelf.bottomStripeNode.removeFromSupernode()
|
||||||
}
|
}
|
||||||
case .blocks:
|
case .blocks:
|
||||||
|
avatarOriginY = 13.0
|
||||||
|
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
if strongSelf.backgroundNode.supernode == nil {
|
||||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||||
}
|
}
|
||||||
@ -211,7 +216,7 @@ class PeerInfoAvatarAndNameItemNode: ListViewItemNode {
|
|||||||
strongSelf.avatarNode.setPeer(account: item.account, peer: peer)
|
strongSelf.avatarNode.setPeer(account: item.account, peer: peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 15.0, y: 15.0), size: CGSize(width: 66.0, height: 66.0))
|
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 15.0, y: avatarOriginY), size: CGSize(width: 66.0, height: 66.0))
|
||||||
strongSelf.nameNode.frame = CGRect(origin: CGPoint(x: 94.0, y: 25.0), size: nameNodeLayout.size)
|
strongSelf.nameNode.frame = CGRect(origin: CGPoint(x: 94.0, y: 25.0), size: nameNodeLayout.size)
|
||||||
|
|
||||||
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: 94.0, y: 25.0 + nameNodeLayout.size.height + 4.0), size: statusNodeLayout.size)
|
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: 94.0, y: 25.0 + nameNodeLayout.size.height + 4.0), size: statusNodeLayout.size)
|
||||||
|
@ -16,11 +16,23 @@ final class PeerInfoControllerInteraction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct PeerInfoSortableStableId: Hashable {
|
||||||
|
let id: PeerInfoEntryStableId
|
||||||
|
|
||||||
|
static func ==(lhs: PeerInfoSortableStableId, rhs: PeerInfoSortableStableId) -> Bool {
|
||||||
|
return lhs.id.isEqual(to: rhs.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashValue: Int {
|
||||||
|
return self.id.hashValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private struct PeerInfoSortableEntry: Identifiable, Comparable {
|
private struct PeerInfoSortableEntry: Identifiable, Comparable {
|
||||||
let entry: PeerInfoEntry
|
let entry: PeerInfoEntry
|
||||||
|
|
||||||
var stableId: Int {
|
var stableId: PeerInfoSortableStableId {
|
||||||
return self.entry.stableId
|
return PeerInfoSortableStableId(id: self.entry.stableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: PeerInfoSortableEntry, rhs: PeerInfoSortableEntry) -> Bool {
|
static func ==(lhs: PeerInfoSortableEntry, rhs: PeerInfoSortableEntry) -> Bool {
|
||||||
@ -48,6 +60,12 @@ private func preparedPeerInfoEntryTransition(account: Account, from fromEntries:
|
|||||||
return PeerInfoEntryTransition(deletions: deletions, insertions: insertions, updates: updates)
|
return PeerInfoEntryTransition(deletions: deletions, insertions: insertions, updates: updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct PeerInfoEquatableState: Equatable {
|
||||||
|
static func ==(lhs: PeerInfoEquatableState, rhs: PeerInfoEquatableState) -> Bool {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public final class PeerInfoController: ListController {
|
public final class PeerInfoController: ListController {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let peerId: PeerId
|
private let peerId: PeerId
|
||||||
@ -62,6 +80,7 @@ public final class PeerInfoController: ListController {
|
|||||||
private let changeSettingsDisposable = MetaDisposable()
|
private let changeSettingsDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var currentListStyle: PeerInfoListStyle = .plain
|
private var currentListStyle: PeerInfoListStyle = .plain
|
||||||
|
private var state = Promise<PeerInfoState?>(nil)
|
||||||
|
|
||||||
public init(account: Account, peerId: PeerId) {
|
public init(account: Account, peerId: PeerId) {
|
||||||
self.account = account
|
self.account = account
|
||||||
@ -156,6 +175,8 @@ public final class PeerInfoController: ListController {
|
|||||||
let style: PeerInfoListStyle
|
let style: PeerInfoListStyle
|
||||||
if let group = view.peers[view.peerId] as? TelegramGroup {
|
if let group = view.peers[view.peerId] as? TelegramGroup {
|
||||||
style = .blocks
|
style = .blocks
|
||||||
|
} else if let channel = view.peers[view.peerId] as? TelegramChannel, case .group = channel.info {
|
||||||
|
style = .blocks
|
||||||
} else {
|
} else {
|
||||||
style = .plain
|
style = .plain
|
||||||
}
|
}
|
||||||
|
@ -242,4 +242,20 @@ class PeerInfoDisclosureItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||||
|
self.topStripeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||||
|
self.bottomStripeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||||
|
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||||
|
self.labelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||||
|
self.topStripeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||||
|
self.bottomStripeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||||
|
self.titleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||||
|
self.labelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,16 +11,45 @@ protocol PeerInfoSection {
|
|||||||
|
|
||||||
protocol PeerInfoEntryStableId {
|
protocol PeerInfoEntryStableId {
|
||||||
func isEqual(to: PeerInfoEntryStableId) -> Bool
|
func isEqual(to: PeerInfoEntryStableId) -> Bool
|
||||||
|
var hashValue: Int { get }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IntPeerInfoEntryStableId: PeerInfoEntryStableId {
|
||||||
|
let value: Int
|
||||||
|
|
||||||
|
func isEqual(to: PeerInfoEntryStableId) -> Bool {
|
||||||
|
if let to = to as? IntPeerInfoEntryStableId, to.value == self.value {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashValue: Int {
|
||||||
|
return self.value.hashValue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol PeerInfoEntry {
|
protocol PeerInfoEntry {
|
||||||
var section: PeerInfoSection { get }
|
var section: PeerInfoSection { get }
|
||||||
var stableId: Int { get }
|
var stableId: PeerInfoEntryStableId { get }
|
||||||
func isEqual(to: PeerInfoEntry) -> Bool
|
func isEqual(to: PeerInfoEntry) -> Bool
|
||||||
func isOrderedBefore(_ entry: PeerInfoEntry) -> Bool
|
func isOrderedBefore(_ entry: PeerInfoEntry) -> Bool
|
||||||
func item(account: Account, interaction: PeerInfoControllerInteraction) -> ListViewItem
|
func item(account: Account, interaction: PeerInfoControllerInteraction) -> ListViewItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PeerInfoNavigationButton {
|
||||||
|
case none
|
||||||
|
case edit
|
||||||
|
case done
|
||||||
|
}
|
||||||
|
|
||||||
|
protocol PeerInfoState {
|
||||||
|
func isEqual(to: PeerInfoState) -> Bool
|
||||||
|
|
||||||
|
var navigationButton: PeerInfoNavigationButton { get }
|
||||||
|
}
|
||||||
|
|
||||||
func peerInfoEntries(view: PeerView) -> [PeerInfoEntry] {
|
func peerInfoEntries(view: PeerView) -> [PeerInfoEntry] {
|
||||||
if let user = view.peers[view.peerId] as? TelegramUser {
|
if let user = view.peers[view.peerId] as? TelegramUser {
|
||||||
return userInfoEntries(view: view)
|
return userInfoEntries(view: view)
|
||||||
@ -29,7 +58,7 @@ func peerInfoEntries(view: PeerView) -> [PeerInfoEntry] {
|
|||||||
case .broadcast:
|
case .broadcast:
|
||||||
return channelBroadcastInfoEntries(view: view)
|
return channelBroadcastInfoEntries(view: view)
|
||||||
case .group:
|
case .group:
|
||||||
return []
|
return groupInfoEntries(view: view)
|
||||||
}
|
}
|
||||||
} else if let group = view.peers[view.peerId] as? TelegramGroup {
|
} else if let group = view.peers[view.peerId] as? TelegramGroup {
|
||||||
return groupInfoEntries(view: view)
|
return groupInfoEntries(view: view)
|
||||||
|
@ -105,7 +105,7 @@ class PeerInfoPeerActionItemNode: ListViewItemNode {
|
|||||||
return { item, width, neighbors in
|
return { item, width, neighbors in
|
||||||
let leftInset: CGFloat = 65.0
|
let leftInset: CGFloat = 65.0
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(NSAttributedString(string: item.title, font: titleFont, textColor: UIColor(0x1195f2)), nil, 1, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), nil)
|
let (titleLayout, titleApply) = makeTitleLayout(NSAttributedString(string: item.title, font: titleFont, textColor: UIColor(0x007ee5)), nil, 1, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
|
||||||
let contentSize: CGSize
|
let contentSize: CGSize
|
||||||
let insets: UIEdgeInsets
|
let insets: UIEdgeInsets
|
||||||
@ -204,4 +204,20 @@ class PeerInfoPeerActionItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.topStripeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.bottomStripeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.topStripeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.bottomStripeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.titleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.iconNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,19 @@ import SwiftSignalKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
|
||||||
class PeerInfoPeerItem: ListViewItem, PeerInfoItem {
|
final class PeerInfoPeerItem: ListViewItem, PeerInfoItem {
|
||||||
let account: Account
|
let account: Account
|
||||||
let peer: Peer?
|
let peer: Peer?
|
||||||
|
let presence: PeerPresence?
|
||||||
|
let label: String?
|
||||||
let sectionId: PeerInfoItemSectionId
|
let sectionId: PeerInfoItemSectionId
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
init(account: Account, peer: Peer?, sectionId: PeerInfoItemSectionId, action: @escaping () -> Void) {
|
init(account: Account, peer: Peer?, presence: PeerPresence?, label: String?, sectionId: PeerInfoItemSectionId, action: @escaping () -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
self.presence = presence
|
||||||
|
self.label = label
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
@ -60,6 +64,7 @@ class PeerInfoPeerItem: ListViewItem, PeerInfoItem {
|
|||||||
private let titleFont = Font.regular(17.0)
|
private let titleFont = Font.regular(17.0)
|
||||||
private let titleBoldFont = Font.medium(17.0)
|
private let titleBoldFont = Font.medium(17.0)
|
||||||
private let statusFont = Font.regular(14.0)
|
private let statusFont = Font.regular(14.0)
|
||||||
|
private let labelFont = Font.regular(13.0)
|
||||||
private let avatarFont = Font.regular(17.0)
|
private let avatarFont = Font.regular(17.0)
|
||||||
|
|
||||||
class PeerInfoPeerItemNode: ListViewItemNode {
|
class PeerInfoPeerItemNode: ListViewItemNode {
|
||||||
@ -70,8 +75,12 @@ class PeerInfoPeerItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
|
private let labelNode: TextNode
|
||||||
private let statusNode: TextNode
|
private let statusNode: TextNode
|
||||||
|
|
||||||
|
private var peerPresenceManager: PeerPresenceStatusManager?
|
||||||
|
private var layoutParams: (PeerInfoPeerItem, CGFloat, PeerInfoItemNeighbors)?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.backgroundNode = ASDisplayNode()
|
self.backgroundNode = ASDisplayNode()
|
||||||
self.backgroundNode.isLayerBacked = true
|
self.backgroundNode.isLayerBacked = true
|
||||||
@ -98,6 +107,11 @@ class PeerInfoPeerItemNode: ListViewItemNode {
|
|||||||
self.statusNode.contentMode = .left
|
self.statusNode.contentMode = .left
|
||||||
self.statusNode.contentsScale = UIScreen.main.scale
|
self.statusNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
|
self.labelNode = TextNode()
|
||||||
|
self.labelNode.isLayerBacked = true
|
||||||
|
self.labelNode.contentMode = .left
|
||||||
|
self.labelNode.contentsScale = UIScreen.main.scale
|
||||||
|
|
||||||
self.highlightedBackgroundNode = ASDisplayNode()
|
self.highlightedBackgroundNode = ASDisplayNode()
|
||||||
self.highlightedBackgroundNode.backgroundColor = UIColor(0xd9d9d9)
|
self.highlightedBackgroundNode.backgroundColor = UIColor(0xd9d9d9)
|
||||||
self.highlightedBackgroundNode.isLayerBacked = true
|
self.highlightedBackgroundNode.isLayerBacked = true
|
||||||
@ -107,15 +121,25 @@ class PeerInfoPeerItemNode: ListViewItemNode {
|
|||||||
self.addSubnode(self.avatarNode)
|
self.addSubnode(self.avatarNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.statusNode)
|
self.addSubnode(self.statusNode)
|
||||||
|
self.addSubnode(self.labelNode)
|
||||||
|
|
||||||
|
self.peerPresenceManager = PeerPresenceStatusManager(update: { [weak self] in
|
||||||
|
if let strongSelf = self, let layoutParams = strongSelf.layoutParams {
|
||||||
|
let (_, apply) = strongSelf.asyncLayout()(layoutParams.0, layoutParams.1, layoutParams.2)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func asyncLayout() -> (_ item: PeerInfoPeerItem, _ width: CGFloat, _ neighbors: PeerInfoItemNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
func asyncLayout() -> (_ item: PeerInfoPeerItem, _ width: CGFloat, _ neighbors: PeerInfoItemNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||||
|
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||||
|
|
||||||
return { item, width, neighbors in
|
return { item, width, neighbors in
|
||||||
var titleAttributedString: NSAttributedString?
|
var titleAttributedString: NSAttributedString?
|
||||||
var statusAttributedString: NSAttributedString?
|
var statusAttributedString: NSAttributedString?
|
||||||
|
var labelAttributedString: NSAttributedString?
|
||||||
|
|
||||||
if let peer = item.peer {
|
if let peer = item.peer {
|
||||||
if let user = peer as? TelegramUser {
|
if let user = peer as? TelegramUser {
|
||||||
@ -133,7 +157,13 @@ class PeerInfoPeerItemNode: ListViewItemNode {
|
|||||||
titleAttributedString = NSAttributedString(string: "Deleted User", font: titleBoldFont, textColor: UIColor(0xa6a6a6))
|
titleAttributedString = NSAttributedString(string: "Deleted User", font: titleBoldFont, textColor: UIColor(0xa6a6a6))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let presence = item.presence as? TelegramUserPresence {
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||||
|
let (string, activity) = stringAndActivityForUserPresence(presence, relativeTo: Int32(timestamp))
|
||||||
|
statusAttributedString = NSAttributedString(string: string, font: statusFont, textColor: activity ? UIColor(0x007ee5) : UIColor(0xa6a6a6))
|
||||||
|
} else {
|
||||||
statusAttributedString = NSAttributedString(string: "last seen recently", font: statusFont, textColor: UIColor(0xa6a6a6))
|
statusAttributedString = NSAttributedString(string: "last seen recently", font: statusFont, textColor: UIColor(0xa6a6a6))
|
||||||
|
}
|
||||||
} else if let group = peer as? TelegramGroup {
|
} else if let group = peer as? TelegramGroup {
|
||||||
titleAttributedString = NSAttributedString(string: group.title, font: titleBoldFont, textColor: UIColor.black)
|
titleAttributedString = NSAttributedString(string: group.title, font: titleBoldFont, textColor: UIColor.black)
|
||||||
} else if let channel = peer as? TelegramChannel {
|
} else if let channel = peer as? TelegramChannel {
|
||||||
@ -141,10 +171,16 @@ class PeerInfoPeerItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let label = item.label {
|
||||||
|
labelAttributedString = NSAttributedString(string: label, font: labelFont, textColor: UIColor(0xa6a6a6))
|
||||||
|
}
|
||||||
|
|
||||||
let leftInset: CGFloat = 65.0
|
let leftInset: CGFloat = 65.0
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(titleAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0, height: CGFloat.greatestFiniteMagnitude), nil)
|
let (labelLayout, labelApply) = makeLabelLayout(labelAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
let (statusLayout, statusApply) = makeStatusLayout(statusAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0, height: CGFloat.greatestFiniteMagnitude), nil)
|
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(titleAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0 - labelLayout.size.width, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
let (statusLayout, statusApply) = makeStatusLayout(statusAttributedString, nil, 1, .end, CGSize(width: width - leftInset - 8.0 - (labelLayout.size.width > 0.0 ? (labelLayout.size.width) + 15.0 : 0.0), height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
|
||||||
let contentSize: CGSize
|
let contentSize: CGSize
|
||||||
let insets: UIEdgeInsets
|
let insets: UIEdgeInsets
|
||||||
@ -172,8 +208,13 @@ class PeerInfoPeerItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
return (layout, { [weak self] in
|
return (layout, { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
strongSelf.layoutParams = (item, width, neighbors)
|
||||||
|
|
||||||
let _ = titleApply()
|
let _ = titleApply()
|
||||||
let _ = statusApply()
|
let _ = statusApply()
|
||||||
|
let _ = labelApply()
|
||||||
|
|
||||||
|
strongSelf.labelNode.isHidden = labelAttributedString == nil
|
||||||
|
|
||||||
if strongSelf.backgroundNode.supernode == nil {
|
if strongSelf.backgroundNode.supernode == nil {
|
||||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||||
@ -203,13 +244,18 @@ class PeerInfoPeerItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 5.0), size: titleLayout.size)
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 5.0), size: titleLayout.size)
|
||||||
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 25.0), size: statusLayout.size)
|
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 25.0), size: statusLayout.size)
|
||||||
|
strongSelf.labelNode.frame = CGRect(origin: CGPoint(x: width - labelLayout.size.width - 15.0, y: floor((contentSize.height - labelLayout.size.height) / 2.0 - labelLayout.size.height / 10.0)), size: labelLayout.size)
|
||||||
|
|
||||||
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 14.0, y: 4.0), size: CGSize(width: 40.0, height: 40.0))
|
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 4.0), size: CGSize(width: 40.0, height: 40.0))
|
||||||
if let peer = item.peer {
|
if let peer = item.peer {
|
||||||
strongSelf.avatarNode.setPeer(account: item.account, peer: peer)
|
strongSelf.avatarNode.setPeer(account: item.account, peer: peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: 48.0 + UIScreenPixel + UIScreenPixel))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: width, height: 48.0 + UIScreenPixel + UIScreenPixel))
|
||||||
|
|
||||||
|
if let presence = item.presence as? TelegramUserPresence {
|
||||||
|
strongSelf.peerPresenceManager?.reset(presence: presence)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -252,4 +298,24 @@ class PeerInfoPeerItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func animateInsertion(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.topStripeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.bottomStripeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
self.labelNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.topStripeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.bottomStripeNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.titleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.avatarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.statusNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.labelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ class PeerInfoTextWithLabelItemNode: ListViewItemNode {
|
|||||||
let insets = peerInfoItemNeighborsPlainInsets(neighbors)
|
let insets = peerInfoItemNeighborsPlainInsets(neighbors)
|
||||||
let leftInset: CGFloat = 35.0
|
let leftInset: CGFloat = 35.0
|
||||||
|
|
||||||
let (labelLayout, labelApply) = makeLabelLayout(NSAttributedString(string: item.label, font: labelFont, textColor: UIColor(0x1195f2)), nil, 1, .end, CGSize(width: width - leftInset - 8.0, height: CGFloat.greatestFiniteMagnitude), nil)
|
let (labelLayout, labelApply) = makeLabelLayout(NSAttributedString(string: item.label, font: labelFont, textColor: UIColor(0x007ee5)), nil, 1, .end, CGSize(width: width - leftInset - 8.0, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
let (textLayout, textApply) = makeTextLayout(NSAttributedString(string: item.text, font: textFont, textColor: UIColor.black), nil, item.multiline ? 0 : 1, .end, CGSize(width: width - leftInset - 8.0, height: CGFloat.greatestFiniteMagnitude), nil)
|
let (textLayout, textApply) = makeTextLayout(NSAttributedString(string: item.text, font: textFont, textColor: UIColor.black), nil, item.multiline ? 0 : 1, .end, CGSize(width: width - leftInset - 8.0, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
let contentSize = CGSize(width: width, height: textLayout.size.height + 39.0)
|
let contentSize = CGSize(width: width, height: textLayout.size.height + 39.0)
|
||||||
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
return (ListViewItemNodeLayout(contentSize: contentSize, insets: insets), { [weak self] in
|
||||||
@ -115,4 +115,10 @@ class PeerInfoTextWithLabelItemNode: ListViewItemNode {
|
|||||||
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||||
self.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
self.separatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||||
|
self.labelNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||||
|
self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||||
|
self.separatorNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import Foundation
|
|||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
import Display
|
import Display
|
||||||
|
|
||||||
private let checkmarkImage = generateTintedImage(image: UIImage(bundleImageName: "List Menu/Checkmark")?.precomposed(), color: UIColor(0x1195f2))
|
private let checkmarkImage = generateTintedImage(image: UIImage(bundleImageName: "List Menu/Checkmark")?.precomposed(), color: UIColor(0x007ee5))
|
||||||
|
|
||||||
private final class PeerMediaCollectionModeSelectionCaseNode: ASDisplayNode {
|
private final class PeerMediaCollectionModeSelectionCaseNode: ASDisplayNode {
|
||||||
fileprivate let mode: PeerMediaCollectionMode
|
fileprivate let mode: PeerMediaCollectionMode
|
||||||
@ -17,7 +17,7 @@ private final class PeerMediaCollectionModeSelectionCaseNode: ASDisplayNode {
|
|||||||
var isSelected = false {
|
var isSelected = false {
|
||||||
didSet {
|
didSet {
|
||||||
if self.isSelected != oldValue {
|
if self.isSelected != oldValue {
|
||||||
self.titleNode.attributedText = NSAttributedString(string: titleForPeerMediaCollectionMode(self.mode), font: Font.regular(17.0), textColor: isSelected ? UIColor(0x1195f2) : UIColor.black)
|
self.titleNode.attributedText = NSAttributedString(string: titleForPeerMediaCollectionMode(self.mode), font: Font.regular(17.0), textColor: isSelected ? UIColor(0x007ee5) : UIColor.black)
|
||||||
self.checkmarkView.isHidden = !self.isSelected
|
self.checkmarkView.isHidden = !self.isSelected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
TelegramUI/PeerPresenceStatusManager.swift
Normal file
32
TelegramUI/PeerPresenceStatusManager.swift
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import Foundation
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
final class PeerPresenceStatusManager {
|
||||||
|
private let update: () -> Void
|
||||||
|
private var timer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
|
init(update: @escaping () -> Void) {
|
||||||
|
self.update = update
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.timer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset(presence: TelegramUserPresence) {
|
||||||
|
timer?.invalidate()
|
||||||
|
timer = nil
|
||||||
|
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||||
|
let timeout = userPresenceStringRefreshTimeout(presence, relativeTo: Int32(timestamp))
|
||||||
|
if timeout.isFinite {
|
||||||
|
self.timer = SwiftSignalKit.Timer(timeout: timeout, repeat: false, completion: { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.update()
|
||||||
|
}
|
||||||
|
}, queue: Queue.mainQueue())
|
||||||
|
self.timer?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
TelegramUI/PresenceStrings.swift
Normal file
140
TelegramUI/PresenceStrings.swift
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import Foundation
|
||||||
|
import Postbox
|
||||||
|
import TelegramCore
|
||||||
|
|
||||||
|
func stringForTimestamp(day: Int32, month: Int32, year: Int32) -> String {
|
||||||
|
return String(format: "%d.%02d.%02d", day, month, year - 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringForTime(hours: Int32, minutes: Int32) -> String {
|
||||||
|
return String(format: "%d:%02d", hours, minutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UserPresenceDay {
|
||||||
|
case today
|
||||||
|
case yesterday
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringForUserPresence(day: UserPresenceDay, hours: Int32, minutes: Int32) -> String {
|
||||||
|
let dayString: String
|
||||||
|
switch day {
|
||||||
|
case .today:
|
||||||
|
dayString = "today"
|
||||||
|
case .yesterday:
|
||||||
|
dayString = "yesterday"
|
||||||
|
}
|
||||||
|
return "last seen \(dayString) at \(stringForTime(hours: hours, minutes: minutes))"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RelativeUserPresenceLastSeen {
|
||||||
|
case justNow
|
||||||
|
case minutesAgo(Int32)
|
||||||
|
case hoursAgo(Int32)
|
||||||
|
case todayAt(hours: Int32, minutes: Int32)
|
||||||
|
case yesterdayAt(hours: Int32, minutes: Int32)
|
||||||
|
case thisYear(month: Int32, day: Int32)
|
||||||
|
case atDate(year: Int32, month: Int32)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum RelativeUserPresenceStatus {
|
||||||
|
case offline
|
||||||
|
case online(at: Int32)
|
||||||
|
case lastSeen(at: Int32)
|
||||||
|
case recently
|
||||||
|
case lastWeek
|
||||||
|
case lastMonth
|
||||||
|
}
|
||||||
|
|
||||||
|
func relativeUserPresenceStatus(_ presence: TelegramUserPresence, relativeTo timestamp: Int32) -> RelativeUserPresenceStatus {
|
||||||
|
switch presence.status {
|
||||||
|
case .none:
|
||||||
|
return .offline
|
||||||
|
case let .present(statusTimestamp):
|
||||||
|
if statusTimestamp >= timestamp {
|
||||||
|
return .online(at: statusTimestamp)
|
||||||
|
} else {
|
||||||
|
return .lastSeen(at: statusTimestamp)
|
||||||
|
}
|
||||||
|
case .recently:
|
||||||
|
return .recently
|
||||||
|
case .lastWeek:
|
||||||
|
return .lastWeek
|
||||||
|
case .lastMonth:
|
||||||
|
return .lastMonth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringAndActivityForUserPresence(_ presence: TelegramUserPresence, relativeTo timestamp: Int32) -> (String, Bool) {
|
||||||
|
switch presence.status {
|
||||||
|
case .none:
|
||||||
|
return ("offline", false)
|
||||||
|
case let .present(statusTimestamp):
|
||||||
|
if statusTimestamp >= timestamp {
|
||||||
|
return ("online", true)
|
||||||
|
} else {
|
||||||
|
let difference = timestamp - statusTimestamp
|
||||||
|
if difference < 30 {
|
||||||
|
return ("last seen just now", false)
|
||||||
|
} else if difference < 60 * 60 {
|
||||||
|
let minutes = difference / 60
|
||||||
|
if minutes <= 1 {
|
||||||
|
return ("last seen 1 minute ago", false)
|
||||||
|
} else {
|
||||||
|
return ("last seen \(minutes) minutes ago", false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var t: time_t = time_t(statusTimestamp)
|
||||||
|
var timeinfo: tm = tm()
|
||||||
|
localtime_r(&t, &timeinfo)
|
||||||
|
|
||||||
|
var now: time_t = time_t(timestamp)
|
||||||
|
var timeinfoNow: tm = tm()
|
||||||
|
localtime_r(&now, &timeinfoNow)
|
||||||
|
|
||||||
|
if timeinfo.tm_year != timeinfoNow.tm_year {
|
||||||
|
return ("last seen \(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year))", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday
|
||||||
|
if dayDifference == 0 || dayDifference == -1 {
|
||||||
|
let day: UserPresenceDay
|
||||||
|
if dayDifference == 0 {
|
||||||
|
day = .today
|
||||||
|
} else {
|
||||||
|
day = .yesterday
|
||||||
|
}
|
||||||
|
return (stringForUserPresence(day: day, hours: timeinfo.tm_hour, minutes: timeinfo.tm_min), false)
|
||||||
|
} else {
|
||||||
|
return ("last seen \(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year))", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .recently:
|
||||||
|
return ("last seen recently", false)
|
||||||
|
case .lastWeek:
|
||||||
|
return ("last seen last week", false)
|
||||||
|
case .lastMonth:
|
||||||
|
return ("last seen last month", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userPresenceStringRefreshTimeout(_ presence: TelegramUserPresence, relativeTo timestamp: Int32) -> Double {
|
||||||
|
switch presence.status {
|
||||||
|
case let .present(statusTimestamp):
|
||||||
|
if statusTimestamp >= timestamp {
|
||||||
|
return Double(statusTimestamp - timestamp)
|
||||||
|
} else {
|
||||||
|
let difference = timestamp - statusTimestamp
|
||||||
|
if difference < 30 {
|
||||||
|
return Double((30 - difference) + 1)
|
||||||
|
} else if difference < 60 * 60 {
|
||||||
|
return Double((difference % 60) + 1)
|
||||||
|
} else {
|
||||||
|
return Double.infinity
|
||||||
|
}
|
||||||
|
return Double.infinity
|
||||||
|
}
|
||||||
|
case .recently, .none, .lastWeek, .lastMonth:
|
||||||
|
return Double.infinity
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
self.closeButton.displaysAsynchronously = false
|
self.closeButton.displaysAsynchronously = false
|
||||||
|
|
||||||
self.lineNode = ASDisplayNode()
|
self.lineNode = ASDisplayNode()
|
||||||
self.lineNode.backgroundColor = UIColor(0x1195f2)
|
self.lineNode.backgroundColor = UIColor(0x007ee5)
|
||||||
|
|
||||||
self.titleNode = ASTextNode()
|
self.titleNode = ASTextNode()
|
||||||
self.titleNode.truncationMode = .byTruncatingTail
|
self.titleNode.truncationMode = .byTruncatingTail
|
||||||
@ -56,7 +56,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode {
|
|||||||
text = messageText
|
text = messageText
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.titleNode.attributedText = NSAttributedString(string: authorName, font: Font.regular(14.5), textColor: UIColor(0x1195f2))
|
strongSelf.titleNode.attributedText = NSAttributedString(string: authorName, font: Font.regular(14.5), textColor: UIColor(0x007ee5))
|
||||||
strongSelf.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.5), textColor: UIColor.black)
|
strongSelf.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.5), textColor: UIColor.black)
|
||||||
|
|
||||||
strongSelf.setNeedsLayout()
|
strongSelf.setNeedsLayout()
|
||||||
|
@ -95,7 +95,7 @@ class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
|
|||||||
|
|
||||||
self.cancelButton = ASButtonNode()
|
self.cancelButton = ASButtonNode()
|
||||||
self.cancelButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
|
self.cancelButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
|
||||||
self.cancelButton.setAttributedTitle(NSAttributedString(string: "Cancel", font: Font.regular(17.0), textColor: UIColor(0x1195f2)), for: [])
|
self.cancelButton.setAttributedTitle(NSAttributedString(string: "Cancel", font: Font.regular(17.0), textColor: UIColor(0x007ee5)), for: [])
|
||||||
self.cancelButton.displaysAsynchronously = false
|
self.cancelButton.displaysAsynchronously = false
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
@ -78,7 +78,7 @@ class SettingsAccountInfoItemNode: ListControllerGroupableItemNode {
|
|||||||
statusColor = UIColor(0xb3b3b3)
|
statusColor = UIColor(0xb3b3b3)
|
||||||
case .Online:
|
case .Online:
|
||||||
statusText = "online"
|
statusText = "online"
|
||||||
statusColor = UIColor(0x1195f2)
|
statusColor = UIColor(0x007ee5)
|
||||||
}
|
}
|
||||||
|
|
||||||
let (statusNodeLayout, statusNodeApply) = layoutStatusNode(NSAttributedString(string: statusText, font: statusFont, textColor: statusColor), nil, 1, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), nil)
|
let (statusNodeLayout, statusNodeApply) = layoutStatusNode(NSAttributedString(string: statusText, font: statusFont, textColor: statusColor), nil, 1, .end, CGSize(width: width - 20, height: CGFloat.greatestFiniteMagnitude), nil)
|
||||||
|
@ -258,6 +258,7 @@ final class TextNode: ASDisplayNode {
|
|||||||
|
|
||||||
var updated = false
|
var updated = false
|
||||||
if let existingLayout = existingLayout, existingLayout.constrainedSize == constrainedSize && existingLayout.maximumNumberOfLines == maximumNumberOfLines && existingLayout.truncationType == truncationType && existingLayout.cutout == cutout {
|
if let existingLayout = existingLayout, existingLayout.constrainedSize == constrainedSize && existingLayout.maximumNumberOfLines == maximumNumberOfLines && existingLayout.truncationType == truncationType && existingLayout.cutout == cutout {
|
||||||
|
|
||||||
let stringMatch: Bool
|
let stringMatch: Bool
|
||||||
if let existingString = existingLayout.attributedString, let string = attributedString {
|
if let existingString = existingLayout.attributedString, let string = attributedString {
|
||||||
stringMatch = existingString.isEqual(to: string)
|
stringMatch = existingString.isEqual(to: string)
|
||||||
|
@ -50,8 +50,8 @@ enum UserInfoEntry: PeerInfoEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var stableId: Int {
|
var stableId: PeerInfoEntryStableId {
|
||||||
return self.sortIndex
|
return IntPeerInfoEntryStableId(value: self.sortIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isEqual(to: PeerInfoEntry) -> Bool {
|
func isEqual(to: PeerInfoEntry) -> Bool {
|
||||||
@ -229,7 +229,31 @@ enum UserInfoEntry: PeerInfoEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func userInfoEntries(view: PeerView) -> [PeerInfoEntry] {
|
final class UserInfoEditingState {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
final class UserInfoState: PeerInfoState {
|
||||||
|
fileprivate let editingState: UserInfoEditingState?
|
||||||
|
|
||||||
|
var navigationButton: PeerInfoNavigationButton {
|
||||||
|
return self.editingState == nil ? .edit : .done
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.editingState = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEqual(to: PeerInfoState) -> Bool {
|
||||||
|
if let to = to as? UserInfoState {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userInfoEntries(view: PeerView, state: PeerInfoState?) -> [PeerInfoEntry] {
|
||||||
var entries: [PeerInfoEntry] = []
|
var entries: [PeerInfoEntry] = []
|
||||||
entries.append(UserInfoEntry.info(peer: view.peers[view.peerId], cachedData: view.cachedData))
|
entries.append(UserInfoEntry.info(peer: view.peers[view.peerId], cachedData: view.cachedData))
|
||||||
if let cachedUserData = view.cachedData as? CachedUserData {
|
if let cachedUserData = view.cachedData as? CachedUserData {
|
||||||
@ -244,9 +268,13 @@ func userInfoEntries(view: PeerView) -> [PeerInfoEntry] {
|
|||||||
if let username = user.username, !username.isEmpty {
|
if let username = user.username, !username.isEmpty {
|
||||||
entries.append(UserInfoEntry.userName(value: username))
|
entries.append(UserInfoEntry.userName(value: username))
|
||||||
}
|
}
|
||||||
|
if let state = state as? UserInfoState, let editingState = state.editingState {
|
||||||
|
|
||||||
|
} else {
|
||||||
entries.append(UserInfoEntry.sendMessage)
|
entries.append(UserInfoEntry.sendMessage)
|
||||||
entries.append(UserInfoEntry.shareContact)
|
entries.append(UserInfoEntry.shareContact)
|
||||||
entries.append(UserInfoEntry.startSecretChat)
|
entries.append(UserInfoEntry.startSecretChat)
|
||||||
|
}
|
||||||
entries.append(UserInfoEntry.sharedMedia)
|
entries.append(UserInfoEntry.sharedMedia)
|
||||||
entries.append(UserInfoEntry.notifications(settings: view.notificationSettings))
|
entries.append(UserInfoEntry.notifications(settings: view.notificationSettings))
|
||||||
entries.append(UserInfoEntry.block)
|
entries.append(UserInfoEntry.block)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user