User Info screen

This commit is contained in:
Ali 2020-02-07 14:23:25 +00:00
parent f9ccae936a
commit e2071301c2
28 changed files with 7682 additions and 4769 deletions

View File

@ -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";

View File

@ -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?()

View File

@ -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
}
}

View File

@ -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)
}
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}
}

View 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
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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() {

View File

@ -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
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}

View 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
}
}

View File

@ -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)

View File

@ -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)