mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
User Info screen
This commit is contained in:
parent
f9ccae936a
commit
e2071301c2
@ -262,6 +262,11 @@
|
||||
"State.Updating" = "Updating...";
|
||||
"State.WaitingForNetwork" = "Waiting for network";
|
||||
|
||||
"ChatState.Connecting" = "connecting...";
|
||||
"ChatState.ConnectingToProxy" = "connecting to proxy...";
|
||||
"ChatState.Updating" = "updating...";
|
||||
"ChatState.WaitingForNetwork" = "waiting for network...";
|
||||
|
||||
// Presence
|
||||
"Presence.online" = "online";
|
||||
|
||||
|
@ -351,7 +351,7 @@ public extension ContainedViewLayoutTransition {
|
||||
func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat, removeOnCompletion: Bool = true, completion: @escaping (Bool) -> Void) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
break
|
||||
completion(true)
|
||||
case let .animated(duration, curve):
|
||||
node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
|
||||
}
|
||||
@ -360,7 +360,7 @@ public extension ContainedViewLayoutTransition {
|
||||
func animatePositionAdditive(layer: CALayer, offset: CGFloat, removeOnCompletion: Bool = true, completion: @escaping (Bool) -> Void) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
break
|
||||
completion(true)
|
||||
case let .animated(duration, curve):
|
||||
layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
|
||||
}
|
||||
@ -369,7 +369,7 @@ public extension ContainedViewLayoutTransition {
|
||||
func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
break
|
||||
completion?()
|
||||
case let .animated(duration, curve):
|
||||
node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in
|
||||
completion?()
|
||||
@ -380,7 +380,7 @@ public extension ContainedViewLayoutTransition {
|
||||
func animatePositionAdditive(layer: CALayer, offset: CGPoint, to toOffset: CGPoint = CGPoint(), removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
break
|
||||
completion?()
|
||||
case let .animated(duration, curve):
|
||||
layer.animatePosition(from: offset, to: toOffset, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in
|
||||
completion?()
|
||||
|
@ -1188,4 +1188,25 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.bounds.contains(point) {
|
||||
if self.backButtonNode.supernode != nil && !self.backButtonNode.isHidden {
|
||||
let effectiveBackButtonRect = CGRect(origin: CGPoint(), size: CGSize(width: self.backButtonNode.frame.maxX + 20.0, height: self.bounds.height))
|
||||
if effectiveBackButtonRect.contains(point) {
|
||||
return self.backButtonNode.internalHitTest(self.view.convert(point, to: self.backButtonNode.view), with: event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard let result = super.hitTest(point, with: event) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if result == self.view || result == self.clippingNode.view {
|
||||
return nil
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ private final class NavigationButtonItemNode: ASTextNode {
|
||||
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
self.updateHighlightedState(self.touchInsideApparentBounds(touches.first!), animated: true)
|
||||
//self.updateHighlightedState(self.touchInsideApparentBounds(touches.first!), animated: true)
|
||||
}
|
||||
|
||||
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
@ -251,7 +251,7 @@ private final class NavigationButtonItemNode: ASTextNode {
|
||||
let previousTouchCount = self.touchCount
|
||||
self.touchCount = max(0, self.touchCount - touches.count)
|
||||
|
||||
if previousTouchCount != 0 && self.touchCount == 0 && self.isEnabled && self.touchInsideApparentBounds(touches.first!) {
|
||||
if previousTouchCount != 0 && self.touchCount == 0 && self.isEnabled {
|
||||
self.pressed()
|
||||
}
|
||||
}
|
||||
@ -455,4 +455,12 @@ public final class NavigationButtonNode: ASDisplayNode {
|
||||
}
|
||||
return totalSize
|
||||
}
|
||||
|
||||
func internalHitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.nodes.count == 1 {
|
||||
return self.nodes[0].view
|
||||
} else {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -424,7 +424,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
}
|
||||
|
||||
private let avatarFont = avatarPlaceholderFont(size: 15.0)
|
||||
private let avatarFont = avatarPlaceholderFont(size: floor(40.0 * 16.0 / 37.0))
|
||||
private let badgeFont = Font.regular(15.0)
|
||||
|
||||
public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNode {
|
||||
|
@ -304,6 +304,9 @@ public func notificationSoundSelectionController(context: AccountContext, isModa
|
||||
playSoundDisposable.dispose()
|
||||
})
|
||||
controller.enableInteractiveDismiss = true
|
||||
if isModal {
|
||||
controller.navigationPresentation = .modal
|
||||
}
|
||||
|
||||
completeImpl = { [weak controller] in
|
||||
let sound = stateValue.with { state in
|
||||
|
@ -77,7 +77,7 @@ public class MemoryBuffer: Equatable, CustomStringConvertible {
|
||||
data.copyBytes(to: self.memory.assumingMemoryBound(to: UInt8.self), count: data.count)
|
||||
self.capacity = data.count
|
||||
self.length = data.count
|
||||
self.freeWhenDone = false
|
||||
self.freeWhenDone = true
|
||||
}
|
||||
}
|
||||
|
||||
|
76
submodules/Postbox/Sources/HistoryTagInfoView.swift
Normal file
76
submodules/Postbox/Sources/HistoryTagInfoView.swift
Normal file
@ -0,0 +1,76 @@
|
||||
import Foundation
|
||||
|
||||
final class MutableHistoryTagInfoView: MutablePostboxView {
|
||||
fileprivate let peerId: PeerId
|
||||
fileprivate let tag: MessageTags
|
||||
|
||||
fileprivate var currentIndex: MessageIndex?
|
||||
|
||||
init(postbox: Postbox, peerId: PeerId, tag: MessageTags) {
|
||||
self.peerId = peerId
|
||||
self.tag = tag
|
||||
for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: self.peerId) {
|
||||
if let index = postbox.messageHistoryTagsTable.latestIndex(tag: self.tag, peerId: self.peerId, namespace: namespace) {
|
||||
self.currentIndex = index
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func replay(postbox: Postbox, transaction: PostboxTransaction) -> Bool {
|
||||
if let operations = transaction.currentOperationsByPeerId[self.peerId] {
|
||||
var updated = false
|
||||
var refresh = false
|
||||
for operation in operations {
|
||||
switch operation {
|
||||
case let .InsertMessage(message):
|
||||
if self.currentIndex == nil {
|
||||
if message.tags.contains(self.tag) {
|
||||
self.currentIndex = message.index
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
case let .Remove(indicesAndTags):
|
||||
if self.currentIndex != nil {
|
||||
for (index, tags) in indicesAndTags {
|
||||
if tags.contains(self.tag) {
|
||||
if index == self.currentIndex {
|
||||
self.currentIndex = nil
|
||||
updated = true
|
||||
refresh = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if refresh {
|
||||
for namespace in postbox.messageHistoryIndexTable.existingNamespaces(peerId: self.peerId) {
|
||||
if let index = postbox.messageHistoryTagsTable.latestIndex(tag: self.tag, peerId: self.peerId, namespace: namespace) {
|
||||
self.currentIndex = index
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updated
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func immutableView() -> PostboxView {
|
||||
return HistoryTagInfoView(self)
|
||||
}
|
||||
}
|
||||
|
||||
public final class HistoryTagInfoView: PostboxView {
|
||||
public let isEmpty: Bool
|
||||
|
||||
init(_ view: MutableHistoryTagInfoView) {
|
||||
self.isEmpty = view.currentIndex == nil
|
||||
}
|
||||
}
|
@ -132,6 +132,15 @@ class MessageHistoryTagsTable: Table {
|
||||
return Int(self.valueBox.count(self.table, start: lowerBoundKey, end: upperBoundKey))
|
||||
}
|
||||
|
||||
func latestIndex(tag: MessageTags, peerId: PeerId, namespace: MessageId.Namespace) -> MessageIndex? {
|
||||
var result: MessageIndex?
|
||||
self.valueBox.range(self.table, start: self.lowerBound(tag: tag, peerId: peerId, namespace: namespace), end: self.upperBound(tag: tag, peerId: peerId, namespace: namespace), keys: { key in
|
||||
result = extractKey(key)
|
||||
return true
|
||||
}, limit: 1)
|
||||
return result
|
||||
}
|
||||
|
||||
func findRandomIndex(peerId: PeerId, namespace: MessageId.Namespace, tag: MessageTags, ignoreIds: ([MessageId], Set<MessageId>), isMessage: (MessageIndex) -> Bool) -> MessageIndex? {
|
||||
var indices: [MessageIndex] = []
|
||||
self.valueBox.range(self.table, start: self.lowerBound(tag: tag, peerId: peerId, namespace: namespace), end: self.upperBound(tag: tag, peerId: peerId, namespace: namespace), keys: { key in
|
||||
|
@ -28,289 +28,300 @@ public enum PostboxViewKey: Hashable {
|
||||
case peerChatInclusion(PeerId)
|
||||
case basicPeer(PeerId)
|
||||
case allChatListHoles(PeerGroupId)
|
||||
case historyTagInfo(peerId: PeerId, tag: MessageTags)
|
||||
|
||||
public var hashValue: Int {
|
||||
switch self {
|
||||
case .itemCollectionInfos:
|
||||
return 0
|
||||
case .itemCollectionIds:
|
||||
return 1
|
||||
case let .peerChatState(peerId):
|
||||
return peerId.hashValue
|
||||
case let .itemCollectionInfo(id):
|
||||
return id.hashValue
|
||||
case let .orderedItemList(id):
|
||||
return id.hashValue
|
||||
case .preferences:
|
||||
return 3
|
||||
case .globalMessageTags:
|
||||
return 4
|
||||
case let .peer(peerId, _):
|
||||
return peerId.hashValue
|
||||
case let .pendingMessageActions(type):
|
||||
return type.hashValue
|
||||
case let .invalidatedMessageHistoryTagSummaries(tagMask, namespace):
|
||||
return tagMask.rawValue.hashValue ^ namespace.hashValue
|
||||
case let .pendingMessageActionsSummary(type, peerId, namespace):
|
||||
return type.hashValue ^ peerId.hashValue ^ namespace.hashValue
|
||||
case let .historyTagSummaryView(tag, peerId, namespace):
|
||||
return tag.rawValue.hashValue ^ peerId.hashValue ^ namespace.hashValue
|
||||
case let .cachedPeerData(peerId):
|
||||
return peerId.hashValue
|
||||
case .unreadCounts:
|
||||
return 5
|
||||
case .peerNotificationSettings:
|
||||
return 6
|
||||
case .pendingPeerNotificationSettings:
|
||||
return 7
|
||||
case let .messageOfInterestHole(location, namespace, count):
|
||||
return 8 &+ 31 &* location.hashValue &+ 31 &* namespace.hashValue &+ 31 &* count.hashValue
|
||||
case let .localMessageTag(tag):
|
||||
return tag.hashValue
|
||||
case .messages:
|
||||
return 10
|
||||
case .additionalChatListItems:
|
||||
return 11
|
||||
case let .cachedItem(id):
|
||||
return id.hashValue
|
||||
case .peerPresences:
|
||||
return 13
|
||||
case .synchronizeGroupMessageStats:
|
||||
return 14
|
||||
case .peerNotificationSettingsBehaviorTimestampView:
|
||||
return 15
|
||||
case let .peerChatInclusion(peerId):
|
||||
return peerId.hashValue
|
||||
case let .basicPeer(peerId):
|
||||
return peerId.hashValue
|
||||
case let .allChatListHoles(groupId):
|
||||
return groupId.hashValue
|
||||
case .itemCollectionInfos:
|
||||
return 0
|
||||
case .itemCollectionIds:
|
||||
return 1
|
||||
case let .peerChatState(peerId):
|
||||
return peerId.hashValue
|
||||
case let .itemCollectionInfo(id):
|
||||
return id.hashValue
|
||||
case let .orderedItemList(id):
|
||||
return id.hashValue
|
||||
case .preferences:
|
||||
return 3
|
||||
case .globalMessageTags:
|
||||
return 4
|
||||
case let .peer(peerId, _):
|
||||
return peerId.hashValue
|
||||
case let .pendingMessageActions(type):
|
||||
return type.hashValue
|
||||
case let .invalidatedMessageHistoryTagSummaries(tagMask, namespace):
|
||||
return tagMask.rawValue.hashValue ^ namespace.hashValue
|
||||
case let .pendingMessageActionsSummary(type, peerId, namespace):
|
||||
return type.hashValue ^ peerId.hashValue ^ namespace.hashValue
|
||||
case let .historyTagSummaryView(tag, peerId, namespace):
|
||||
return tag.rawValue.hashValue ^ peerId.hashValue ^ namespace.hashValue
|
||||
case let .cachedPeerData(peerId):
|
||||
return peerId.hashValue
|
||||
case .unreadCounts:
|
||||
return 5
|
||||
case .peerNotificationSettings:
|
||||
return 6
|
||||
case .pendingPeerNotificationSettings:
|
||||
return 7
|
||||
case let .messageOfInterestHole(location, namespace, count):
|
||||
return 8 &+ 31 &* location.hashValue &+ 31 &* namespace.hashValue &+ 31 &* count.hashValue
|
||||
case let .localMessageTag(tag):
|
||||
return tag.hashValue
|
||||
case .messages:
|
||||
return 10
|
||||
case .additionalChatListItems:
|
||||
return 11
|
||||
case let .cachedItem(id):
|
||||
return id.hashValue
|
||||
case .peerPresences:
|
||||
return 13
|
||||
case .synchronizeGroupMessageStats:
|
||||
return 14
|
||||
case .peerNotificationSettingsBehaviorTimestampView:
|
||||
return 15
|
||||
case let .peerChatInclusion(peerId):
|
||||
return peerId.hashValue
|
||||
case let .basicPeer(peerId):
|
||||
return peerId.hashValue
|
||||
case let .allChatListHoles(groupId):
|
||||
return groupId.hashValue
|
||||
case let .historyTagInfo(peerId, tag):
|
||||
return peerId.hashValue ^ tag.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: PostboxViewKey, rhs: PostboxViewKey) -> Bool {
|
||||
switch lhs {
|
||||
case let .itemCollectionInfos(lhsNamespaces):
|
||||
if case let .itemCollectionInfos(rhsNamespaces) = rhs, lhsNamespaces == rhsNamespaces {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .itemCollectionIds(lhsNamespaces):
|
||||
if case let .itemCollectionIds(rhsNamespaces) = rhs, lhsNamespaces == rhsNamespaces {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .itemCollectionInfo(id):
|
||||
if case .itemCollectionInfo(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerChatState(peerId):
|
||||
if case .peerChatState(peerId) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .orderedItemList(id):
|
||||
if case .orderedItemList(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .preferences(lhsKeys):
|
||||
if case let .preferences(rhsKeys) = rhs, lhsKeys == rhsKeys {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .globalMessageTags(globalTag, position, count, _):
|
||||
if case .globalMessageTags(globalTag, position, count, _) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peer(peerId, components):
|
||||
if case .peer(peerId, components) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .pendingMessageActions(type):
|
||||
if case .pendingMessageActions(type) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .invalidatedMessageHistoryTagSummaries:
|
||||
if case .invalidatedMessageHistoryTagSummaries = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .pendingMessageActionsSummary(type, peerId, namespace):
|
||||
if case .pendingMessageActionsSummary(type, peerId, namespace) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .historyTagSummaryView(tag, peerId, namespace):
|
||||
if case .historyTagSummaryView(tag, peerId, namespace) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .cachedPeerData(peerId):
|
||||
if case .cachedPeerData(peerId) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .unreadCounts(lhsItems):
|
||||
if case let .unreadCounts(rhsItems) = rhs, lhsItems == rhsItems {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerNotificationSettings(peerIds):
|
||||
if case .peerNotificationSettings(peerIds) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .pendingPeerNotificationSettings:
|
||||
if case .pendingPeerNotificationSettings = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .messageOfInterestHole(peerId, namespace, count):
|
||||
if case .messageOfInterestHole(peerId, namespace, count) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .localMessageTag(tag):
|
||||
if case .localMessageTag(tag) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .messages(ids):
|
||||
if case .messages(ids) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .additionalChatListItems:
|
||||
if case .additionalChatListItems = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .cachedItem(id):
|
||||
if case .cachedItem(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerPresences(ids):
|
||||
if case .peerPresences(ids) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .synchronizeGroupMessageStats:
|
||||
if case .synchronizeGroupMessageStats = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .peerNotificationSettingsBehaviorTimestampView:
|
||||
if case .peerNotificationSettingsBehaviorTimestampView = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerChatInclusion(id):
|
||||
if case .peerChatInclusion(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .basicPeer(id):
|
||||
if case .basicPeer(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .allChatListHoles(groupId):
|
||||
if case .allChatListHoles(groupId) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .itemCollectionInfos(lhsNamespaces):
|
||||
if case let .itemCollectionInfos(rhsNamespaces) = rhs, lhsNamespaces == rhsNamespaces {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .itemCollectionIds(lhsNamespaces):
|
||||
if case let .itemCollectionIds(rhsNamespaces) = rhs, lhsNamespaces == rhsNamespaces {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .itemCollectionInfo(id):
|
||||
if case .itemCollectionInfo(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerChatState(peerId):
|
||||
if case .peerChatState(peerId) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .orderedItemList(id):
|
||||
if case .orderedItemList(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .preferences(lhsKeys):
|
||||
if case let .preferences(rhsKeys) = rhs, lhsKeys == rhsKeys {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .globalMessageTags(globalTag, position, count, _):
|
||||
if case .globalMessageTags(globalTag, position, count, _) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peer(peerId, components):
|
||||
if case .peer(peerId, components) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .pendingMessageActions(type):
|
||||
if case .pendingMessageActions(type) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .invalidatedMessageHistoryTagSummaries:
|
||||
if case .invalidatedMessageHistoryTagSummaries = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .pendingMessageActionsSummary(type, peerId, namespace):
|
||||
if case .pendingMessageActionsSummary(type, peerId, namespace) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .historyTagSummaryView(tag, peerId, namespace):
|
||||
if case .historyTagSummaryView(tag, peerId, namespace) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .cachedPeerData(peerId):
|
||||
if case .cachedPeerData(peerId) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .unreadCounts(lhsItems):
|
||||
if case let .unreadCounts(rhsItems) = rhs, lhsItems == rhsItems {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerNotificationSettings(peerIds):
|
||||
if case .peerNotificationSettings(peerIds) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .pendingPeerNotificationSettings:
|
||||
if case .pendingPeerNotificationSettings = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .messageOfInterestHole(peerId, namespace, count):
|
||||
if case .messageOfInterestHole(peerId, namespace, count) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .localMessageTag(tag):
|
||||
if case .localMessageTag(tag) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .messages(ids):
|
||||
if case .messages(ids) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .additionalChatListItems:
|
||||
if case .additionalChatListItems = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .cachedItem(id):
|
||||
if case .cachedItem(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerPresences(ids):
|
||||
if case .peerPresences(ids) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .synchronizeGroupMessageStats:
|
||||
if case .synchronizeGroupMessageStats = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .peerNotificationSettingsBehaviorTimestampView:
|
||||
if case .peerNotificationSettingsBehaviorTimestampView = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .peerChatInclusion(id):
|
||||
if case .peerChatInclusion(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .basicPeer(id):
|
||||
if case .basicPeer(id) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .allChatListHoles(groupId):
|
||||
if case .allChatListHoles(groupId) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .historyTagInfo(peerId, tag):
|
||||
if case .historyTagInfo(peerId, tag) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func postboxViewForKey(postbox: Postbox, key: PostboxViewKey) -> MutablePostboxView {
|
||||
switch key {
|
||||
case let .itemCollectionInfos(namespaces):
|
||||
return MutableItemCollectionInfosView(postbox: postbox, namespaces: namespaces)
|
||||
case let .itemCollectionIds(namespaces):
|
||||
return MutableItemCollectionIdsView(postbox: postbox, namespaces: namespaces)
|
||||
case let .itemCollectionInfo(id):
|
||||
return MutableItemCollectionInfoView(postbox: postbox, id: id)
|
||||
case let .peerChatState(peerId):
|
||||
return MutablePeerChatStateView(postbox: postbox, peerId: peerId)
|
||||
case let .orderedItemList(id):
|
||||
return MutableOrderedItemListView(postbox: postbox, collectionId: id)
|
||||
case let .preferences(keys):
|
||||
return MutablePreferencesView(postbox: postbox, keys: keys)
|
||||
case let .globalMessageTags(globalTag, position, count, groupingPredicate):
|
||||
return MutableGlobalMessageTagsView(postbox: postbox, globalTag: globalTag, position: position, count: count, groupingPredicate: groupingPredicate)
|
||||
case let .peer(peerId, components):
|
||||
return MutablePeerView(postbox: postbox, peerId: peerId, components: components)
|
||||
case let .pendingMessageActions(type):
|
||||
return MutablePendingMessageActionsView(postbox: postbox, type: type)
|
||||
case let .invalidatedMessageHistoryTagSummaries(tagMask, namespace):
|
||||
return MutableInvalidatedMessageHistoryTagSummariesView(postbox: postbox, tagMask: tagMask, namespace: namespace)
|
||||
case let .pendingMessageActionsSummary(type, peerId, namespace):
|
||||
return MutablePendingMessageActionsSummaryView(postbox: postbox, type: type, peerId: peerId, namespace: namespace)
|
||||
case let .historyTagSummaryView(tag, peerId, namespace):
|
||||
return MutableMessageHistoryTagSummaryView(postbox: postbox, tag: tag, peerId: peerId, namespace: namespace)
|
||||
case let .cachedPeerData(peerId):
|
||||
return MutableCachedPeerDataView(postbox: postbox, peerId: peerId)
|
||||
case let .unreadCounts(items):
|
||||
return MutableUnreadMessageCountsView(postbox: postbox, items: items)
|
||||
case let .peerNotificationSettings(peerIds):
|
||||
return MutablePeerNotificationSettingsView(postbox: postbox, peerIds: peerIds)
|
||||
case .pendingPeerNotificationSettings:
|
||||
return MutablePendingPeerNotificationSettingsView(postbox: postbox)
|
||||
case let .messageOfInterestHole(location, namespace, count):
|
||||
return MutableMessageOfInterestHolesView(postbox: postbox, location: location, namespace: namespace, count: count)
|
||||
case let .localMessageTag(tag):
|
||||
return MutableLocalMessageTagsView(postbox: postbox, tag: tag)
|
||||
case let .messages(ids):
|
||||
return MutableMessagesView(postbox: postbox, ids: ids)
|
||||
case .additionalChatListItems:
|
||||
return MutableAdditionalChatListItemsView(postbox: postbox)
|
||||
case let .cachedItem(id):
|
||||
return MutableCachedItemView(postbox: postbox, id: id)
|
||||
case let .peerPresences(ids):
|
||||
return MutablePeerPresencesView(postbox: postbox, ids: ids)
|
||||
case .synchronizeGroupMessageStats:
|
||||
return MutableSynchronizeGroupMessageStatsView(postbox: postbox)
|
||||
case .peerNotificationSettingsBehaviorTimestampView:
|
||||
return MutablePeerNotificationSettingsBehaviorTimestampView(postbox: postbox)
|
||||
case let .peerChatInclusion(peerId):
|
||||
return MutablePeerChatInclusionView(postbox: postbox, peerId: peerId)
|
||||
case let .basicPeer(peerId):
|
||||
return MutableBasicPeerView(postbox: postbox, peerId: peerId)
|
||||
case let .allChatListHoles(groupId):
|
||||
return MutableAllChatListHolesView(postbox: postbox, groupId: groupId)
|
||||
case let .itemCollectionInfos(namespaces):
|
||||
return MutableItemCollectionInfosView(postbox: postbox, namespaces: namespaces)
|
||||
case let .itemCollectionIds(namespaces):
|
||||
return MutableItemCollectionIdsView(postbox: postbox, namespaces: namespaces)
|
||||
case let .itemCollectionInfo(id):
|
||||
return MutableItemCollectionInfoView(postbox: postbox, id: id)
|
||||
case let .peerChatState(peerId):
|
||||
return MutablePeerChatStateView(postbox: postbox, peerId: peerId)
|
||||
case let .orderedItemList(id):
|
||||
return MutableOrderedItemListView(postbox: postbox, collectionId: id)
|
||||
case let .preferences(keys):
|
||||
return MutablePreferencesView(postbox: postbox, keys: keys)
|
||||
case let .globalMessageTags(globalTag, position, count, groupingPredicate):
|
||||
return MutableGlobalMessageTagsView(postbox: postbox, globalTag: globalTag, position: position, count: count, groupingPredicate: groupingPredicate)
|
||||
case let .peer(peerId, components):
|
||||
return MutablePeerView(postbox: postbox, peerId: peerId, components: components)
|
||||
case let .pendingMessageActions(type):
|
||||
return MutablePendingMessageActionsView(postbox: postbox, type: type)
|
||||
case let .invalidatedMessageHistoryTagSummaries(tagMask, namespace):
|
||||
return MutableInvalidatedMessageHistoryTagSummariesView(postbox: postbox, tagMask: tagMask, namespace: namespace)
|
||||
case let .pendingMessageActionsSummary(type, peerId, namespace):
|
||||
return MutablePendingMessageActionsSummaryView(postbox: postbox, type: type, peerId: peerId, namespace: namespace)
|
||||
case let .historyTagSummaryView(tag, peerId, namespace):
|
||||
return MutableMessageHistoryTagSummaryView(postbox: postbox, tag: tag, peerId: peerId, namespace: namespace)
|
||||
case let .cachedPeerData(peerId):
|
||||
return MutableCachedPeerDataView(postbox: postbox, peerId: peerId)
|
||||
case let .unreadCounts(items):
|
||||
return MutableUnreadMessageCountsView(postbox: postbox, items: items)
|
||||
case let .peerNotificationSettings(peerIds):
|
||||
return MutablePeerNotificationSettingsView(postbox: postbox, peerIds: peerIds)
|
||||
case .pendingPeerNotificationSettings:
|
||||
return MutablePendingPeerNotificationSettingsView(postbox: postbox)
|
||||
case let .messageOfInterestHole(location, namespace, count):
|
||||
return MutableMessageOfInterestHolesView(postbox: postbox, location: location, namespace: namespace, count: count)
|
||||
case let .localMessageTag(tag):
|
||||
return MutableLocalMessageTagsView(postbox: postbox, tag: tag)
|
||||
case let .messages(ids):
|
||||
return MutableMessagesView(postbox: postbox, ids: ids)
|
||||
case .additionalChatListItems:
|
||||
return MutableAdditionalChatListItemsView(postbox: postbox)
|
||||
case let .cachedItem(id):
|
||||
return MutableCachedItemView(postbox: postbox, id: id)
|
||||
case let .peerPresences(ids):
|
||||
return MutablePeerPresencesView(postbox: postbox, ids: ids)
|
||||
case .synchronizeGroupMessageStats:
|
||||
return MutableSynchronizeGroupMessageStatsView(postbox: postbox)
|
||||
case .peerNotificationSettingsBehaviorTimestampView:
|
||||
return MutablePeerNotificationSettingsBehaviorTimestampView(postbox: postbox)
|
||||
case let .peerChatInclusion(peerId):
|
||||
return MutablePeerChatInclusionView(postbox: postbox, peerId: peerId)
|
||||
case let .basicPeer(peerId):
|
||||
return MutableBasicPeerView(postbox: postbox, peerId: peerId)
|
||||
case let .allChatListHoles(groupId):
|
||||
return MutableAllChatListHolesView(postbox: postbox, groupId: groupId)
|
||||
case let .historyTagInfo(peerId, tag):
|
||||
return MutableHistoryTagInfoView(postbox: postbox, peerId: peerId, tag: tag)
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ public final class SearchDisplayController {
|
||||
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver), navigationBarHeight: navigationBarFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
public func activate(insertSubnode: (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode) {
|
||||
public func activate(insertSubnode: (ASDisplayNode, Bool) -> Void, placeholder: SearchBarPlaceholderNode?) {
|
||||
guard let (layout, navigationBarHeight) = self.containerLayout else {
|
||||
return
|
||||
}
|
||||
@ -110,19 +110,20 @@ public final class SearchDisplayController {
|
||||
|
||||
self.contentNode.containerLayoutUpdated(ContainerViewLayout(size: layout.size, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: layout.safeInsets, statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
|
||||
let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil)
|
||||
|
||||
let contentNodePosition = self.contentNode.layer.position
|
||||
|
||||
var contentNavigationBarHeight = navigationBarHeight
|
||||
if layout.statusBarHeight == nil {
|
||||
contentNavigationBarHeight += 28.0
|
||||
}
|
||||
|
||||
self.contentNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
|
||||
self.searchBar.placeholderString = placeholder.placeholderString
|
||||
if let placeholder = placeholder {
|
||||
let initialTextBackgroundFrame = placeholder.convert(placeholder.backgroundNode.frame, to: nil)
|
||||
|
||||
let contentNodePosition = self.contentNode.layer.position
|
||||
|
||||
self.contentNode.layer.animatePosition(from: CGPoint(x: contentNodePosition.x, y: contentNodePosition.y + (initialTextBackgroundFrame.maxY + 8.0 - contentNavigationBarHeight)), to: contentNodePosition, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
self.searchBar.placeholderString = placeholder.placeholderString
|
||||
}
|
||||
|
||||
let navigationBarFrame: CGRect
|
||||
switch self.mode {
|
||||
@ -149,18 +150,26 @@ public final class SearchDisplayController {
|
||||
self.searchBar.layout()
|
||||
|
||||
self.searchBar.activate()
|
||||
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
if let placeholder = placeholder {
|
||||
self.searchBar.animateIn(from: placeholder, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
} else {
|
||||
self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public func deactivate(placeholder: SearchBarPlaceholderNode?, animated: Bool = true) {
|
||||
self.searchBar.deactivate()
|
||||
|
||||
let searchBar = self.searchBar
|
||||
if let placeholder = placeholder {
|
||||
let searchBar = self.searchBar
|
||||
searchBar.transitionOut(to: placeholder, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate, completion: {
|
||||
[weak searchBar] in
|
||||
searchBar?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
searchBar.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak searchBar] _ in
|
||||
searchBar?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
let contentNode = self.contentNode
|
||||
|
@ -1064,7 +1064,7 @@ public final class AccountViewTracker {
|
||||
}
|
||||
}
|
||||
|
||||
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
public func aroundMessageHistoryViewForLocation(_ chatLocation: ChatLocation, index: MessageHistoryAnchorIndex, anchorIndex: MessageHistoryAnchorIndex, count: Int, clipHoles: Bool = true, fixedCombinedReadStates: MessageHistoryViewReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
if let account = self.account {
|
||||
let inputAnchor: HistoryViewInputAnchor
|
||||
switch index {
|
||||
@ -1075,7 +1075,7 @@ public final class AccountViewTracker {
|
||||
case let .message(index):
|
||||
inputAnchor = .index(index)
|
||||
}
|
||||
let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: inputAnchor, count: count, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||
let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: inputAnchor, count: count, clipHoles: clipHoles, fixedCombinedReadStates: fixedCombinedReadStates, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, namespaces: .not(Namespaces.Message.allScheduled), orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(chatLocation: chatLocation, additionalData: additionalData))
|
||||
return wrappedMessageHistorySignal(chatLocation: chatLocation, signal: signal, addHoleIfNeeded: false)
|
||||
} else {
|
||||
return .never()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1892,7 +1892,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
avatarNode.tapped = { [weak self] in
|
||||
self?.navigationButtonAction(.openChatInfo(expandAvatar: true))
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var expandAvatar = false
|
||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer, peer.smallProfileImage != nil {
|
||||
expandAvatar = true
|
||||
}
|
||||
strongSelf.navigationButtonAction(.openChatInfo(expandAvatar: expandAvatar))
|
||||
}
|
||||
}
|
||||
self.navigationItem.titleView = self.chatTitleView
|
||||
|
@ -87,7 +87,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
|
||||
return self.measureNode.measure(CGSize(width: 240.0, height: 160.0)).width
|
||||
}
|
||||
|
||||
func update(theme: PresentationTheme, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, alignment: NSTextAlignment = .left, animated: Bool, badgeAnimated: Bool = true) {
|
||||
func update(theme: PresentationTheme?, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, alignment: NSTextAlignment = .left, animated: Bool, badgeAnimated: Bool = true) {
|
||||
var transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
||||
|
||||
let previousContentSize = self.previousContentSize
|
||||
@ -263,7 +263,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
|
||||
var originY: CGFloat = 5.0
|
||||
switch mediaDownloadState {
|
||||
case .remote:
|
||||
if let image = PresentationResourcesChat.chatBubbleFileCloudFetchMediaIcon(theme) {
|
||||
if let theme = theme, let image = PresentationResourcesChat.chatBubbleFileCloudFetchMediaIcon(theme) {
|
||||
state = .customIcon(image)
|
||||
} else {
|
||||
state = .none
|
||||
|
@ -60,7 +60,7 @@ private final class ChatTitleNetworkStatusNode: ASDisplayNode {
|
||||
func updateTheme(theme: PresentationTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.medium(24.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
|
||||
self.activityIndicator.type = .custom(self.theme.rootController.navigationBar.primaryTextColor, 22.0, 1.5, false)
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
private var titleRightIcon: ChatTitleIcon = .none
|
||||
private var titleScamIcon = false
|
||||
|
||||
private var networkStatusNode: ChatTitleNetworkStatusNode?
|
||||
//private var networkStatusNode: ChatTitleNetworkStatusNode?
|
||||
|
||||
private var presenceManager: PeerPresenceStatusManager?
|
||||
|
||||
@ -125,7 +125,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
isOnline = true
|
||||
}
|
||||
|
||||
if isOnline || layout?.metrics.widthClass == .regular {
|
||||
/*if isOnline || layout?.metrics.widthClass == .regular {
|
||||
self.contentContainer.isHidden = false
|
||||
if let networkStatusNode = self.networkStatusNode {
|
||||
self.networkStatusNode = nil
|
||||
@ -155,7 +155,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
case .online:
|
||||
break
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
@ -164,6 +164,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
didSet {
|
||||
if self.networkState != oldValue {
|
||||
updateNetworkStatusNode(networkState: self.networkState, layout: self.layout)
|
||||
self.updateStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -277,175 +278,191 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
}
|
||||
|
||||
var state = ChatTitleActivityNodeState.none
|
||||
if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty, inputActivitiesAllowed {
|
||||
var stringValue = ""
|
||||
var first = true
|
||||
var mergedActivity = inputActivities[0].1
|
||||
for (_, activity) in inputActivities {
|
||||
if activity != mergedActivity {
|
||||
mergedActivity = .typingText
|
||||
break
|
||||
}
|
||||
switch self.networkState {
|
||||
case .waitingForNetwork, .connecting, .updating:
|
||||
var infoText: String
|
||||
switch self.networkState {
|
||||
case .waitingForNetwork:
|
||||
infoText = self.strings.ChatState_WaitingForNetwork
|
||||
case let .connecting(proxy):
|
||||
infoText = self.strings.ChatState_Connecting
|
||||
case .updating:
|
||||
infoText = self.strings.ChatState_Updating
|
||||
case .online:
|
||||
infoText = ""
|
||||
}
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
switch mergedActivity {
|
||||
case .typingText:
|
||||
stringValue = strings.Conversation_typing
|
||||
case .uploadingFile:
|
||||
stringValue = strings.Activity_UploadingDocument
|
||||
case .recordingVoice:
|
||||
stringValue = strings.Activity_RecordingAudio
|
||||
case .uploadingPhoto:
|
||||
stringValue = strings.Activity_UploadingPhoto
|
||||
case .uploadingVideo:
|
||||
stringValue = strings.Activity_UploadingVideo
|
||||
case .playingGame:
|
||||
stringValue = strings.Activity_PlayingGame
|
||||
case .recordingInstantVideo:
|
||||
stringValue = strings.Activity_RecordingVideoMessage
|
||||
case .uploadingInstantVideo:
|
||||
stringValue = strings.Activity_UploadingVideoMessage
|
||||
}
|
||||
} else {
|
||||
for (peer, _) in inputActivities {
|
||||
let title = peer.compactDisplayTitle
|
||||
if !title.isEmpty {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
stringValue += ", "
|
||||
}
|
||||
stringValue += title
|
||||
state = .info(NSAttributedString(string: infoText, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor), .generic)
|
||||
case .online:
|
||||
if let (peerId, inputActivities) = self.inputActivities, !inputActivities.isEmpty, inputActivitiesAllowed {
|
||||
var stringValue = ""
|
||||
var first = true
|
||||
var mergedActivity = inputActivities[0].1
|
||||
for (_, activity) in inputActivities {
|
||||
if activity != mergedActivity {
|
||||
mergedActivity = .typingText
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
let color = self.theme.rootController.navigationBar.accentTextColor
|
||||
let string = NSAttributedString(string: stringValue, font: Font.regular(13.0), textColor: color)
|
||||
switch mergedActivity {
|
||||
case .typingText:
|
||||
state = .typingText(string, color)
|
||||
case .recordingVoice:
|
||||
state = .recordingVoice(string, color)
|
||||
case .recordingInstantVideo:
|
||||
state = .recordingVideo(string, color)
|
||||
case .uploadingFile, .uploadingInstantVideo, .uploadingPhoto, .uploadingVideo:
|
||||
state = .uploading(string, color)
|
||||
case .playingGame:
|
||||
state = .playingGame(string, color)
|
||||
}
|
||||
} else {
|
||||
if let titleContent = self.titleContent {
|
||||
switch titleContent {
|
||||
case let .peer(peerView, onlineMemberCount, isScheduledMessages):
|
||||
if let peer = peerViewMainPeer(peerView) {
|
||||
let servicePeer = isServicePeer(peer)
|
||||
if peer.id == self.account.peerId || isScheduledMessages {
|
||||
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
} else if let user = peer as? TelegramUser {
|
||||
if servicePeer {
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
switch mergedActivity {
|
||||
case .typingText:
|
||||
stringValue = strings.Conversation_typing
|
||||
case .uploadingFile:
|
||||
stringValue = strings.Activity_UploadingDocument
|
||||
case .recordingVoice:
|
||||
stringValue = strings.Activity_RecordingAudio
|
||||
case .uploadingPhoto:
|
||||
stringValue = strings.Activity_UploadingPhoto
|
||||
case .uploadingVideo:
|
||||
stringValue = strings.Activity_UploadingVideo
|
||||
case .playingGame:
|
||||
stringValue = strings.Activity_PlayingGame
|
||||
case .recordingInstantVideo:
|
||||
stringValue = strings.Activity_RecordingVideoMessage
|
||||
case .uploadingInstantVideo:
|
||||
stringValue = strings.Activity_UploadingVideoMessage
|
||||
}
|
||||
} else {
|
||||
for (peer, _) in inputActivities {
|
||||
let title = peer.compactDisplayTitle
|
||||
if !title.isEmpty {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
stringValue += ", "
|
||||
}
|
||||
stringValue += title
|
||||
}
|
||||
}
|
||||
}
|
||||
let color = self.theme.rootController.navigationBar.accentTextColor
|
||||
let string = NSAttributedString(string: stringValue, font: Font.regular(13.0), textColor: color)
|
||||
switch mergedActivity {
|
||||
case .typingText:
|
||||
state = .typingText(string, color)
|
||||
case .recordingVoice:
|
||||
state = .recordingVoice(string, color)
|
||||
case .recordingInstantVideo:
|
||||
state = .recordingVideo(string, color)
|
||||
case .uploadingFile, .uploadingInstantVideo, .uploadingPhoto, .uploadingVideo:
|
||||
state = .uploading(string, color)
|
||||
case .playingGame:
|
||||
state = .playingGame(string, color)
|
||||
}
|
||||
} else {
|
||||
if let titleContent = self.titleContent {
|
||||
switch titleContent {
|
||||
case let .peer(peerView, onlineMemberCount, isScheduledMessages):
|
||||
if let peer = peerViewMainPeer(peerView) {
|
||||
let servicePeer = isServicePeer(peer)
|
||||
if peer.id == self.account.peerId || isScheduledMessages {
|
||||
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
} else if user.flags.contains(.isSupport) {
|
||||
let statusText = self.strings.Bot_GenericSupportStatus
|
||||
|
||||
let string = NSAttributedString(string: statusText, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
} else if let _ = user.botInfo {
|
||||
let statusText = self.strings.Bot_GenericBotStatus
|
||||
|
||||
let string = NSAttributedString(string: statusText, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
} else if let peer = peerViewMainPeer(peerView) {
|
||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||
let userPresence: TelegramUserPresence
|
||||
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence {
|
||||
userPresence = presence
|
||||
self.presenceManager?.reset(presence: presence)
|
||||
} else if let user = peer as? TelegramUser {
|
||||
if servicePeer {
|
||||
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
} else if user.flags.contains(.isSupport) {
|
||||
let statusText = self.strings.Bot_GenericSupportStatus
|
||||
|
||||
let string = NSAttributedString(string: statusText, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
} else if let _ = user.botInfo {
|
||||
let statusText = self.strings.Bot_GenericBotStatus
|
||||
|
||||
let string = NSAttributedString(string: statusText, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
} else if let peer = peerViewMainPeer(peerView) {
|
||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||
let userPresence: TelegramUserPresence
|
||||
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence {
|
||||
userPresence = presence
|
||||
self.presenceManager?.reset(presence: presence)
|
||||
} else {
|
||||
userPresence = TelegramUserPresence(status: .none, lastActivity: 0)
|
||||
}
|
||||
let (string, activity) = stringAndActivityForUserPresence(strings: self.strings, dateTimeFormat: self.dateTimeFormat, presence: userPresence, relativeTo: Int32(timestamp))
|
||||
let attributedString = NSAttributedString(string: string, font: Font.regular(13.0), textColor: activity ? self.theme.rootController.navigationBar.accentTextColor : self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(attributedString, activity ? .online : .lastSeenTime)
|
||||
} else {
|
||||
userPresence = TelegramUserPresence(status: .none, lastActivity: 0)
|
||||
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
}
|
||||
let (string, activity) = stringAndActivityForUserPresence(strings: self.strings, dateTimeFormat: self.dateTimeFormat, presence: userPresence, relativeTo: Int32(timestamp))
|
||||
let attributedString = NSAttributedString(string: string, font: Font.regular(13.0), textColor: activity ? self.theme.rootController.navigationBar.accentTextColor : self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(attributedString, activity ? .online : .lastSeenTime)
|
||||
} else {
|
||||
let string = NSAttributedString(string: "", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
var onlineCount = 0
|
||||
if let cachedGroupData = peerView.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
|
||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||
for participant in participants.participants {
|
||||
if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence {
|
||||
let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: Int32(timestamp))
|
||||
switch relativeStatus {
|
||||
case .online:
|
||||
onlineCount += 1
|
||||
default:
|
||||
break
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
var onlineCount = 0
|
||||
if let cachedGroupData = peerView.cachedData as? CachedGroupData, let participants = cachedGroupData.participants {
|
||||
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
|
||||
for participant in participants.participants {
|
||||
if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence {
|
||||
let relativeStatus = relativeUserPresenceStatus(presence, relativeTo: Int32(timestamp))
|
||||
switch relativeStatus {
|
||||
case .online:
|
||||
onlineCount += 1
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if onlineCount > 1 {
|
||||
let string = NSMutableAttributedString()
|
||||
|
||||
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(group.participantCount))), ", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
||||
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
||||
state = .info(string, .generic)
|
||||
} else {
|
||||
let string = NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
}
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
||||
if memberCount == 0 {
|
||||
let string: NSAttributedString
|
||||
if case .group = channel.info {
|
||||
string = NSAttributedString(string: strings.Group_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
} else {
|
||||
string = NSAttributedString(string: strings.Channel_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
}
|
||||
if onlineCount > 1 {
|
||||
let string = NSMutableAttributedString()
|
||||
|
||||
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(group.participantCount))), ", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
||||
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
||||
state = .info(string, .generic)
|
||||
} else {
|
||||
if case .group = channel.info, let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 {
|
||||
let string = NSMutableAttributedString()
|
||||
|
||||
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
||||
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineMemberCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
||||
let string = NSAttributedString(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
}
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount {
|
||||
if memberCount == 0 {
|
||||
let string: NSAttributedString
|
||||
if case .group = channel.info {
|
||||
string = NSAttributedString(string: strings.Group_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
} else {
|
||||
string = NSAttributedString(string: strings.Channel_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
}
|
||||
state = .info(string, .generic)
|
||||
} else {
|
||||
let membersString: String
|
||||
if case .group = channel.info {
|
||||
membersString = strings.Conversation_StatusMembers(memberCount)
|
||||
if case .group = channel.info, let onlineMemberCount = onlineMemberCount, onlineMemberCount > 1 {
|
||||
let string = NSMutableAttributedString()
|
||||
|
||||
string.append(NSAttributedString(string: "\(strings.Conversation_StatusMembers(Int32(memberCount))), ", font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
||||
string.append(NSAttributedString(string: strings.Conversation_StatusOnline(Int32(onlineMemberCount)), font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor))
|
||||
state = .info(string, .generic)
|
||||
} else {
|
||||
membersString = strings.Conversation_StatusSubscribers(memberCount)
|
||||
let membersString: String
|
||||
if case .group = channel.info {
|
||||
membersString = strings.Conversation_StatusMembers(memberCount)
|
||||
} else {
|
||||
membersString = strings.Conversation_StatusSubscribers(memberCount)
|
||||
}
|
||||
let string = NSAttributedString(string: membersString, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
}
|
||||
let string = NSAttributedString(string: membersString, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch channel.info {
|
||||
case .group:
|
||||
let string = NSAttributedString(string: strings.Group_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
case .broadcast:
|
||||
let string = NSAttributedString(string: strings.Channel_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
} else {
|
||||
switch channel.info {
|
||||
case .group:
|
||||
let string = NSAttributedString(string: strings.Group_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
case .broadcast:
|
||||
let string = NSAttributedString(string: strings.Channel_Status, font: Font.regular(13.0), textColor: self.theme.rootController.navigationBar.secondaryTextColor)
|
||||
state = .info(string, .generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.accessibilityLabel = self.titleNode.attributedText?.string
|
||||
self.accessibilityValue = state.string
|
||||
} else {
|
||||
self.accessibilityLabel = nil
|
||||
}
|
||||
|
||||
self.accessibilityLabel = self.titleNode.attributedText?.string
|
||||
self.accessibilityValue = state.string
|
||||
} else {
|
||||
self.accessibilityLabel = nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -552,7 +569,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
self.networkStatusNode?.updateTheme(theme: theme)
|
||||
//self.networkStatusNode?.updateTheme(theme: theme)
|
||||
let titleContent = self.titleContent
|
||||
self.titleContent = titleContent
|
||||
self.updateStatus()
|
||||
@ -612,7 +629,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
|
||||
let titleSideInset: CGFloat = 3.0
|
||||
if size.height > 40.0 {
|
||||
var titleSize = self.titleNode.updateLayout(CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0, height: size.height))
|
||||
var titleSize = self.titleNode.updateLayout(CGSize(width: clearBounds.width - leftIconWidth - credibilityIconWidth - rightIconWidth - titleSideInset * 2.0 - leftInset, height: size.height))
|
||||
titleSize.width += credibilityIconWidth
|
||||
let activitySize = self.activityNode.updateLayout(clearBounds.size, alignment: .left)
|
||||
let titleInfoSpacing: CGFloat = 0.0
|
||||
@ -663,10 +680,10 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
|
||||
}
|
||||
}
|
||||
|
||||
if let networkStatusNode = self.networkStatusNode {
|
||||
/*if let networkStatusNode = self.networkStatusNode {
|
||||
transition.updateFrame(node: networkStatusNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
networkStatusNode.updateLayout(size: size, transition: transition)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@objc func buttonPressed() {
|
||||
|
@ -33,7 +33,7 @@ func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMedia
|
||||
}
|
||||
}
|
||||
|
||||
func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal<FileMediaResourceStatus, NoError> {
|
||||
func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false) -> Signal<FileMediaResourceStatus, NoError> {
|
||||
let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) |> map { status -> MediaPlayerPlaybackStatus? in
|
||||
return status?.status
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import AccountContext
|
||||
import RadialStatusNode
|
||||
import PhotoResources
|
||||
import MusicAlbumArtResources
|
||||
import UniversalMediaPlayer
|
||||
|
||||
private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:])
|
||||
|
||||
@ -124,6 +125,7 @@ private struct FetchControls {
|
||||
private enum FileIconImage: Equatable {
|
||||
case imageRepresentation(TelegramMediaFile, TelegramMediaImageRepresentation)
|
||||
case albumArt(TelegramMediaFile, SharedMediaPlaybackAlbumArt)
|
||||
case roundVideo(TelegramMediaFile)
|
||||
|
||||
static func ==(lhs: FileIconImage, rhs: FileIconImage) -> Bool {
|
||||
switch lhs {
|
||||
@ -139,6 +141,12 @@ private enum FileIconImage: Equatable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .roundVideo(file):
|
||||
if case .roundVideo(file) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,6 +167,10 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
private let statusButtonNode: HighlightTrackingButtonNode
|
||||
private let statusNode: RadialStatusNode
|
||||
|
||||
private var waveformNode: AudioWaveformNode?
|
||||
private var waveformForegroundNode: AudioWaveformNode?
|
||||
private var waveformScrubbingNode: MediaPlayerScrubbingNode?
|
||||
|
||||
private var currentIconImage: FileIconImage?
|
||||
private var currentMedia: Media?
|
||||
|
||||
@ -167,6 +179,8 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
private var fetchStatus: MediaResourceStatus?
|
||||
private var resourceStatus: FileMediaResourceMediaStatus?
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
private let playbackStatusDisposable = MetaDisposable()
|
||||
private let playbackStatus = Promise<MediaPlayerStatus>()
|
||||
|
||||
private var downloadStatusIconNode: ASImageNode
|
||||
private var linearProgressNode: ASDisplayNode
|
||||
@ -226,7 +240,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
self.downloadStatusIconNode.displayWithoutProcessing = true
|
||||
|
||||
self.progressNode = RadialProgressNode(theme: RadialProgressTheme(backgroundColor: .black, foregroundColor: .white, icon: nil))
|
||||
self.progressNode.isLayerBacked = true
|
||||
//self.progressNode.isLayerBacked = true
|
||||
|
||||
self.linearProgressNode = ASDisplayNode()
|
||||
self.linearProgressNode.isLayerBacked = true
|
||||
@ -235,6 +249,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.progressNode)
|
||||
self.addSubnode(self.descriptionNode)
|
||||
self.addSubnode(self.descriptionProgressNode)
|
||||
self.addSubnode(self.extensionIconNode)
|
||||
@ -335,9 +350,13 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
var iconImage: FileIconImage?
|
||||
var updateIconImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||
var updatedStatusSignal: Signal<FileMediaResourceStatus, NoError>?
|
||||
var updatedPlaybackStatusSignal: Signal<MediaPlayerStatus, NoError>?
|
||||
var updatedFetchControls: FetchControls?
|
||||
var waveform: AudioWaveform?
|
||||
|
||||
var isAudio = false
|
||||
var isVoice = false
|
||||
var isInstantVideo = false
|
||||
|
||||
let message = item.message
|
||||
|
||||
@ -346,9 +365,12 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
if let file = media as? TelegramMediaFile {
|
||||
selectedMedia = file
|
||||
|
||||
isInstantVideo = file.isInstantVideo
|
||||
|
||||
for attribute in file.attributes {
|
||||
if case let .Audio(voice, _, title, performer, _) = attribute {
|
||||
if case let .Audio(voice, _, title, performer, waveformValue) = attribute {
|
||||
isAudio = true
|
||||
isVoice = voice
|
||||
|
||||
titleText = NSAttributedString(string: title ?? (file.fileName ?? "Unknown Track"), font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor)
|
||||
|
||||
@ -365,11 +387,21 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
|
||||
if !voice {
|
||||
iconImage = .albumArt(file, SharedMediaPlaybackAlbumArt(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: false)))
|
||||
} else {
|
||||
titleText = NSAttributedString(string: " ", font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor)
|
||||
descriptionText = NSAttributedString(string: item.message.author?.displayTitle(strings: item.strings, displayOrder: .firstLast) ?? " ", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor)
|
||||
waveformValue?.withDataNoCopy { data in
|
||||
waveform = AudioWaveform(bitstream: data, bitsPerSample: 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isAudio {
|
||||
if isInstantVideo {
|
||||
titleText = NSAttributedString(string: item.strings.Message_VideoMessage, font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor)
|
||||
descriptionText = NSAttributedString(string: item.message.author?.displayTitle(strings: item.strings, displayOrder: .firstLast) ?? " ", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor)
|
||||
iconImage = .roundVideo(file)
|
||||
} else if !isAudio {
|
||||
let fileName: String = file.fileName ?? ""
|
||||
titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
|
||||
|
||||
@ -402,7 +434,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
}
|
||||
}
|
||||
|
||||
if isAudio {
|
||||
if isAudio && !isVoice {
|
||||
leftInset += 14.0
|
||||
}
|
||||
|
||||
@ -435,9 +467,9 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
}
|
||||
|
||||
if statusUpdated {
|
||||
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false)
|
||||
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isSharedMedia: true)
|
||||
|
||||
if isAudio {
|
||||
if isAudio || isInstantVideo {
|
||||
if let currentUpdatedStatusSignal = updatedStatusSignal {
|
||||
updatedStatusSignal = currentUpdatedStatusSignal
|
||||
|> map { status in
|
||||
@ -450,6 +482,9 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
if isVoice {
|
||||
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -472,6 +507,11 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
let imageCorners = ImageCorners(topLeft: .Corner(4.0), topRight: .Corner(4.0), bottomLeft: .Corner(4.0), bottomRight: .Corner(4.0))
|
||||
let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor)
|
||||
iconImageApply = iconImageLayout(arguments)
|
||||
case let .roundVideo(file):
|
||||
let iconSize = CGSize(width: 42.0, height: 42.0)
|
||||
let imageCorners = ImageCorners(topLeft: .Corner(iconSize.width / 2.0), topRight: .Corner(iconSize.width / 2.0), bottomLeft: .Corner(iconSize.width / 2.0), bottomRight: .Corner(iconSize.width / 2.0))
|
||||
let arguments = TransformImageArguments(corners: imageCorners, imageSize: (file.dimensions ?? PixelDimensions(width: 320, height: 320)).cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor)
|
||||
iconImageApply = iconImageLayout(arguments)
|
||||
}
|
||||
}
|
||||
|
||||
@ -482,7 +522,8 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
updateIconImageSignal = chatWebpageSnippetFile(account: item.context.account, fileReference: .message(message: MessageReference(message), media: file), representation: representation)
|
||||
case let .albumArt(file, albumArt):
|
||||
updateIconImageSignal = playerAlbumArt(postbox: item.context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true)
|
||||
|
||||
case let .roundVideo(file):
|
||||
updateIconImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, videoReference: FileMediaReference.message(message: MessageReference(message), media: file), autoFetchFullSizeThumbnail: true)
|
||||
}
|
||||
} else {
|
||||
updateIconImageSignal = .complete()
|
||||
@ -581,6 +622,48 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
|
||||
strongSelf.currentIconImage = iconImage
|
||||
|
||||
if isVoice {
|
||||
let waveformNode: AudioWaveformNode
|
||||
let waveformForegroundNode: AudioWaveformNode
|
||||
let waveformScrubbingNode: MediaPlayerScrubbingNode
|
||||
if let current = strongSelf.waveformNode {
|
||||
waveformNode = current
|
||||
} else {
|
||||
waveformNode = AudioWaveformNode()
|
||||
waveformNode.isLayerBacked = true
|
||||
strongSelf.waveformNode = waveformNode
|
||||
strongSelf.addSubnode(waveformNode)
|
||||
}
|
||||
if let current = strongSelf.waveformForegroundNode {
|
||||
waveformForegroundNode = current
|
||||
} else {
|
||||
waveformForegroundNode = AudioWaveformNode()
|
||||
waveformForegroundNode.isLayerBacked = true
|
||||
strongSelf.waveformForegroundNode = waveformForegroundNode
|
||||
strongSelf.addSubnode(waveformForegroundNode)
|
||||
}
|
||||
if let current = strongSelf.waveformScrubbingNode {
|
||||
waveformScrubbingNode = current
|
||||
} else {
|
||||
waveformScrubbingNode = MediaPlayerScrubbingNode(content: .custom(backgroundNode: waveformNode, foregroundContentNode: waveformForegroundNode))
|
||||
waveformScrubbingNode.hitTestSlop = UIEdgeInsets(top: -10.0, left: 0.0, bottom: -10.0, right: 0.0)
|
||||
waveformScrubbingNode.seek = { timestamp in
|
||||
if let strongSelf = self, let context = strongSelf.context, let message = strongSelf.message, let type = peerMessageMediaPlayerType(message) {
|
||||
context.sharedContext.mediaManager.playlistControl(.seek(timestamp), type: type)
|
||||
}
|
||||
}
|
||||
waveformScrubbingNode.enableScrubbing = false
|
||||
waveformScrubbingNode.status = strongSelf.playbackStatus.get()
|
||||
strongSelf.waveformScrubbingNode = waveformScrubbingNode
|
||||
strongSelf.addSubnode(waveformScrubbingNode)
|
||||
}
|
||||
|
||||
strongSelf.waveformScrubbingNode?.frame = CGRect(origin: CGPoint(x: leftOffset + leftInset, y: 10.0), size: CGSize(width: params.width - (leftOffset + leftInset) - 16.0, height: 12.0))
|
||||
|
||||
waveformNode.setup(color: item.theme.list.controlSecondaryColor, waveform: waveform)
|
||||
waveformForegroundNode.setup(color: item.theme.list.itemAccentColor, waveform: waveform)
|
||||
}
|
||||
|
||||
if let iconImageApply = iconImageApply {
|
||||
if let updateImageSignal = updateIconImageSignal {
|
||||
strongSelf.iconImageNode.setSignal(updateImageSignal)
|
||||
@ -632,10 +715,24 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
|
||||
transition.updateFrame(node: strongSelf.downloadStatusIconNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset, y: strongSelf.descriptionNode.frame.minY + floor((strongSelf.descriptionNode.frame.height - 11.0) / 2.0)), size: CGSize(width: 11.0, height: 11.0)))
|
||||
|
||||
let progressSize: CGFloat = 40.0
|
||||
transition.updateFrame(node: strongSelf.progressNode, frame: CGRect(origin: CGPoint(x: leftOffset + params.leftInset + floor((leftInset - params.leftInset - progressSize) / 2.0), y: floor((nodeLayout.contentSize.height - progressSize) / 2.0)), size: CGSize(width: progressSize, height: progressSize)))
|
||||
|
||||
if let updatedFetchControls = updatedFetchControls {
|
||||
let _ = strongSelf.fetchControls.swap(updatedFetchControls)
|
||||
}
|
||||
|
||||
if let updatedPlaybackStatusSignal = updatedPlaybackStatusSignal {
|
||||
strongSelf.playbackStatus.set(updatedPlaybackStatusSignal)
|
||||
/*strongSelf.playbackStatusDisposable.set((updatedPlaybackStatusSignal |> deliverOnMainQueue).start(next: { [weak strongSelf] status in
|
||||
displayLinkDispatcher.dispatch {
|
||||
if let strongSelf = strongSelf {
|
||||
strongSelf.playerStatus = status
|
||||
}
|
||||
}
|
||||
}))*/
|
||||
}
|
||||
|
||||
strongSelf.updateStatus(transition: transition)
|
||||
}
|
||||
})
|
||||
@ -648,25 +745,34 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
}
|
||||
|
||||
var isAudio = false
|
||||
var isVoice = false
|
||||
var isInstantVideo = false
|
||||
if let file = media as? TelegramMediaFile {
|
||||
isAudio = file.isMusic || file.isVoice
|
||||
isVoice = file.isVoice
|
||||
isInstantVideo = file.isInstantVideo
|
||||
}
|
||||
|
||||
self.progressNode.isHidden = !isVoice
|
||||
|
||||
var enableScrubbing = false
|
||||
var musicIsPlaying: Bool?
|
||||
var statusState: RadialStatusNodeState = .none
|
||||
if !isAudio {
|
||||
if !isAudio && !isInstantVideo {
|
||||
self.updateProgressFrame(size: contentSize, leftInset: layoutParams.leftInset, rightInset: layoutParams.rightInset, transition: .immediate)
|
||||
} else {
|
||||
switch fetchStatus {
|
||||
case let .Fetching(_, progress):
|
||||
let adjustedProgress = max(progress, 0.027)
|
||||
statusState = .cloudProgress(color: item.theme.list.itemAccentColor, strokeBackgroundColor: item.theme.list.itemAccentColor.withAlphaComponent(0.5), lineWidth: 2.0, value: CGFloat(adjustedProgress))
|
||||
case .Local:
|
||||
break
|
||||
case .Remote:
|
||||
if let image = PresentationResourcesItemList.cloudFetchIcon(item.theme) {
|
||||
statusState = .customIcon(image)
|
||||
}
|
||||
if !isVoice && !isInstantVideo {
|
||||
switch fetchStatus {
|
||||
case let .Fetching(_, progress):
|
||||
let adjustedProgress = max(progress, 0.027)
|
||||
statusState = .cloudProgress(color: item.theme.list.itemAccentColor, strokeBackgroundColor: item.theme.list.itemAccentColor.withAlphaComponent(0.5), lineWidth: 2.0, value: CGFloat(adjustedProgress))
|
||||
case .Local:
|
||||
break
|
||||
case .Remote:
|
||||
if let image = PresentationResourcesItemList.cloudFetchIcon(item.theme) {
|
||||
statusState = .customIcon(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.statusNode.transitionToState(statusState, completion: {})
|
||||
self.statusButtonNode.isUserInteractionEnabled = statusState != .none
|
||||
@ -691,6 +797,7 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
}
|
||||
}
|
||||
case let .playbackStatus(playbackStatus):
|
||||
enableScrubbing = true
|
||||
switch playbackStatus {
|
||||
case .playing:
|
||||
musicIsPlaying = true
|
||||
@ -701,7 +808,8 @@ final class ListMessageFileItemNode: ListMessageNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
if let musicIsPlaying = musicIsPlaying {
|
||||
self.waveformScrubbingNode?.enableScrubbing = enableScrubbing
|
||||
if let musicIsPlaying = musicIsPlaying, !isVoice {
|
||||
if self.playbackOverlayNode == nil {
|
||||
let playbackOverlayNode = ListMessagePlaybackOverlayNode()
|
||||
playbackOverlayNode.frame = self.iconImageNode.frame
|
||||
|
@ -13,6 +13,8 @@ import TelegramUIPreferences
|
||||
final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let paneInteraction: PeerInfoPaneInteraction
|
||||
private let controllerInteraction: ChatControllerInteraction
|
||||
|
||||
private let listNode: ChatHistoryListNode
|
||||
|
||||
@ -24,14 +26,25 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
return self.ready.get()
|
||||
}
|
||||
|
||||
private let selectedMessagesPromise = Promise<Set<MessageId>?>(nil)
|
||||
private var selectedMessages: Set<MessageId>? {
|
||||
didSet {
|
||||
if self.selectedMessages != oldValue {
|
||||
self.selectedMessagesPromise.set(.single(self.selectedMessages))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var hiddenMediaDisposable: Disposable?
|
||||
|
||||
init(context: AccountContext, openMessage: @escaping (MessageId) -> Bool, peerId: PeerId, tagMask: MessageTags) {
|
||||
init(context: AccountContext, openMessage: @escaping (MessageId) -> Bool, peerId: PeerId, tagMask: MessageTags, interaction: PeerInfoPaneInteraction) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.paneInteraction = interaction
|
||||
|
||||
var openMessageImpl: ((MessageId) -> Bool)?
|
||||
let controllerInteraction = ChatControllerInteraction(openMessage: { message, _ in
|
||||
var toggleMessageSelectionImpl: (([MessageId]) -> Void)?
|
||||
self.controllerInteraction = ChatControllerInteraction(openMessage: { message, _ in
|
||||
return openMessageImpl?(message.id) ?? false
|
||||
}, openPeer: { _, _, _ in
|
||||
}, openPeerMention: { _ in
|
||||
@ -39,7 +52,8 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
}, openMessageContextActions: { _, _, _, _ in
|
||||
}, navigateToMessage: { _, _ in
|
||||
}, tapMessage: nil, clickThroughMessage: {
|
||||
}, toggleMessagesSelection: { _, _ in
|
||||
}, toggleMessagesSelection: { ids, _ in
|
||||
toggleMessageSelectionImpl?(ids)
|
||||
}, sendCurrentMessage: { _ in
|
||||
}, sendMessage: { _ in
|
||||
}, sendSticker: { _, _, _, _ in
|
||||
@ -97,8 +111,13 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
}, requestMessageUpdate: { _ in
|
||||
}, cancelInteractiveKeyboardGestures: {
|
||||
}, automaticMediaDownloadSettings: MediaAutoDownloadSettings.defaultSettings, pollActionState: ChatInterfacePollActionState(), stickerSettings: ChatInterfaceStickerSettings(loopAnimatedStickers: false))
|
||||
self.controllerInteraction.selectionState = self.paneInteraction.selectedMessageIds.flatMap { ids in
|
||||
return ChatInterfaceSelectionState(selectedIds: ids)
|
||||
}
|
||||
self.selectedMessages = self.paneInteraction.selectedMessageIds
|
||||
self.selectedMessagesPromise.set(.single(self.selectedMessages))
|
||||
|
||||
self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: nil, controllerInteraction: controllerInteraction, selectedMessages: .single(nil), mode: .list(search: false, reversed: false))
|
||||
self.listNode = ChatHistoryListNode(context: context, chatLocation: .peer(peerId), tagMask: tagMask, subject: nil, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), mode: .list(search: false, reversed: false))
|
||||
|
||||
super.init()
|
||||
|
||||
@ -106,6 +125,12 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
return openMessage(id)
|
||||
}
|
||||
|
||||
toggleMessageSelectionImpl = { [weak self] ids in
|
||||
for id in ids {
|
||||
self?.paneInteraction.toggleMessageSelected(id)
|
||||
}
|
||||
}
|
||||
|
||||
self.hiddenMediaDisposable = context.sharedContext.mediaManager.galleryHiddenMediaManager.hiddenIds().start(next: { [weak self] ids in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -116,7 +141,7 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
hiddenMedia[messageId] = [media]
|
||||
}
|
||||
}
|
||||
controllerInteraction.hiddenMedia = hiddenMedia
|
||||
strongSelf.controllerInteraction.hiddenMedia = hiddenMedia
|
||||
strongSelf.listNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ListMessageNode {
|
||||
itemNode.updateHiddenMedia()
|
||||
@ -171,4 +196,16 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
}
|
||||
return transitionNode
|
||||
}
|
||||
|
||||
func updateSelectedMessages(animated: Bool) {
|
||||
self.controllerInteraction.selectionState = self.paneInteraction.selectedMessageIds.flatMap { ids in
|
||||
return ChatInterfaceSelectionState(selectedIds: ids)
|
||||
}
|
||||
self.listNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
itemNode.updateSelectionState(animated: animated)
|
||||
}
|
||||
}
|
||||
self.selectedMessages = self.paneInteraction.selectedMessageIds
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,165 @@
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import ContextUI
|
||||
import PhotoResources
|
||||
import TelegramUIPreferences
|
||||
import ItemListPeerItem
|
||||
import MergeLists
|
||||
import ItemListUI
|
||||
|
||||
private struct GroupsInCommonListTransaction {
|
||||
let deletions: [ListViewDeleteItem]
|
||||
let insertions: [ListViewInsertItem]
|
||||
let updates: [ListViewUpdateItem]
|
||||
}
|
||||
|
||||
private struct GroupsInCommonListEntry: Comparable, Identifiable {
|
||||
var index: Int
|
||||
var peer: Peer
|
||||
|
||||
var stableId: PeerId {
|
||||
return self.peer.id
|
||||
}
|
||||
|
||||
static func ==(lhs: GroupsInCommonListEntry, rhs: GroupsInCommonListEntry) -> Bool {
|
||||
return lhs.peer.isEqual(rhs.peer)
|
||||
}
|
||||
|
||||
static func <(lhs: GroupsInCommonListEntry, rhs: GroupsInCommonListEntry) -> Bool {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void) -> ListViewItem {
|
||||
let peer = self.peer
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: self.peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: 0, action: {
|
||||
openPeer(peer)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in
|
||||
}, removePeer: { _ in
|
||||
}, contextAction: { node, gesture in
|
||||
//arguments.contextAction(peer, node, gesture)
|
||||
}, hasTopStripe: false, noInsets: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func preparedTransition(from fromEntries: [GroupsInCommonListEntry], to toEntries: [GroupsInCommonListEntry], context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void) -> GroupsInCommonListTransaction {
|
||||
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(context: context, presentationData: presentationData, openPeer: openPeer), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, openPeer: openPeer), directionHint: nil) }
|
||||
|
||||
return GroupsInCommonListTransaction(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
|
||||
final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let paneInteraction: PeerInfoPaneInteraction
|
||||
|
||||
private let listNode: ListView
|
||||
private var peers: [Peer] = []
|
||||
private var currentEntries: [GroupsInCommonListEntry] = []
|
||||
private var enqueuedTransactions: [GroupsInCommonListTransaction] = []
|
||||
|
||||
private var currentParams: (size: CGSize, isScrollingLockedAtTop: Bool, presentationData: PresentationData)?
|
||||
|
||||
private let ready = Promise<Bool>()
|
||||
private var didSetReady: Bool = false
|
||||
var isReady: Signal<Bool, NoError> {
|
||||
return self.ready.get()
|
||||
}
|
||||
|
||||
init(context: AccountContext, peerId: PeerId, interaction: PeerInfoPaneInteraction, peers: [Peer]) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.paneInteraction = interaction
|
||||
|
||||
self.listNode = ListView()
|
||||
|
||||
super.init()
|
||||
|
||||
self.listNode.preloadPages = true
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
self.peers = peers
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
func scrollToTop() -> Bool {
|
||||
if !self.listNode.scrollToOffsetFromTop(0.0) {
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Spring(duration: 0.4), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
||||
let isFirstLayout = self.currentParams == nil
|
||||
self.currentParams = (size, isScrollingLockedAtTop, presentationData)
|
||||
|
||||
transition.updateFrame(node: self.listNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
let (duration, curve) = listViewAnimationDurationAndCurve(transition: transition)
|
||||
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), headerInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
self.listNode.scrollEnabled = !isScrollingLockedAtTop
|
||||
|
||||
if isFirstLayout {
|
||||
self.updatePeers(peers: self.peers, presentationData: presentationData)
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePeers(peers: [Peer], presentationData: PresentationData) {
|
||||
var entries: [GroupsInCommonListEntry] = []
|
||||
for peer in peers {
|
||||
entries.append(GroupsInCommonListEntry(index: entries.count, peer: peer))
|
||||
}
|
||||
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, openPeer: { [weak self] peer in
|
||||
self?.paneInteraction.openPeer(peer)
|
||||
})
|
||||
self.currentEntries = entries
|
||||
self.enqueuedTransactions.append(transaction)
|
||||
self.dequeueTransaction()
|
||||
}
|
||||
|
||||
private func dequeueTransaction() {
|
||||
guard let (layout, _, _) = self.currentParams, let transaction = self.enqueuedTransactions.first else {
|
||||
return
|
||||
}
|
||||
|
||||
self.enqueuedTransactions.remove(at: 0)
|
||||
|
||||
var options = ListViewDeleteAndInsertOptions()
|
||||
options.insert(.Synchronous)
|
||||
|
||||
self.listNode.transaction(deleteIndices: transaction.deletions, insertIndicesAndItems: transaction.insertions, updateIndicesAndItems: transaction.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if !strongSelf.didSetReady {
|
||||
strongSelf.didSetReady = true
|
||||
strongSelf.ready.set(.single(true))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func findLoadedMessage(id: MessageId) -> Message? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func transitionNodeForGallery(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateSelectedMessages(animated: Bool) {
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,98 @@
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
|
||||
enum PeerInfoScreenActionColor {
|
||||
case accent
|
||||
case destructive
|
||||
}
|
||||
|
||||
final class PeerInfoScreenActionItem: PeerInfoScreenItem {
|
||||
let id: AnyHashable
|
||||
let text: String
|
||||
let color: PeerInfoScreenActionColor
|
||||
let action: (() -> Void)?
|
||||
|
||||
init(id: AnyHashable, text: String, color: PeerInfoScreenActionColor = .accent, action: (() -> Void)?) {
|
||||
self.id = id
|
||||
self.text = text
|
||||
self.color = color
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func node() -> PeerInfoScreenItemNode {
|
||||
return PeerInfoScreenActionItemNode()
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerInfoScreenActionItemNode: PeerInfoScreenItemNode {
|
||||
private let selectionNode: PeerInfoScreenSelectableBackgroundNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let bottomSeparatorNode: ASDisplayNode
|
||||
|
||||
private var item: PeerInfoScreenActionItem?
|
||||
|
||||
override init() {
|
||||
var bringToFrontForHighlightImpl: (() -> Void)?
|
||||
self.selectionNode = PeerInfoScreenSelectableBackgroundNode(bringToFrontForHighlight: { bringToFrontForHighlightImpl?() })
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
self.bottomSeparatorNode = ASDisplayNode()
|
||||
self.bottomSeparatorNode.isLayerBacked = true
|
||||
|
||||
super.init()
|
||||
|
||||
bringToFrontForHighlightImpl = { [weak self] in
|
||||
self?.bringToFrontForHighlight?()
|
||||
}
|
||||
|
||||
self.addSubnode(self.bottomSeparatorNode)
|
||||
self.addSubnode(self.selectionNode)
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
override func update(width: CGFloat, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
guard let item = item as? PeerInfoScreenActionItem else {
|
||||
return 10.0
|
||||
}
|
||||
|
||||
self.item = item
|
||||
|
||||
self.selectionNode.pressed = item.action
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
let textColorValue: UIColor
|
||||
switch item.color {
|
||||
case .accent:
|
||||
textColorValue = presentationData.theme.list.itemAccentColor
|
||||
case .destructive:
|
||||
textColorValue = presentationData.theme.list.itemDestructiveColor
|
||||
}
|
||||
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue)
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: 11.0), size: textSize)
|
||||
|
||||
let height = textSize.height + 22.0
|
||||
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
|
||||
let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel
|
||||
self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition)
|
||||
transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset)))
|
||||
|
||||
transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel)))
|
||||
transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0)
|
||||
|
||||
return height
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
|
||||
final class PeerInfoScreenDisclosureItem: PeerInfoScreenItem {
|
||||
let id: AnyHashable
|
||||
let label: String
|
||||
let text: String
|
||||
let action: (() -> Void)?
|
||||
|
||||
init(id: AnyHashable, label: String, text: String, action: (() -> Void)?) {
|
||||
self.id = id
|
||||
self.label = label
|
||||
self.text = text
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func node() -> PeerInfoScreenItemNode {
|
||||
return PeerInfoScreenDisclosureItemNode()
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
|
||||
private let selectionNode: PeerInfoScreenSelectableBackgroundNode
|
||||
private let labelNode: ImmediateTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let arrowNode: ASImageNode
|
||||
private let bottomSeparatorNode: ASDisplayNode
|
||||
|
||||
private var item: PeerInfoScreenDisclosureItem?
|
||||
|
||||
override init() {
|
||||
var bringToFrontForHighlightImpl: (() -> Void)?
|
||||
self.selectionNode = PeerInfoScreenSelectableBackgroundNode(bringToFrontForHighlight: { bringToFrontForHighlightImpl?() })
|
||||
|
||||
self.labelNode = ImmediateTextNode()
|
||||
self.labelNode.displaysAsynchronously = false
|
||||
self.labelNode.isUserInteractionEnabled = false
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
self.arrowNode = ASImageNode()
|
||||
self.arrowNode.isLayerBacked = true
|
||||
self.arrowNode.displaysAsynchronously = false
|
||||
self.arrowNode.displayWithoutProcessing = true
|
||||
self.arrowNode.isUserInteractionEnabled = false
|
||||
|
||||
self.bottomSeparatorNode = ASDisplayNode()
|
||||
self.bottomSeparatorNode.isLayerBacked = true
|
||||
|
||||
super.init()
|
||||
|
||||
bringToFrontForHighlightImpl = { [weak self] in
|
||||
self?.bringToFrontForHighlight?()
|
||||
}
|
||||
|
||||
self.addSubnode(self.bottomSeparatorNode)
|
||||
self.addSubnode(self.selectionNode)
|
||||
self.addSubnode(self.labelNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
}
|
||||
|
||||
override func update(width: CGFloat, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
guard let item = item as? PeerInfoScreenDisclosureItem else {
|
||||
return 10.0
|
||||
}
|
||||
|
||||
self.item = item
|
||||
|
||||
self.selectionNode.pressed = item.action
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
let textColorValue: UIColor = presentationData.theme.list.itemPrimaryTextColor
|
||||
let labelColorValue: UIColor = presentationData.theme.list.itemSecondaryTextColor
|
||||
|
||||
self.labelNode.attributedText = NSAttributedString(string: item.label, font: Font.regular(17.0), textColor: labelColorValue)
|
||||
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue)
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
||||
let labelSize = self.labelNode.updateLayout(CGSize(width: width - textSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
let arrowInset: CGFloat = 18.0
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: 11.0), size: textSize)
|
||||
let labelFrame = CGRect(origin: CGPoint(x: width - sideInset - arrowInset - labelSize.width, y: 11.0), size: labelSize)
|
||||
|
||||
let height = textSize.height + 22.0
|
||||
|
||||
if let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) {
|
||||
self.arrowNode.image = arrowImage
|
||||
let arrowFrame = CGRect(origin: CGPoint(x: width - 7.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.labelNode, frame: labelFrame)
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
|
||||
let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel
|
||||
self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition)
|
||||
transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset)))
|
||||
|
||||
transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel)))
|
||||
transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0)
|
||||
|
||||
return height
|
||||
}
|
||||
}
|
121
submodules/TelegramUI/TelegramUI/PeerInfoScreenSwitchItem.swift
Normal file
121
submodules/TelegramUI/TelegramUI/PeerInfoScreenSwitchItem.swift
Normal file
@ -0,0 +1,121 @@
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
|
||||
final class PeerInfoScreenSwitchItem: PeerInfoScreenItem {
|
||||
let id: AnyHashable
|
||||
let text: String
|
||||
let value: Bool
|
||||
let toggled: ((Bool) -> Void)?
|
||||
|
||||
init(id: AnyHashable, text: String, value: Bool, toggled: ((Bool) -> Void)?) {
|
||||
self.id = id
|
||||
self.text = text
|
||||
self.value = value
|
||||
self.toggled = toggled
|
||||
}
|
||||
|
||||
func node() -> PeerInfoScreenItemNode {
|
||||
return PeerInfoScreenSwitchItemNode()
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerInfoScreenSwitchItemNode: PeerInfoScreenItemNode {
|
||||
private let selectionNode: PeerInfoScreenSelectableBackgroundNode
|
||||
private let textNode: ImmediateTextNode
|
||||
private let switchNode: SwitchNode
|
||||
private let bottomSeparatorNode: ASDisplayNode
|
||||
|
||||
private var item: PeerInfoScreenSwitchItem?
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
override init() {
|
||||
var bringToFrontForHighlightImpl: (() -> Void)?
|
||||
self.selectionNode = PeerInfoScreenSelectableBackgroundNode(bringToFrontForHighlight: { bringToFrontForHighlightImpl?() })
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
self.switchNode = SwitchNode()
|
||||
|
||||
self.bottomSeparatorNode = ASDisplayNode()
|
||||
self.bottomSeparatorNode.isLayerBacked = true
|
||||
|
||||
super.init()
|
||||
|
||||
bringToFrontForHighlightImpl = { [weak self] in
|
||||
self?.bringToFrontForHighlight?()
|
||||
}
|
||||
|
||||
self.addSubnode(self.bottomSeparatorNode)
|
||||
self.addSubnode(self.selectionNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.switchNode)
|
||||
|
||||
self.switchNode.valueUpdated = { [weak self] value in
|
||||
self?.item?.toggled?(value)
|
||||
}
|
||||
}
|
||||
|
||||
override func update(width: CGFloat, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
guard let item = item as? PeerInfoScreenSwitchItem else {
|
||||
return 10.0
|
||||
}
|
||||
|
||||
let firstTime = self.item == nil
|
||||
|
||||
if self.theme !== presentationData.theme {
|
||||
self.theme = presentationData.theme
|
||||
|
||||
self.switchNode.frameColor = presentationData.theme.list.itemSwitchColors.frameColor
|
||||
self.switchNode.contentColor = presentationData.theme.list.itemSwitchColors.contentColor
|
||||
self.switchNode.handleColor = presentationData.theme.list.itemSwitchColors.handleColor
|
||||
}
|
||||
|
||||
self.item = item
|
||||
|
||||
self.selectionNode.pressed = nil
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
let textColorValue: UIColor = presentationData.theme.list.itemPrimaryTextColor
|
||||
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue)
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0 - 56.0, height: .greatestFiniteMagnitude))
|
||||
|
||||
let arrowInset: CGFloat = 18.0
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: 11.0), size: textSize)
|
||||
|
||||
let height = textSize.height + 22.0
|
||||
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
|
||||
if let switchView = self.switchNode.view as? UISwitch {
|
||||
if self.switchNode.bounds.size.width.isZero {
|
||||
switchView.sizeToFit()
|
||||
}
|
||||
let switchSize = switchView.bounds.size
|
||||
|
||||
self.switchNode.frame = CGRect(origin: CGPoint(x: width - switchSize.width - 15.0, y: floor((height - switchSize.height) / 2.0)), size: switchSize)
|
||||
if switchView.isOn != item.value {
|
||||
switchView.setOn(item.value, animated: !firstTime)
|
||||
}
|
||||
}
|
||||
|
||||
let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel
|
||||
self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition)
|
||||
transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset)))
|
||||
|
||||
transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: sideInset, y: height - UIScreenPixel), size: CGSize(width: width - sideInset, height: UIScreenPixel)))
|
||||
transition.updateAlpha(node: self.bottomSeparatorNode, alpha: bottomItem == nil ? 0.0 : 1.0)
|
||||
|
||||
return height
|
||||
}
|
||||
}
|
@ -8,13 +8,26 @@ import TelegramPresentationData
|
||||
import AccountContext
|
||||
import ContextUI
|
||||
import PhotoResources
|
||||
import RadialStatusNode
|
||||
import TelegramStringFormatting
|
||||
import GridMessageSelectionNode
|
||||
|
||||
private let mediaBadgeBackgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
||||
private let mediaBadgeTextColor = UIColor.white
|
||||
|
||||
private final class VisualMediaItemInteraction {
|
||||
let openMessage: (MessageId) -> Void
|
||||
var hiddenMedia: [MessageId: [Media]] = [:]
|
||||
let toggleSelection: (MessageId) -> Void
|
||||
|
||||
init(openMessage: @escaping (MessageId) -> Void) {
|
||||
var hiddenMedia: [MessageId: [Media]] = [:]
|
||||
var selectedMessageIds: Set<MessageId>?
|
||||
|
||||
init(
|
||||
openMessage: @escaping (MessageId) -> Void,
|
||||
toggleSelection: @escaping (MessageId) -> Void
|
||||
) {
|
||||
self.openMessage = openMessage
|
||||
self.toggleSelection = toggleSelection
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,12 +37,16 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
private let imageNode: TransformImageNode
|
||||
private var statusNode: RadialStatusNode
|
||||
private let mediaBadgeNode: ChatMessageInteractiveMediaBadge
|
||||
private var selectionNode: GridMessageSelectionNode?
|
||||
|
||||
private let fetchStatusDisposable = MetaDisposable()
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
private var resourceStatus: MediaResourceStatus?
|
||||
|
||||
private var item: (VisualMediaItem, Media?, CGSize, CGSize?)?
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
init(context: AccountContext, interaction: VisualMediaItemInteraction) {
|
||||
self.context = context
|
||||
@ -37,11 +54,19 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.imageNode = TransformImageNode()
|
||||
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6))
|
||||
let progressDiameter: CGFloat = 40.0
|
||||
self.statusNode.frame = CGRect(x: 0.0, y: 0.0, width: progressDiameter, height: progressDiameter)
|
||||
self.statusNode.isUserInteractionEnabled = false
|
||||
|
||||
self.mediaBadgeNode = ChatMessageInteractiveMediaBadge()
|
||||
self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 50.0, height: 50.0))
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.imageNode)
|
||||
self.containerNode.addSubnode(self.mediaBadgeNode)
|
||||
|
||||
self.containerNode.isGestureEnabled = false
|
||||
}
|
||||
@ -69,6 +94,7 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
if item === self.item?.0 && size == self.item?.2 {
|
||||
return
|
||||
}
|
||||
self.theme = theme
|
||||
var media: Media?
|
||||
for value in item.message.media {
|
||||
if let image = value as? TelegramMediaImage {
|
||||
@ -88,20 +114,20 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
self.imageNode.setSignal(mediaGridMessagePhoto(account: context.account, photoReference: .message(message: MessageReference(item.message), media: image), fullRepresentationSize: CGSize(width: 300.0, height: 300.0), synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad, dispatchOnDisplayLink: true)
|
||||
|
||||
self.fetchStatusDisposable.set(nil)
|
||||
/*self.statusNode.transitionToState(.none, completion: { [weak self] in
|
||||
self.statusNode.transitionToState(.none, completion: { [weak self] in
|
||||
self?.statusNode.isHidden = true
|
||||
})*/
|
||||
//self.mediaBadgeNode.isHidden = true
|
||||
})
|
||||
self.mediaBadgeNode.isHidden = true
|
||||
self.resourceStatus = nil
|
||||
} else if let file = media as? TelegramMediaFile, file.isVideo {
|
||||
mediaDimensions = file.dimensions?.cgSize
|
||||
self.imageNode.setSignal(mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .message(message: MessageReference(item.message), media: file), synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: true), attemptSynchronously: synchronousLoad)
|
||||
|
||||
/*self.mediaBadgeNode.isHidden = false
|
||||
self.mediaBadgeNode.isHidden = false
|
||||
|
||||
self.resourceStatus = nil
|
||||
self.fetchStatusDisposable.set((messageMediaFileStatus(context: context, messageId: messageId, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self, let item = strongSelf.item {
|
||||
self.fetchStatusDisposable.set((messageMediaFileStatus(context: context, messageId: item.message.id, file: file) |> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let strongSelf = self, let (item, _, _, _) = strongSelf.item {
|
||||
strongSelf.resourceStatus = status
|
||||
|
||||
let isStreamable = isMediaStreamable(message: item.message, media: file)
|
||||
@ -158,18 +184,25 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
badgeContent = .text(inset: 0.0, backgroundColor: mediaBadgeBackgroundColor, foregroundColor: mediaBadgeTextColor, text: NSAttributedString(string: durationString))
|
||||
}
|
||||
|
||||
strongSelf.mediaBadgeNode.update(theme: item.theme, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false)
|
||||
strongSelf.mediaBadgeNode.update(theme: nil, content: badgeContent, mediaDownloadState: mediaDownloadState, alignment: .right, animated: false, badgeAnimated: false)
|
||||
}
|
||||
}
|
||||
}))
|
||||
if self.statusNode.supernode == nil {
|
||||
self.imageNode.addSubnode(self.statusNode)
|
||||
}*/
|
||||
}
|
||||
} else {
|
||||
//self.mediaBadgeNode.isHidden = true
|
||||
self.mediaBadgeNode.isHidden = true
|
||||
}
|
||||
self.item = (item, media, size, mediaDimensions)
|
||||
|
||||
let progressDiameter: CGFloat = 40.0
|
||||
self.statusNode.frame = CGRect(origin: CGPoint(x: floor((size.width - progressDiameter) / 2.0), y: floor((size.height - progressDiameter) / 2.0)), size: CGSize(width: progressDiameter, height: progressDiameter))
|
||||
|
||||
self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: size.width - 3.0, y: size.height - 18.0 - 3.0), size: CGSize(width: 50.0, height: 50.0))
|
||||
|
||||
self.selectionNode?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.updateHiddenMedia()
|
||||
}
|
||||
|
||||
@ -185,6 +218,46 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
let imageSize = mediaDimensions.aspectFilled(imageFrame.size)
|
||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets(), emptyColor: theme.list.mediaPlaceholderColor))()
|
||||
}
|
||||
|
||||
self.updateSelectionState(animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSelectionState(animated: Bool) {
|
||||
if let (item, media, _, mediaDimensions) = self.item, let theme = self.theme {
|
||||
if let selectedIds = self.interaction.selectedMessageIds {
|
||||
let selected = selectedIds.contains(item.message.id)
|
||||
|
||||
if let selectionNode = self.selectionNode {
|
||||
selectionNode.updateSelected(selected, animated: animated)
|
||||
selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||
} else {
|
||||
let selectionNode = GridMessageSelectionNode(theme: theme, toggle: { [weak self] value in
|
||||
if let strongSelf = self, let messageId = strongSelf.item?.0.message.id {
|
||||
strongSelf.interaction.toggleSelection(messageId)
|
||||
}
|
||||
})
|
||||
|
||||
selectionNode.frame = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||
self.containerNode.addSubnode(selectionNode)
|
||||
self.selectionNode = selectionNode
|
||||
selectionNode.updateSelected(selected, animated: false)
|
||||
if animated {
|
||||
selectionNode.animateIn()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let selectionNode = self.selectionNode {
|
||||
self.selectionNode = nil
|
||||
if animated {
|
||||
selectionNode.animateOut { [weak selectionNode] in
|
||||
selectionNode?.removeFromSupernode()
|
||||
}
|
||||
} else {
|
||||
selectionNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,15 +267,15 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
var statusNodeHidden = false
|
||||
var accessoryHidden = false
|
||||
if let strongSelf = self {
|
||||
//statusNodeHidden = strongSelf.statusNode.isHidden
|
||||
//accessoryHidden = strongSelf.mediaBadgeNode.isHidden
|
||||
//strongSelf.statusNode.isHidden = true
|
||||
//strongSelf.mediaBadgeNode.isHidden = true
|
||||
statusNodeHidden = strongSelf.statusNode.isHidden
|
||||
accessoryHidden = strongSelf.mediaBadgeNode.isHidden
|
||||
strongSelf.statusNode.isHidden = true
|
||||
strongSelf.mediaBadgeNode.isHidden = true
|
||||
}
|
||||
let view = imageNode?.view.snapshotContentTree(unhide: true)
|
||||
if let strongSelf = self {
|
||||
//strongSelf.statusNode.isHidden = statusNodeHidden
|
||||
//strongSelf.mediaBadgeNode.isHidden = accessoryHidden
|
||||
strongSelf.statusNode.isHidden = statusNodeHidden
|
||||
strongSelf.mediaBadgeNode.isHidden = accessoryHidden
|
||||
}
|
||||
return (view, nil)
|
||||
})
|
||||
@ -232,6 +305,8 @@ private final class VisualMediaItem {
|
||||
final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate {
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let interaction: PeerInfoPaneInteraction
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
|
||||
private var _itemInteraction: VisualMediaItemInteraction?
|
||||
@ -257,17 +332,24 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
private var isRequestingView: Bool = false
|
||||
private var isFirstHistoryView: Bool = true
|
||||
|
||||
init(context: AccountContext, openMessage: @escaping (MessageId) -> Bool, peerId: PeerId) {
|
||||
init(context: AccountContext, openMessage: @escaping (MessageId) -> Bool, peerId: PeerId, interaction: PeerInfoPaneInteraction) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.interaction = interaction
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self._itemInteraction = VisualMediaItemInteraction(openMessage: { id in
|
||||
openMessage(id)
|
||||
})
|
||||
self._itemInteraction = VisualMediaItemInteraction(
|
||||
openMessage: { id in
|
||||
openMessage(id)
|
||||
},
|
||||
toggleSelection: { id in
|
||||
interaction.toggleMessageSelected(id)
|
||||
}
|
||||
)
|
||||
self.itemInteraction.selectedMessageIds = self.interaction.selectedMessageIds
|
||||
|
||||
self.scrollNode.view.showsVerticalScrollIndicator = false
|
||||
if #available(iOS 11.0, *) {
|
||||
@ -372,6 +454,13 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateSelectedMessages(animated: Bool) {
|
||||
self.itemInteraction.selectedMessageIds = self.interaction.selectedMessageIds
|
||||
for (_, itemNode) in self.visibleMediaItems {
|
||||
itemNode.updateSelectionState(animated: animated)
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize, isScrollingLockedAtTop: Bool, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.currentParams = (size, isScrollingLockedAtTop, presentationData)
|
||||
|
||||
@ -420,10 +509,10 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
maxVisibleRow = min(rowCount - 1, maxVisibleRow)
|
||||
|
||||
let minVisibleIndex = minVisibleRow * itemsInRow
|
||||
let maxVisibleIndex = min(self.mediaItems.count - 1, maxVisibleRow * itemsInRow - 1)
|
||||
let maxVisibleIndex = min(self.mediaItems.count - 1, (maxVisibleRow + 1) * itemsInRow - 1)
|
||||
|
||||
var validIds = Set<UInt32>()
|
||||
if minVisibleIndex < maxVisibleIndex {
|
||||
if minVisibleIndex <= maxVisibleIndex {
|
||||
for i in minVisibleIndex ... maxVisibleIndex {
|
||||
let stableId = self.mediaItems[i].message.stableId
|
||||
validIds.insert(stableId)
|
||||
|
Binary file not shown.
Binary file not shown.
@ -448,12 +448,12 @@ public final class WalletStrings: Equatable {
|
||||
public var Wallet_SecureStorageReset_Title: String { return self._s[218]! }
|
||||
public var Wallet_Receive_CommentHeader: String { return self._s[219]! }
|
||||
public var Wallet_Info_ReceiveGrams: String { return self._s[220]! }
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
}
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
|
Loading…
x
Reference in New Issue
Block a user