Sparse message list improvements

This commit is contained in:
Ali 2021-10-12 22:41:10 +04:00
parent 9ad9720707
commit 7c63fe30ea
23 changed files with 968 additions and 55 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "more_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "calendar_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "zoomin_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "zoomout_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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