mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Sparse message list improvements
This commit is contained in:
parent
9ad9720707
commit
7c63fe30ea
@ -63,6 +63,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
textColor = presentationData.theme.contextMenu.primaryColor
|
||||
case .destructive:
|
||||
textColor = presentationData.theme.contextMenu.destructiveColor
|
||||
case .disabled:
|
||||
textColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)
|
||||
}
|
||||
|
||||
let titleFont: UIFont
|
||||
@ -156,6 +158,7 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
}
|
||||
}
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.buttonNode.isUserInteractionEnabled = self.action.action != nil
|
||||
|
||||
if let iconSource = action.iconSource {
|
||||
self.iconDisposable = (iconSource.signal
|
||||
@ -273,6 +276,8 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
textColor = presentationData.theme.contextMenu.primaryColor
|
||||
case .destructive:
|
||||
textColor = presentationData.theme.contextMenu.destructiveColor
|
||||
case .disabled:
|
||||
textColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4)
|
||||
}
|
||||
|
||||
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
|
||||
@ -310,13 +315,13 @@ final class ContextActionNode: ASDisplayNode, ContextActionNodeProtocol {
|
||||
guard let controller = self.getController() else {
|
||||
return
|
||||
}
|
||||
self.action.action(controller, { [weak self] result in
|
||||
self.action.action?(controller, { [weak self] result in
|
||||
self?.actionSelected(result)
|
||||
})
|
||||
}
|
||||
|
||||
func setIsHighlighted(_ value: Bool) {
|
||||
if value {
|
||||
if value && self.buttonNode.isUserInteractionEnabled {
|
||||
self.highlightedBackgroundNode.alpha = 1.0
|
||||
} else {
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
|
@ -28,6 +28,7 @@ public enum ContextMenuActionItemTextLayout {
|
||||
public enum ContextMenuActionItemTextColor {
|
||||
case primary
|
||||
case destructive
|
||||
case disabled
|
||||
}
|
||||
|
||||
public enum ContextMenuActionResult {
|
||||
@ -74,9 +75,9 @@ public final class ContextMenuActionItem {
|
||||
public let badge: ContextMenuActionBadge?
|
||||
public let icon: (PresentationTheme) -> UIImage?
|
||||
public let iconSource: ContextMenuActionItemIconSource?
|
||||
public let action: (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void
|
||||
public let action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?
|
||||
|
||||
public init(text: String, textColor: ContextMenuActionItemTextColor = .primary, textLayout: ContextMenuActionItemTextLayout = .twoLinesMax, textFont: ContextMenuActionItemFont = .regular, badge: ContextMenuActionBadge? = nil, icon: @escaping (PresentationTheme) -> UIImage?, iconSource: ContextMenuActionItemIconSource? = nil, action: @escaping (ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void) {
|
||||
public init(text: String, textColor: ContextMenuActionItemTextColor = .primary, textLayout: ContextMenuActionItemTextLayout = .twoLinesMax, textFont: ContextMenuActionItemFont = .regular, badge: ContextMenuActionBadge? = nil, icon: @escaping (PresentationTheme) -> UIImage?, iconSource: ContextMenuActionItemIconSource? = nil, action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?) {
|
||||
self.text = text
|
||||
self.textColor = textColor
|
||||
self.textFont = textFont
|
||||
|
@ -546,7 +546,7 @@ private func generateSegments(geometry: CapturedGeometryNode, superAlpha: CGFloa
|
||||
case let .color(color, alpha):
|
||||
triangleFill = .color(TriangleFill.Color(color).multiplied(alpha: Float(geometry.alpha * alpha * superAlpha)))
|
||||
case let .gradient(colors, positions, start, end, type):
|
||||
triangleFill = .gradient(TriangleFill.Gradient(colors: colors.map(TriangleFill.Color.init), colorLocations: positions.map(Float.init), start: start, end: end, isRadial: type == .radial))
|
||||
triangleFill = .gradient(TriangleFill.Gradient(colors: colors.map { TriangleFill.Color($0).multiplied(alpha: Float(geometry.alpha * superAlpha)) }, colorLocations: positions.map(Float.init), start: start, end: end, isRadial: type == .radial))
|
||||
}
|
||||
|
||||
let mappedFillRule: LottieMeshFillRule
|
||||
|
@ -36,7 +36,7 @@ public final class MeshAnimationCache {
|
||||
let item = Item()
|
||||
self.items[resource.id] = item
|
||||
|
||||
let path = self.mediaBox.cachedRepresentationPathForId(resource.id.stringRepresentation, representationId: "mesh-animation", keepDuration: .shortLived)
|
||||
let path = self.mediaBox.cachedRepresentationPathForId(resource.id.stringRepresentation, representationId: "mesh-animation", keepDuration: .general)
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
|
||||
let animation = MeshAnimation.read(buffer: MeshReadBuffer(data: data))
|
||||
item.readyPath = path
|
||||
@ -68,7 +68,7 @@ public final class MeshAnimationCache {
|
||||
if let animation = generateMeshAnimation(data: jsonData) {
|
||||
let buffer = MeshWriteBuffer()
|
||||
animation.write(buffer: buffer)
|
||||
mediaBox.storeCachedResourceRepresentation(resource, representationId: "mesh-animation", keepDuration: .shortLived, data: buffer.makeData(), completion: { path in
|
||||
mediaBox.storeCachedResourceRepresentation(resource, representationId: "mesh-animation", keepDuration: .general, data: buffer.makeData(), completion: { path in
|
||||
subscriber.putNext((animation, path))
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
|
@ -336,6 +336,11 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
private var activityTimer: SwiftSignalKit.Timer?
|
||||
|
||||
public var beginScrolling: (() -> UIScrollView?)?
|
||||
public var openCurrentDate: (() -> Void)?
|
||||
|
||||
private var offsetBarTimer: SwiftSignalKit.Timer?
|
||||
private var beganAtDateIndicator = false
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
private struct ProjectionData {
|
||||
var minY: CGFloat
|
||||
@ -354,6 +359,9 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.dateIndicator.isUserInteractionEnabled = false
|
||||
self.lineIndicator.isUserInteractionEnabled = false
|
||||
|
||||
self.view.addSubview(self.dateIndicator)
|
||||
self.view.addSubview(self.lineIndicator)
|
||||
|
||||
@ -362,8 +370,11 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
guard let strongSelf = self else {
|
||||
return false
|
||||
}
|
||||
if !strongSelf.dateIndicator.frame.contains(point) {
|
||||
return false
|
||||
|
||||
if strongSelf.dateIndicator.frame.contains(point) {
|
||||
strongSelf.beganAtDateIndicator = true
|
||||
} else {
|
||||
strongSelf.beganAtDateIndicator = false
|
||||
}
|
||||
|
||||
return true
|
||||
@ -372,13 +383,19 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.1, curve: .easeInOut)
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.dateIndicator.layer, offset: CGPoint(x: -80.0, y: 0.0))
|
||||
|
||||
let offsetBarTimer = SwiftSignalKit.Timer(timeout: 0.2, repeat: false, completion: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.performOffsetBarTimerEvent()
|
||||
}, queue: .mainQueue())
|
||||
strongSelf.offsetBarTimer?.invalidate()
|
||||
strongSelf.offsetBarTimer = offsetBarTimer
|
||||
offsetBarTimer.start()
|
||||
|
||||
strongSelf.isDragging = true
|
||||
|
||||
strongSelf.updateLineIndicator(transition: transition)
|
||||
|
||||
if let scrollView = strongSelf.beginScrolling?() {
|
||||
strongSelf.draggingScrollView = scrollView
|
||||
strongSelf.scrollingInitialOffset = scrollView.contentOffset.y
|
||||
@ -393,6 +410,13 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
}
|
||||
strongSelf.draggingScrollView = nil
|
||||
|
||||
if strongSelf.offsetBarTimer != nil {
|
||||
strongSelf.offsetBarTimer?.invalidate()
|
||||
strongSelf.offsetBarTimer = nil
|
||||
|
||||
strongSelf.openCurrentDate?()
|
||||
}
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.dateIndicator.layer, offset: CGPoint(x: 0.0, y: 0.0))
|
||||
|
||||
@ -413,6 +437,12 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
|
||||
if strongSelf.offsetBarTimer != nil {
|
||||
strongSelf.offsetBarTimer?.invalidate()
|
||||
strongSelf.offsetBarTimer = nil
|
||||
strongSelf.performOffsetBarTimerEvent()
|
||||
}
|
||||
|
||||
let indicatorArea = projectionData.maxY - projectionData.minY
|
||||
let scrollFraction = projectionData.scrollableHeight / indicatorArea
|
||||
|
||||
@ -434,6 +464,15 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
self.view.addGestureRecognizer(dragGesture)
|
||||
}
|
||||
|
||||
private func performOffsetBarTimerEvent() {
|
||||
self.hapticFeedback.impact()
|
||||
self.offsetBarTimer = nil
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.1, curve: .easeInOut)
|
||||
transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: self.beganAtDateIndicator ? CGPoint(x: -80.0, y: 0.0) : CGPoint(x: -3.0, y: 0.0))
|
||||
self.updateLineIndicator(transition: transition)
|
||||
}
|
||||
|
||||
public func update(
|
||||
containerSize: CGSize,
|
||||
containerInsets: UIEdgeInsets,
|
||||
@ -556,6 +595,13 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
if self.lineIndicator.alpha <= 0.01 {
|
||||
return nil
|
||||
}
|
||||
if self.lineIndicator.frame.insetBy(dx: -4.0, dy: -2.0).contains(point) {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
|
||||
upgradedMessageHoles: upgradedMessageHoles,
|
||||
messageThreadHoles: messageThreadHoles,
|
||||
existingMessageTags: MessageTags.all,
|
||||
messageTagsWithSummary: [.unseenPersonalMessage, .pinned],
|
||||
messageTagsWithSummary: [.unseenPersonalMessage, .pinned, .video, .photo, .gif, .music, .voiceOrInstantVideo, .webPage, .file],
|
||||
existingGlobalMessageTags: GlobalMessageTags.all,
|
||||
peerNamespacesRequiringMessageTextIndex: [Namespaces.Peer.SecretChat],
|
||||
peerSummaryCounterTags: { peer, isContact in
|
||||
|
@ -72,7 +72,7 @@ public final class SparseMessageList {
|
||||
|
||||
private var topSectionItemRequestCount: Int = 100
|
||||
private var topSection: TopSection?
|
||||
private var topItemsDisposable: Disposable?
|
||||
private var topItemsDisposable = MetaDisposable()
|
||||
|
||||
private var sparseItems: SparseItems?
|
||||
private var sparseItemsDisposable: Disposable?
|
||||
@ -83,6 +83,7 @@ public final class SparseMessageList {
|
||||
}
|
||||
private let loadHoleDisposable = MetaDisposable()
|
||||
private var loadingHole: LoadingHole?
|
||||
private var scheduledLoadingHole: LoadingHole?
|
||||
|
||||
private var loadingPlaceholders: [MessageId: Disposable] = [:]
|
||||
private var loadedPlaceholders: [MessageId: Message] = [:]
|
||||
@ -104,7 +105,10 @@ public final class SparseMessageList {
|
||||
guard let inputPeer = inputPeer else {
|
||||
return .single(SparseItems(items: []))
|
||||
}
|
||||
return account.network.request(Api.functions.messages.getSearchResultsPositions(peer: inputPeer, filter: .inputMessagesFilterPhotoVideo, offsetId: 0, limit: 1000))
|
||||
guard let messageFilter = messageFilterForTagMask(messageTag) else {
|
||||
return .single(SparseItems(items: []))
|
||||
}
|
||||
return account.network.request(Api.functions.messages.getSearchResultsPositions(peer: inputPeer, filter: messageFilter, offsetId: 0, limit: 1000))
|
||||
|> map { result -> SparseItems in
|
||||
switch result {
|
||||
case let .searchResultsPositions(totalCount, positions):
|
||||
@ -159,13 +163,13 @@ public final class SparseMessageList {
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.topItemsDisposable?.dispose()
|
||||
self.topItemsDisposable.dispose()
|
||||
self.sparseItemsDisposable?.dispose()
|
||||
self.loadHoleDisposable.dispose()
|
||||
}
|
||||
|
||||
private func resetTopSection() {
|
||||
self.topItemsDisposable = (self.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .upperBound, count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tagMask: self.messageTag, appendMessagesFromTheSameGroup: false, namespaces: .not(Set(Namespaces.Message.allScheduled)), orderStatistics: [])
|
||||
self.topItemsDisposable.set((self.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId), anchor: .upperBound, count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tagMask: self.messageTag, appendMessagesFromTheSameGroup: false, namespaces: .not(Set(Namespaces.Message.allScheduled)), orderStatistics: [])
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] view, updateType, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -176,7 +180,7 @@ public final class SparseMessageList {
|
||||
default:
|
||||
strongSelf.updateTopSection(view: view)
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
func loadMoreFromTopSection() {
|
||||
@ -338,6 +342,12 @@ public final class SparseMessageList {
|
||||
if self.loadingHole == loadingHole {
|
||||
return
|
||||
}
|
||||
|
||||
if self.loadingHole != nil {
|
||||
self.scheduledLoadingHole = loadingHole
|
||||
return
|
||||
}
|
||||
|
||||
self.loadingHole = loadingHole
|
||||
let mappedDirection: MessageHistoryViewRelativeHoleDirection
|
||||
switch direction {
|
||||
@ -480,6 +490,11 @@ public final class SparseMessageList {
|
||||
|
||||
if strongSelf.loadingHole == loadingHole {
|
||||
strongSelf.loadingHole = nil
|
||||
|
||||
if let scheduledLoadingHole = strongSelf.scheduledLoadingHole {
|
||||
strongSelf.scheduledLoadingHole = nil
|
||||
strongSelf.loadHole(anchor: scheduledLoadingHole.anchor, direction: scheduledLoadingHole.direction)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
public extension TelegramEngine {
|
||||
final class Messages {
|
||||
@ -245,5 +246,52 @@ public extension TelegramEngine {
|
||||
public func sparseMessageList(peerId: EnginePeer.Id, tag: EngineMessage.Tags) -> SparseMessageList {
|
||||
return SparseMessageList(account: self.account, peerId: peerId, messageTag: tag)
|
||||
}
|
||||
|
||||
public func refreshMessageTagStats(peerId: EnginePeer.Id, tags: [EngineMessage.Tags]) -> Signal<Never, NoError> {
|
||||
let account = self.account
|
||||
return self.account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
}
|
||||
|> mapToSignal { inputPeer -> Signal<Never, NoError> in
|
||||
guard let inputPeer = inputPeer else {
|
||||
return .complete()
|
||||
}
|
||||
var signals: [Signal<(count: Int32?, topId: Int32?), NoError>] = []
|
||||
for tag in tags {
|
||||
guard let filter = messageFilterForTagMask(tag) else {
|
||||
signals.append(.single((nil, nil)))
|
||||
continue
|
||||
}
|
||||
signals.append(self.account.network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, topMsgId: nil, filter: filter, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1, maxId: 0, minId: 0, hash: 0))
|
||||
|> map { result -> (count: Int32?, topId: Int32?) in
|
||||
switch result {
|
||||
case let .messagesSlice(_, count, _, _, messages, _, _):
|
||||
return (count, messages.first?.id(namespace: Namespaces.Message.Cloud)?.id)
|
||||
case let .channelMessages(_, _, count, _, messages, _, _):
|
||||
return (count, messages.first?.id(namespace: Namespaces.Message.Cloud)?.id)
|
||||
case let .messages(messages, _, _):
|
||||
return (Int32(messages.count), messages.first?.id(namespace: Namespaces.Message.Cloud)?.id)
|
||||
case .messagesNotModified:
|
||||
return (nil, nil)
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<(count: Int32?, topId: Int32?), NoError> in
|
||||
return .single((nil, nil))
|
||||
})
|
||||
}
|
||||
return combineLatest(signals)
|
||||
|> mapToSignal { counts -> Signal<Never, NoError> in
|
||||
return account.postbox.transaction { transaction in
|
||||
for i in 0 ..< tags.count {
|
||||
let (count, maxId) = counts[i]
|
||||
if let count = count {
|
||||
transaction.replaceMessageTagSummary(peerId: peerId, tagMask: tags[i], namespace: Namespaces.Message.Cloud, count: count, maxId: maxId ?? 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ public struct PresentationResourcesRootController {
|
||||
|
||||
public static func navigationMoreCircledIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.navigationMoreCircledIcon.rawValue, { theme in
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: theme.rootController.navigationBar.accentTextColor)
|
||||
generateTintedImage(image: UIImage(bundleImageName: "Chat List/NavigationMore"), color: theme.rootController.navigationBar.accentTextColor)
|
||||
})
|
||||
}
|
||||
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat List/NavigationMore.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat List/NavigationMore.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "more_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
95
submodules/TelegramUI/Images.xcassets/Chat List/NavigationMore.imageset/more_30.pdf
vendored
Normal file
95
submodules/TelegramUI/Images.xcassets/Chat List/NavigationMore.imageset/more_30.pdf
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 4.335022 4.335083 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.330000 10.664956 m
|
||||
1.330000 15.820534 5.509422 19.999956 10.665000 19.999956 c
|
||||
15.820578 19.999956 20.000000 15.820534 20.000000 10.664956 c
|
||||
20.000000 5.509378 15.820578 1.329956 10.665000 1.329956 c
|
||||
5.509422 1.329956 1.330000 5.509378 1.330000 10.664956 c
|
||||
h
|
||||
10.665000 21.329956 m
|
||||
4.774883 21.329956 0.000000 16.555073 0.000000 10.664956 c
|
||||
0.000000 4.774839 4.774883 -0.000046 10.665000 -0.000046 c
|
||||
16.555117 -0.000046 21.330002 4.774839 21.330002 10.664956 c
|
||||
21.330002 16.555073 16.555117 21.329956 10.665000 21.329956 c
|
||||
h
|
||||
10.852508 9.227441 m
|
||||
11.646418 9.227441 12.290008 9.871032 12.290008 10.664941 c
|
||||
12.290008 11.458850 11.646418 12.102441 10.852508 12.102441 c
|
||||
10.058598 12.102441 9.415008 11.458850 9.415008 10.664941 c
|
||||
9.415008 9.871032 10.058598 9.227441 10.852508 9.227441 c
|
||||
h
|
||||
5.665008 9.227433 m
|
||||
6.458917 9.227433 7.102508 9.871024 7.102508 10.664933 c
|
||||
7.102508 11.458842 6.458917 12.102433 5.665008 12.102433 c
|
||||
4.871099 12.102433 4.227508 11.458842 4.227508 10.664933 c
|
||||
4.227508 9.871024 4.871099 9.227433 5.665008 9.227433 c
|
||||
h
|
||||
17.477493 10.664933 m
|
||||
17.477493 9.871024 16.833900 9.227433 16.039993 9.227433 c
|
||||
15.246083 9.227433 14.602492 9.871024 14.602492 10.664933 c
|
||||
14.602492 11.458842 15.246083 12.102433 16.039993 12.102433 c
|
||||
16.833900 12.102433 17.477493 11.458842 17.477493 10.664933 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1435
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001525 00000 n
|
||||
0000001548 00000 n
|
||||
0000001721 00000 n
|
||||
0000001795 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1854
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Calendar.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Calendar.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "calendar_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
156
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Calendar.imageset/calendar_24.pdf
vendored
Normal file
156
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Calendar.imageset/calendar_24.pdf
vendored
Normal file
@ -0,0 +1,156 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.834991 3.835022 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
4.665000 16.330017 m
|
||||
5.032270 16.330017 5.330000 16.032286 5.330000 15.665017 c
|
||||
5.330000 15.330014 l
|
||||
5.436179 15.330017 l
|
||||
5.465001 15.330017 l
|
||||
10.865002 15.330017 l
|
||||
10.893824 15.330017 l
|
||||
10.929527 15.330017 10.964918 15.330018 11.000000 15.330014 c
|
||||
11.000000 15.665017 l
|
||||
11.000000 16.032286 11.297730 16.330017 11.665000 16.330017 c
|
||||
12.032269 16.330017 12.330000 16.032286 12.330000 15.665017 c
|
||||
12.330000 15.316912 l
|
||||
12.530820 15.310531 12.716895 15.301043 12.889549 15.286936 c
|
||||
13.430929 15.242703 13.898640 15.149774 14.328878 14.930556 c
|
||||
15.018492 14.579180 15.579165 14.018507 15.930542 13.328892 c
|
||||
16.149759 12.898653 16.242689 12.430944 16.286922 11.889564 c
|
||||
16.330013 11.362141 16.330009 10.709471 16.330004 9.893824 c
|
||||
16.330004 9.865017 l
|
||||
16.330004 5.465019 l
|
||||
16.330004 5.436213 l
|
||||
16.330009 4.620564 16.330013 3.967896 16.286922 3.440473 c
|
||||
16.242689 2.899092 16.149759 2.431382 15.930542 2.001143 c
|
||||
15.579165 1.311530 15.018492 0.750856 14.328878 0.399480 c
|
||||
13.898640 0.180264 13.430929 0.087334 12.889549 0.043100 c
|
||||
12.362140 0.000010 11.709491 0.000013 10.893873 0.000019 c
|
||||
10.893807 0.000019 l
|
||||
10.865002 0.000019 l
|
||||
5.465001 0.000019 l
|
||||
5.436195 0.000019 l
|
||||
5.436131 0.000019 l
|
||||
4.620513 0.000013 3.967864 0.000010 3.440454 0.043100 c
|
||||
2.899074 0.087334 2.431364 0.180264 2.001125 0.399480 c
|
||||
1.311511 0.750856 0.750837 1.311530 0.399462 2.001143 c
|
||||
0.180244 2.431382 0.087314 2.899092 0.043082 3.440473 c
|
||||
-0.000010 3.967892 -0.000006 4.620557 0.000000 5.436197 c
|
||||
0.000000 5.465019 l
|
||||
0.000000 9.865017 l
|
||||
0.000000 9.893839 l
|
||||
0.000000 9.893844 l
|
||||
-0.000006 10.709482 -0.000010 11.362145 0.043082 11.889564 c
|
||||
0.087314 12.430944 0.180244 12.898653 0.399462 13.328892 c
|
||||
0.750837 14.018507 1.311511 14.579180 2.001125 14.930556 c
|
||||
2.431364 15.149774 2.899074 15.242703 3.440454 15.286936 c
|
||||
3.613107 15.301043 3.799181 15.310531 4.000000 15.316912 c
|
||||
4.000000 15.665017 l
|
||||
4.000000 16.032286 4.297731 16.330017 4.665000 16.330017 c
|
||||
h
|
||||
11.000000 14.000007 m
|
||||
11.000000 13.665017 l
|
||||
11.000000 13.297748 11.297730 13.000017 11.665000 13.000017 c
|
||||
12.032269 13.000017 12.330000 13.297748 12.330000 13.665017 c
|
||||
12.330000 13.986263 l
|
||||
12.493963 13.980605 12.643336 13.972620 12.781244 13.961353 c
|
||||
13.240376 13.923841 13.513575 13.853280 13.725071 13.745518 c
|
||||
14.164429 13.521653 14.521639 13.164444 14.745503 12.725085 c
|
||||
14.853266 12.513588 14.923826 12.240391 14.961339 11.781260 c
|
||||
14.999486 11.314363 15.000003 10.716069 15.000003 9.865017 c
|
||||
15.000003 5.465019 l
|
||||
15.000003 4.613967 14.999486 4.015673 14.961339 3.548777 c
|
||||
14.923826 3.089645 14.853266 2.816447 14.745503 2.604951 c
|
||||
14.521639 2.165593 14.164429 1.808383 13.725071 1.584518 c
|
||||
13.513575 1.476756 13.240376 1.406196 12.781244 1.368683 c
|
||||
12.314349 1.330536 11.716054 1.330019 10.865002 1.330019 c
|
||||
5.465001 1.330019 l
|
||||
4.613949 1.330019 4.015654 1.330536 3.548759 1.368683 c
|
||||
3.089627 1.406196 2.816429 1.476756 2.604933 1.584518 c
|
||||
2.165574 1.808383 1.808365 2.165593 1.584500 2.604951 c
|
||||
1.476737 2.816447 1.406177 3.089645 1.368665 3.548777 c
|
||||
1.330518 4.015673 1.330001 4.613967 1.330001 5.465019 c
|
||||
1.330001 9.865017 l
|
||||
1.330001 10.716069 1.330518 11.314363 1.368665 11.781260 c
|
||||
1.406177 12.240391 1.476737 12.513588 1.584500 12.725085 c
|
||||
1.808365 13.164444 2.165574 13.521653 2.604933 13.745518 c
|
||||
2.816429 13.853280 3.089627 13.923841 3.548759 13.961353 c
|
||||
3.686666 13.972620 3.836038 13.980605 4.000000 13.986263 c
|
||||
4.000000 13.665017 l
|
||||
4.000000 13.297748 4.297731 13.000017 4.665000 13.000017 c
|
||||
5.032270 13.000017 5.330000 13.297748 5.330000 13.665017 c
|
||||
5.330000 14.000007 l
|
||||
5.465001 14.000017 l
|
||||
10.865002 14.000017 l
|
||||
10.910725 14.000017 10.955717 14.000015 11.000000 14.000007 c
|
||||
h
|
||||
3.365005 11.230013 m
|
||||
2.997736 11.230013 2.700005 10.932281 2.700005 10.565012 c
|
||||
2.700005 10.197743 2.997736 9.900013 3.365005 9.900013 c
|
||||
12.965006 9.900013 l
|
||||
13.332275 9.900013 13.630005 10.197743 13.630005 10.565012 c
|
||||
13.630005 10.932281 13.332275 11.230013 12.965006 11.230013 c
|
||||
3.365005 11.230013 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
4054
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000004144 00000 n
|
||||
0000004167 00000 n
|
||||
0000004340 00000 n
|
||||
0000004414 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
4473
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ZoomIn.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ZoomIn.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "zoomin_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
100
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ZoomIn.imageset/zoomin_24.pdf
vendored
Normal file
100
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ZoomIn.imageset/zoomin_24.pdf
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 4.335022 3.088989 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.330000 9.911050 m
|
||||
1.330000 12.857489 3.718561 15.246050 6.665000 15.246050 c
|
||||
9.611439 15.246050 12.000000 12.857489 12.000000 9.911050 c
|
||||
12.000000 6.964611 9.611439 4.576050 6.665000 4.576050 c
|
||||
3.718561 4.576050 1.330000 6.964611 1.330000 9.911050 c
|
||||
h
|
||||
6.665000 16.576050 m
|
||||
2.984022 16.576050 0.000000 13.592028 0.000000 9.911050 c
|
||||
0.000000 6.230072 2.984022 3.246050 6.665000 3.246050 c
|
||||
8.206339 3.246050 9.625477 3.769255 10.754500 4.647752 c
|
||||
15.078101 0.324150 l
|
||||
15.402237 0.000015 15.927763 0.000015 16.251900 0.324150 c
|
||||
16.576035 0.648287 16.576035 1.173813 16.251900 1.497949 c
|
||||
11.928298 5.821549 l
|
||||
12.806795 6.950573 13.330000 8.369711 13.330000 9.911050 c
|
||||
13.330000 13.592028 10.345978 16.576050 6.665000 16.576050 c
|
||||
h
|
||||
3.500000 9.911050 m
|
||||
3.500000 10.278319 3.797731 10.576050 4.165000 10.576050 c
|
||||
6.000000 10.576050 l
|
||||
6.000000 12.411050 l
|
||||
6.000000 12.778319 6.297730 13.076050 6.665000 13.076050 c
|
||||
7.032269 13.076050 7.330000 12.778319 7.330000 12.411050 c
|
||||
7.330000 10.576050 l
|
||||
9.165000 10.576050 l
|
||||
9.532269 10.576050 9.830000 10.278319 9.830000 9.911050 c
|
||||
9.830000 9.543780 9.532269 9.246050 9.165000 9.246050 c
|
||||
7.330000 9.246050 l
|
||||
7.330000 7.411050 l
|
||||
7.330000 7.043780 7.032269 6.746050 6.665000 6.746050 c
|
||||
6.297730 6.746050 6.000000 7.043780 6.000000 7.411050 c
|
||||
6.000000 9.246050 l
|
||||
4.165000 9.246050 l
|
||||
3.797731 9.246050 3.500000 9.543780 3.500000 9.911050 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1499
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001589 00000 n
|
||||
0000001612 00000 n
|
||||
0000001785 00000 n
|
||||
0000001859 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1918
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ZoomOut.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ZoomOut.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "zoomout_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
90
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ZoomOut.imageset/zoomout_24.pdf
vendored
Normal file
90
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/ZoomOut.imageset/zoomout_24.pdf
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 4.335022 3.088989 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
6.665000 15.246050 m
|
||||
3.718561 15.246050 1.330000 12.857489 1.330000 9.911050 c
|
||||
1.330000 6.964611 3.718561 4.576050 6.665000 4.576050 c
|
||||
9.611439 4.576050 12.000000 6.964611 12.000000 9.911050 c
|
||||
12.000000 12.857489 9.611439 15.246050 6.665000 15.246050 c
|
||||
h
|
||||
0.000000 9.911050 m
|
||||
0.000000 13.592028 2.984022 16.576050 6.665000 16.576050 c
|
||||
10.345978 16.576050 13.330000 13.592028 13.330000 9.911050 c
|
||||
13.330000 8.369711 12.806795 6.950573 11.928298 5.821549 c
|
||||
16.251900 1.497949 l
|
||||
16.576035 1.173813 16.576035 0.648287 16.251900 0.324150 c
|
||||
15.927763 0.000015 15.402237 0.000015 15.078101 0.324150 c
|
||||
10.754500 4.647752 l
|
||||
9.625477 3.769255 8.206339 3.246050 6.665000 3.246050 c
|
||||
2.984022 3.246050 0.000000 6.230072 0.000000 9.911050 c
|
||||
h
|
||||
4.165000 10.576050 m
|
||||
3.797731 10.576050 3.500000 10.278319 3.500000 9.911050 c
|
||||
3.500000 9.543780 3.797731 9.246050 4.165000 9.246050 c
|
||||
9.165000 9.246050 l
|
||||
9.532269 9.246050 9.830000 9.543780 9.830000 9.911050 c
|
||||
9.830000 10.278319 9.532269 10.576050 9.165000 10.576050 c
|
||||
4.165000 10.576050 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1147
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001237 00000 n
|
||||
0000001260 00000 n
|
||||
0000001433 00000 n
|
||||
0000001507 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1566
|
||||
%%EOF
|
@ -77,6 +77,10 @@ final class PeerInfoGroupsInCommonPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
var isReady: Signal<Bool, NoError> {
|
||||
return self.ready.get()
|
||||
}
|
||||
|
||||
var status: Signal<PeerInfoStatusData?, NoError> {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
private var disposable: Disposable?
|
||||
|
||||
|
@ -49,6 +49,11 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
private var mediaAccessoryPanelContainer: PassthroughContainerNode
|
||||
private var mediaAccessoryPanel: (MediaNavigationAccessoryPanel, MediaManagerPlayerType)?
|
||||
private var dismissingPanel: ASDisplayNode?
|
||||
|
||||
private let statusPromise = Promise<PeerInfoStatusData?>(nil)
|
||||
var status: Signal<PeerInfoStatusData?, NoError> {
|
||||
self.statusPromise.get()
|
||||
}
|
||||
|
||||
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, tagMask: MessageTags) {
|
||||
self.context = context
|
||||
@ -130,6 +135,28 @@ final class PeerInfoListPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
self.statusPromise.set(context.account.postbox.combinedView(keys: [PostboxViewKey.historyTagSummaryView(tag: tagMask, peerId: peerId, namespace: Namespaces.Message.Cloud)])
|
||||
|> map { views -> PeerInfoStatusData? in
|
||||
let count: Int32 = (views.views[PostboxViewKey.historyTagSummaryView(tag: tagMask, peerId: peerId, namespace: Namespaces.Message.Cloud)] as? MessageHistoryTagSummaryView)?.count ?? 0
|
||||
if count == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
switch tagMask {
|
||||
case MessageTags.file:
|
||||
return PeerInfoStatusData(text: "\(count) files", isActivity: false)
|
||||
case MessageTags.music:
|
||||
return PeerInfoStatusData(text: "\(count) music files", isActivity: false)
|
||||
case MessageTags.voiceOrInstantVideo:
|
||||
return PeerInfoStatusData(text: "\(count) voice messages", isActivity: false)
|
||||
case MessageTags.webPage:
|
||||
return PeerInfoStatusData(text: "\(count) links", isActivity: false)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
@ -120,6 +120,10 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
var isReady: Signal<Bool, NoError> {
|
||||
return self.ready.get()
|
||||
}
|
||||
|
||||
var status: Signal<PeerInfoStatusData?, NoError> {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
private var disposable: Disposable?
|
||||
|
||||
|
@ -14,6 +14,7 @@ import UniversalMediaPlayer
|
||||
import ListMessageItem
|
||||
import ChatMessageInteractiveMediaBadge
|
||||
import SparseItemGrid
|
||||
import ShimmerEffect
|
||||
|
||||
private final class FrameSequenceThumbnailNode: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
@ -144,6 +145,10 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
private var frameSequenceThumbnailNode: FrameSequenceThumbnailNode?
|
||||
|
||||
private let containerNode: ContextControllerSourceNode
|
||||
|
||||
private var placeholderNode: ShimmerEffectNode?
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
|
||||
private let imageNode: TransformImageNode
|
||||
private var statusNode: RadialStatusNode
|
||||
private let mediaBadgeNode: ChatMessageInteractiveMediaBadge
|
||||
@ -171,6 +176,9 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
|
||||
self.mediaBadgeNode = ChatMessageInteractiveMediaBadge()
|
||||
self.mediaBadgeNode.frame = CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 50.0, height: 50.0))
|
||||
|
||||
let shimmerNode = ShimmerEffectNode()
|
||||
self.placeholderNode = shimmerNode
|
||||
|
||||
super.init()
|
||||
|
||||
@ -204,6 +212,15 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
self?.progressPressed()
|
||||
}
|
||||
}
|
||||
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
//var rect = rect
|
||||
//rect.origin.y += self.insets.top
|
||||
self.absoluteLocation = (rect, containerSize)
|
||||
if let shimmerNode = self.placeholderNode {
|
||||
shimmerNode.updateAbsoluteRect(rect, within: containerSize)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
@ -270,13 +287,13 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
self.containerNode.cancelGesture()
|
||||
}
|
||||
|
||||
func update(size: CGSize, item: VisualMediaItem, theme: PresentationTheme, synchronousLoad: Bool) {
|
||||
func update(size: CGSize, item: VisualMediaItem?, theme: PresentationTheme, synchronousLoad: Bool) {
|
||||
if item === self.item?.0 && size == self.item?.2 {
|
||||
return
|
||||
}
|
||||
self.theme = theme
|
||||
var media: Media?
|
||||
if let message = item.message {
|
||||
if let item = item, let message = item.message {
|
||||
for value in message.media {
|
||||
if let image = value as? TelegramMediaImage {
|
||||
media = image
|
||||
@ -287,8 +304,20 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let shimmerNode = self.placeholderNode {
|
||||
shimmerNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
if let (rect, size) = self.absoluteLocation {
|
||||
shimmerNode.updateAbsoluteRect(rect, within: size)
|
||||
}
|
||||
|
||||
var shapes: [ShimmerEffectNode.Shape] = []
|
||||
shapes.append(.rect(rect: CGRect(origin: CGPoint(), size: size)))
|
||||
|
||||
shimmerNode.update(backgroundColor: theme.list.itemBlocksBackgroundColor, foregroundColor: theme.list.mediaPlaceholderColor, shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: size)
|
||||
}
|
||||
|
||||
if let message = item.message, let file = media as? TelegramMediaFile, file.isAnimated {
|
||||
if let item = item, let message = item.message, let file = media as? TelegramMediaFile, file.isAnimated {
|
||||
if self.videoLayerFrameManager == nil {
|
||||
let sampleBufferLayer: SampleBufferLayer
|
||||
if let current = self.sampleBufferLayer {
|
||||
@ -310,10 +339,22 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
self.videoLayerFrameManager = nil
|
||||
}
|
||||
|
||||
if let message = item.message, let media = media, (self.item?.1 == nil || !media.isEqual(to: self.item!.1!)) {
|
||||
if let item = item, let message = item.message, let media = media, (self.item?.1 == nil || !media.isEqual(to: self.item!.1!)) {
|
||||
var mediaDimensions: CGSize?
|
||||
if let image = media as? TelegramMediaImage, let largestSize = largestImageRepresentation(image.representations)?.dimensions {
|
||||
mediaDimensions = largestSize.cgSize
|
||||
|
||||
if let placeholderNode = self.placeholderNode, placeholderNode.supernode == nil {
|
||||
self.containerNode.insertSubnode(placeholderNode, at: 0)
|
||||
}
|
||||
self.imageNode.imageUpdated = { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
strongSelf.placeholderNode?.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
self.imageNode.setSignal(mediaGridMessagePhoto(account: context.account, photoReference: .message(message: MessageReference(message), media: image), fullRepresentationSize: CGSize(width: 300.0, height: 300.0), synchronousLoad: synchronousLoad), attemptSynchronously: synchronousLoad, dispatchOnDisplayLink: true)
|
||||
|
||||
@ -324,6 +365,18 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
self.mediaBadgeNode.isHidden = true
|
||||
self.resourceStatus = nil
|
||||
} else if let file = media as? TelegramMediaFile, file.isVideo {
|
||||
if let placeholderNode = self.placeholderNode, placeholderNode.supernode == nil {
|
||||
self.containerNode.insertSubnode(placeholderNode, at: 0)
|
||||
}
|
||||
self.imageNode.imageUpdated = { [weak self] image in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if image != nil {
|
||||
strongSelf.placeholderNode?.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
mediaDimensions = file.dimensions?.cgSize
|
||||
self.imageNode.setSignal(mediaGridMessageVideo(postbox: context.account.postbox, videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: true), attemptSynchronously: synchronousLoad)
|
||||
|
||||
@ -405,6 +458,10 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
||||
self.item = (item, media, size, mediaDimensions)
|
||||
|
||||
self.updateHiddenMedia()
|
||||
} else {
|
||||
if let placeholderNode = self.placeholderNode, placeholderNode.supernode == nil {
|
||||
self.containerNode.insertSubnode(placeholderNode, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
let progressDiameter: CGFloat = 40.0
|
||||
@ -574,6 +631,7 @@ private final class VisualMediaItem {
|
||||
enum StableId: Hashable {
|
||||
case message(UInt32)
|
||||
case placeholder(MessageId)
|
||||
case hole(UInt32)
|
||||
}
|
||||
|
||||
var stableId: StableId {
|
||||
@ -879,6 +937,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
private let peerId: PeerId
|
||||
private let chatControllerInteraction: ChatControllerInteraction
|
||||
private(set) var contentType: ContentType
|
||||
private var contentTypePromise: ValuePromise<ContentType>
|
||||
|
||||
weak var parentController: ViewController?
|
||||
|
||||
@ -899,6 +958,11 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
var isReady: Signal<Bool, NoError> {
|
||||
return self.ready.get()
|
||||
}
|
||||
|
||||
private let statusPromise = Promise<PeerInfoStatusData?>(nil)
|
||||
var status: Signal<PeerInfoStatusData?, NoError> {
|
||||
self.statusPromise.get()
|
||||
}
|
||||
|
||||
private let listDisposable = MetaDisposable()
|
||||
private var hiddenMediaDisposable: Disposable?
|
||||
@ -919,12 +983,15 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
private var requestedPlaceholderIds = Set<MessageId>()
|
||||
|
||||
private(set) var zoomLevel: ZoomLevel = .level3
|
||||
|
||||
var openCurrentDate: (() -> Void)?
|
||||
|
||||
init(context: AccountContext, chatControllerInteraction: ChatControllerInteraction, peerId: PeerId, contentType: ContentType) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.chatControllerInteraction = chatControllerInteraction
|
||||
self.contentType = contentType
|
||||
self.contentTypePromise = ValuePromise<ContentType>(contentType)
|
||||
|
||||
self.scrollingArea = SparseItemGridScrollingArea()
|
||||
self.scrollNode = ASScrollNode()
|
||||
@ -939,6 +1006,13 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}
|
||||
return strongSelf.scrollNode.view
|
||||
}
|
||||
|
||||
self.scrollingArea.openCurrentDate = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.openCurrentDate?()
|
||||
}
|
||||
|
||||
self._itemInteraction = VisualMediaItemInteraction(
|
||||
openMessage: { [weak self] message in
|
||||
@ -993,6 +1067,102 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}, queue: .mainQueue())
|
||||
self.animationTimer = animationTimer
|
||||
animationTimer.start()
|
||||
|
||||
self.statusPromise.set((self.contentTypePromise.get()
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { contentType -> Signal<(ContentType, [MessageTags: Int32]), NoError> in
|
||||
var summaries: [MessageTags] = []
|
||||
switch contentType {
|
||||
case .photoOrVideo:
|
||||
summaries.append(.photo)
|
||||
summaries.append(.video)
|
||||
case .photo:
|
||||
summaries.append(.photo)
|
||||
case .video:
|
||||
summaries.append(.video)
|
||||
case .gifs:
|
||||
summaries.append(.gif)
|
||||
}
|
||||
return context.account.postbox.combinedView(keys: summaries.map { tag in
|
||||
return PostboxViewKey.historyTagSummaryView(tag: tag, peerId: peerId, namespace: Namespaces.Message.Cloud)
|
||||
})
|
||||
|> map { views -> (ContentType, [MessageTags: Int32]) in
|
||||
switch contentType {
|
||||
case .photoOrVideo:
|
||||
summaries.append(.photo)
|
||||
summaries.append(.video)
|
||||
case .photo:
|
||||
summaries.append(.photo)
|
||||
case .video:
|
||||
summaries.append(.video)
|
||||
case .gifs:
|
||||
summaries.append(.gif)
|
||||
}
|
||||
var result: [MessageTags: Int32] = [:]
|
||||
for tag in summaries {
|
||||
if let view = views.views[PostboxViewKey.historyTagSummaryView(tag: tag, peerId: peerId, namespace: Namespaces.Message.Cloud)] as? MessageHistoryTagSummaryView {
|
||||
result[tag] = view.count ?? 0
|
||||
} else {
|
||||
result[tag] = 0
|
||||
}
|
||||
}
|
||||
return (contentType, result)
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||
if lhs.0 != rhs.0 {
|
||||
return false
|
||||
}
|
||||
if lhs.1 != rhs.1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|> map { contentType, dict -> PeerInfoStatusData? in
|
||||
switch contentType {
|
||||
case .photoOrVideo:
|
||||
let photoCount: Int32 = dict[.photo] ?? 0
|
||||
let videoCount: Int32 = dict[.video] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if photoCount != 0 && videoCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(photoCount) photos, \(videoCount) videos", isActivity: false)
|
||||
} else if photoCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(photoCount) photos", isActivity: false)
|
||||
} else if videoCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(photoCount) videos", isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .photo:
|
||||
let photoCount: Int32 = dict[.photo] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if photoCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(photoCount) photos", isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .video:
|
||||
let videoCount: Int32 = dict[.video] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if videoCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(videoCount) videos", isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case .gifs:
|
||||
let gifCount: Int32 = dict[.gif] ?? 0
|
||||
|
||||
//TODO:localize
|
||||
if gifCount != 0 {
|
||||
return PeerInfoStatusData(text: "\(gifCount) gifs", isActivity: false)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -1006,6 +1176,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
return
|
||||
}
|
||||
self.contentType = contentType
|
||||
self.contentTypePromise.set(contentType)
|
||||
|
||||
self.listSource = self.context.engine.messages.sparseMessageList(peerId: self.peerId, tag: tagMaskForType(self.contentType))
|
||||
self.isRequestingView = false
|
||||
@ -1018,17 +1189,66 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}
|
||||
self.zoomLevel = level
|
||||
|
||||
self.itemsLayout = nil
|
||||
if let (size, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams {
|
||||
if let copyView = self.scrollNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
copyView.backgroundColor = self.context.sharedContext.currentPresentationData.with({ $0 }).theme.list.plainBackgroundColor
|
||||
|
||||
var currentTopVisibleItemFrame: CGRect?
|
||||
if let itemsLayout = self.itemsLayout {
|
||||
let headerItemMinY = self.scrollNode.view.bounds.minY + 1.0
|
||||
let (minVisibleIndex, maxVisibleIndex) = itemsLayout.visibleRange(rect: self.scrollNode.view.bounds)
|
||||
|
||||
if minVisibleIndex <= maxVisibleIndex {
|
||||
for i in minVisibleIndex ... maxVisibleIndex {
|
||||
let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: sideInset)
|
||||
|
||||
if currentTopVisibleItemFrame == nil && itemFrame.maxY > headerItemMinY {
|
||||
currentTopVisibleItemFrame = self.scrollNode.view.convert(itemFrame, to: self.view)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.itemsLayout = nil
|
||||
|
||||
let copyView = self.scrollNode.view.snapshotView(afterScreenUpdates: false)
|
||||
|
||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: false, transition: .immediate)
|
||||
|
||||
var updatedTopVisibleItemFrame: CGRect?
|
||||
if let itemsLayout = self.itemsLayout {
|
||||
let headerItemMinY = self.scrollNode.view.bounds.minY + 1.0
|
||||
let (updatedMinVisibleIndex, updatedMaxVisibleIndex) = itemsLayout.visibleRange(rect: self.scrollNode.view.bounds)
|
||||
|
||||
if updatedMinVisibleIndex <= updatedMaxVisibleIndex {
|
||||
for i in updatedMinVisibleIndex ... updatedMaxVisibleIndex {
|
||||
let itemFrame = itemsLayout.frame(forItemAt: i, sideInset: sideInset)
|
||||
|
||||
if updatedTopVisibleItemFrame == nil && itemFrame.maxY > headerItemMinY {
|
||||
updatedTopVisibleItemFrame = self.scrollNode.view.convert(itemFrame, to: self.view)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let copyView = copyView, let currentTopVisibleItemFrame = currentTopVisibleItemFrame, let updatedTopVisibleItemFrame = updatedTopVisibleItemFrame {
|
||||
self.view.addSubview(copyView)
|
||||
copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyView] _ in
|
||||
copyView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: true, transition: .immediate)
|
||||
let additionalOffset = CGPoint(x: updatedTopVisibleItemFrame.minX - currentTopVisibleItemFrame.minX, y: updatedTopVisibleItemFrame.minY - currentTopVisibleItemFrame.minY)
|
||||
self.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: self.scrollNode.view.contentOffset.y + additionalOffset.y), animated: false)
|
||||
|
||||
let widthFactor = updatedTopVisibleItemFrame.width / currentTopVisibleItemFrame.width
|
||||
copyView.layer.animateScale(from: 1.0, to: widthFactor, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in })
|
||||
let copyOffset = CGPoint(x: -self.scrollNode.bounds.width * (1.0 - widthFactor) * 0.5, y: -self.scrollNode.bounds.height * (1.0 - widthFactor) * 0.5)//.offsetBy(dx: additionalOffset.x, dy: additionalOffset.y)
|
||||
copyView.layer.animatePosition(from: CGPoint(), to: copyOffset, duration: 0.2, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
|
||||
self.scrollNode.layer.animateScale(from: 1.0 / widthFactor, to: 1.0, duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: true)
|
||||
let originalOffset = CGPoint(x: -self.scrollNode.bounds.width * (1.0 - 1.0 / widthFactor) * 0.5, y: -self.scrollNode.bounds.height * (1.0 - 1.0 / widthFactor) * 0.5)//.offsetBy(dx: additionalOffset.x, dy: additionalOffset.y)
|
||||
self.scrollNode.layer.animatePosition(from: originalOffset, to: CGPoint(), duration: 0.2, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: true, additive: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1328,7 +1548,7 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
containerInsets: UIEdgeInsets(top: 0.0, left: currentParams.sideInset, bottom: currentParams.bottomInset, right: currentParams.sideInset),
|
||||
contentHeight: itemsLayout.contentHeight,
|
||||
contentOffset: self.scrollNode.view.contentOffset.y,
|
||||
isScrolling: self.scrollNode.view.isDragging || self.scrollNode.view.isDecelerating,
|
||||
isScrolling: self.scrollNode.view.isDragging || self.scrollNode.view.isDecelerating || self.decelerationAnimator != nil,
|
||||
dateString: dateString,
|
||||
transition: transition
|
||||
)
|
||||
@ -1413,16 +1633,14 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}
|
||||
}
|
||||
|
||||
guard let item = maybeItem else {
|
||||
continue
|
||||
let stableId: VisualMediaItem.StableId
|
||||
if let item = maybeItem {
|
||||
stableId = item.stableId
|
||||
} else {
|
||||
stableId = .hole(UInt32(itemIndex))
|
||||
}
|
||||
let stableId = item.stableId
|
||||
validIds.insert(stableId)
|
||||
|
||||
if item.message == nil && !self.requestedPlaceholderIds.contains(item.id) {
|
||||
//requestPlaceholderIds.append(item.id)
|
||||
self.requestedPlaceholderIds.insert(item.id)
|
||||
}
|
||||
validIds.insert(stableId)
|
||||
|
||||
let itemFrame = itemsLayout.frame(forItemAt: itemIndex, sideInset: sideInset)
|
||||
|
||||
@ -1435,12 +1653,13 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
self.scrollNode.addSubnode(itemNode)
|
||||
}
|
||||
itemNode.frame = itemFrame
|
||||
itemNode.updateAbsoluteRect(itemFrame.offsetBy(dx: 0.0, dy: -activeRect.origin.y), within: activeRect.size)
|
||||
|
||||
var itemSynchronousLoad = false
|
||||
if itemIndex >= minActuallyVisibleIndex && itemIndex <= maxActuallyVisibleIndex {
|
||||
itemSynchronousLoad = synchronousLoad
|
||||
}
|
||||
itemNode.update(size: itemFrame.size, item: item, theme: theme, synchronousLoad: itemSynchronousLoad)
|
||||
itemNode.update(size: itemFrame.size, item: maybeItem, theme: theme, synchronousLoad: itemSynchronousLoad)
|
||||
itemNode.updateIsVisible(itemFrame.intersects(activeRect))
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ protocol PeerInfoPaneNode: ASDisplayNode {
|
||||
var isReady: Signal<Bool, NoError> { get }
|
||||
|
||||
var parentController: ViewController? { get set }
|
||||
|
||||
var status: Signal<PeerInfoStatusData?, NoError> { get }
|
||||
|
||||
func update(size: CGSize, sideInset: CGFloat, bottomInset: CGFloat, visibleHeight: CGFloat, isScrollingLockedAtTop: Bool, expandProgress: CGFloat, presentationData: PresentationData, synchronous: Bool, transition: ContainedViewLayoutTransition)
|
||||
func scrollToTop() -> Bool
|
||||
@ -383,12 +385,17 @@ private final class PeerInfoPendingPane {
|
||||
peerId: PeerId,
|
||||
key: PeerInfoPaneKey,
|
||||
hasBecomeReady: @escaping (PeerInfoPaneKey) -> Void,
|
||||
parentController: ViewController?
|
||||
parentController: ViewController?,
|
||||
openMediaCalendar: @escaping () -> Void
|
||||
) {
|
||||
let paneNode: PeerInfoPaneNode
|
||||
switch key {
|
||||
case .media:
|
||||
paneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, contentType: .photoOrVideo)
|
||||
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, contentType: .photoOrVideo)
|
||||
paneNode = visualPaneNode
|
||||
visualPaneNode.openCurrentDate = {
|
||||
openMediaCalendar()
|
||||
}
|
||||
case .files:
|
||||
paneNode = PeerInfoListPaneNode(context: context, updatedPresentationData: updatedPresentationData, chatControllerInteraction: chatControllerInteraction, peerId: peerId, tagMask: .file)
|
||||
case .links:
|
||||
@ -398,7 +405,11 @@ private final class PeerInfoPendingPane {
|
||||
case .music:
|
||||
paneNode = PeerInfoListPaneNode(context: context, updatedPresentationData: updatedPresentationData, chatControllerInteraction: chatControllerInteraction, peerId: peerId, tagMask: .music)
|
||||
case .gifs:
|
||||
paneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, contentType: .gifs)
|
||||
let visualPaneNode = PeerInfoVisualMediaPaneNode(context: context, chatControllerInteraction: chatControllerInteraction, peerId: peerId, contentType: .gifs)
|
||||
paneNode = visualPaneNode
|
||||
visualPaneNode.openCurrentDate = {
|
||||
openMediaCalendar()
|
||||
}
|
||||
case .groupsInCommon:
|
||||
paneNode = PeerInfoGroupsInCommonPaneNode(context: context, peerId: peerId, chatControllerInteraction: chatControllerInteraction, openPeerContextAction: openPeerContextAction, groupsInCommonContext: data.groupsInCommon!)
|
||||
case .members:
|
||||
@ -465,6 +476,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
||||
|
||||
var currentPaneUpdated: ((Bool) -> Void)?
|
||||
var requestExpandTabs: (() -> Bool)?
|
||||
|
||||
var openMediaCalendar: (() -> Void)?
|
||||
|
||||
private var currentAvailablePanes: [PeerInfoPaneKey]?
|
||||
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
@ -763,7 +776,10 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
||||
apply()
|
||||
}
|
||||
},
|
||||
parentController: self.parentController
|
||||
parentController: self.parentController,
|
||||
openMediaCalendar: { [weak self] in
|
||||
self?.openMediaCalendar?()
|
||||
}
|
||||
)
|
||||
self.pendingPanes[key] = pane
|
||||
pane.pane.node.frame = paneFrame
|
||||
|
@ -1490,7 +1490,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr
|
||||
return result
|
||||
}
|
||||
|
||||
private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
|
||||
private weak var controller: PeerInfoScreenImpl?
|
||||
|
||||
private let context: AccountContext
|
||||
@ -1514,6 +1514,12 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
private let paneContainerNode: PeerInfoPaneContainerNode
|
||||
private var ignoreScrolling: Bool = false
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
|
||||
private var customStatusData: PeerInfoStatusData?
|
||||
private let customStatusPromise = Promise<PeerInfoStatusData?>(nil)
|
||||
private var customStatusDisposable: Disposable?
|
||||
|
||||
private var refreshMessageTagStatsDisposable: Disposable?
|
||||
|
||||
private var searchDisplayController: SearchDisplayController?
|
||||
|
||||
@ -2246,6 +2252,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if let pane = strongSelf.paneContainerNode.currentPane?.node {
|
||||
strongSelf.customStatusPromise.set(pane.status)
|
||||
} else {
|
||||
strongSelf.customStatusPromise.set(.single(nil))
|
||||
}
|
||||
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
if strongSelf.headerNode.isAvatarExpanded {
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||
@ -2290,6 +2303,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
self.paneContainerNode.openMediaCalendar = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.openMediaCalendar()
|
||||
}
|
||||
|
||||
self.paneContainerNode.requestPerformPeerMemberAction = { [weak self] member, action in
|
||||
guard let strongSelf = self else {
|
||||
@ -2868,6 +2888,19 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|
||||
self.context.prefetchManager?.prepareNextGreetingSticker()
|
||||
}
|
||||
|
||||
self.customStatusDisposable = (self.customStatusPromise.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.customStatusData = value
|
||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
|
||||
}
|
||||
})
|
||||
|
||||
self.refreshMessageTagStatsDisposable = context.engine.messages.refreshMessageTagStats(peerId: peerId, tags: [.video, .photo, .gif, .music, .voiceOrInstantVideo, .webPage, .file]).start()
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -2887,6 +2920,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self.supportPeerDisposable.dispose()
|
||||
self.tipsPeerDisposable.dispose()
|
||||
self.shareStatusDisposable?.dispose()
|
||||
self.customStatusDisposable?.dispose()
|
||||
self.refreshMessageTagStatsDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -5958,28 +5993,32 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
//TODO:localize
|
||||
items.append(.action(ContextMenuActionItem(text: "Zoom In", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak pane] _, a in
|
||||
|
||||
let canZoomIn = pane.zoomLevel.decremented() != pane.zoomLevel
|
||||
let canZoomOut = pane.zoomLevel.incremented() != pane.zoomLevel
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Zoom In", textColor: canZoomIn ? .primary : .disabled, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ZoomIn"), color: canZoomIn ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
|
||||
}, action: canZoomIn ? { [weak pane] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let pane = pane else {
|
||||
return
|
||||
}
|
||||
pane.updateZoomLevel(level: pane.zoomLevel.decremented())
|
||||
})))
|
||||
items.append(.action(ContextMenuActionItem(text: "Zoom Out", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak pane] _, a in
|
||||
} : nil)))
|
||||
items.append(.action(ContextMenuActionItem(text: "Zoom Out", textColor : canZoomOut ? .primary : .disabled, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ZoomOut"), color: canZoomOut ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
|
||||
}, action: canZoomOut ? { [weak pane] _, a in
|
||||
a(.default)
|
||||
|
||||
guard let pane = pane else {
|
||||
return
|
||||
}
|
||||
pane.updateZoomLevel(level: pane.zoomLevel.incremented())
|
||||
})))
|
||||
} : nil)))
|
||||
items.append(.action(ContextMenuActionItem(text: "Show Calendar", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Search/Calendar"), color: theme.contextMenu.primaryColor)
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Calendar"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] _, a in
|
||||
a(.default)
|
||||
|
||||
@ -6112,7 +6151,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: layout.safeInsets.left, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, transition: transition, additive: additive)
|
||||
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: layout.safeInsets.left, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, notificationSettings: self.data?.notificationSettings, statusData: self.customStatusData ?? self.data?.status, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, transition: transition, additive: additive)
|
||||
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: headerHeight))
|
||||
if additive {
|
||||
transition.updateFrameAdditive(node: self.headerNode, frame: headerFrame)
|
||||
@ -6361,7 +6400,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
if !additive {
|
||||
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: layout.safeInsets.left, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, transition: transition, additive: additive)
|
||||
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: layout.safeInsets.left, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, notificationSettings: self.data?.notificationSettings, statusData: self.customStatusData ?? self.data?.status, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, transition: transition, additive: additive)
|
||||
}
|
||||
|
||||
let paneAreaExpansionDistance: CGFloat = 32.0
|
||||
|
Loading…
x
Reference in New Issue
Block a user