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