Video avatar fixes

This commit is contained in:
Ilya Laktyushin 2020-07-19 19:05:30 +03:00
parent 8b970a676a
commit a0a2847eef
9 changed files with 156 additions and 124 deletions

View File

@ -352,6 +352,15 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
self.items[i].updateNode(node: itemNode, synchronous: synchronous)
}
}
for i in (0 ..< self.itemNodes.count).reversed() {
let node = self.itemNodes[i]
if node.index > self.items.count - 1 {
node.removeFromSupernode()
self.itemNodes.remove(at: i)
}
}
self.updateCentralIndexOffset(transition: .immediate)
}
}

View File

@ -10,7 +10,7 @@ private let maxWidth: CGFloat = 75.0
public protocol GalleryThumbnailItem {
func isEqual(to: GalleryThumbnailItem) -> Bool
var image: (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) { get }
func image(synchronous: Bool) -> (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize)
}
private final class GalleryThumbnailItemNode: ASDisplayNode {
@ -19,19 +19,19 @@ private final class GalleryThumbnailItemNode: ASDisplayNode {
private let imageSize: CGSize
init(item: GalleryThumbnailItem) {
init(item: GalleryThumbnailItem, synchronous: Bool) {
self.imageNode = TransformImageNode()
self.imageContainerNode = ASDisplayNode()
self.imageContainerNode.clipsToBounds = true
self.imageContainerNode.cornerRadius = 2.0
let (signal, imageSize) = item.image
let (signal, imageSize) = item.image(synchronous: synchronous)
self.imageSize = imageSize
super.init()
self.imageContainerNode.addSubnode(self.imageNode)
self.addSubnode(self.imageContainerNode)
self.imageNode.setSignal(signal)
self.imageNode.setSignal(signal, attemptSynchronously: synchronous)
}
func updateLayout(height: CGFloat, progress: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
@ -57,6 +57,7 @@ public final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDel
private var itemNodes: [GalleryThumbnailItemNode] = []
private var centralIndexAndProgress: (Int, CGFloat?)?
private var currentLayout: CGSize?
public var updateSynchronously: Bool = false
private var isPanning: Bool = false
@ -96,7 +97,6 @@ public final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDel
for i in 0 ..< self.items.count {
if !self.items[i].isEqual(to: items[i]) {
updated = true
break
}
}
} else {
@ -108,7 +108,7 @@ public final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDel
if let index = self.items.firstIndex(where: { $0.isEqual(to: item) }) {
itemNodes.append(self.itemNodes[index])
} else {
itemNodes.append(GalleryThumbnailItemNode(item: item))
itemNodes.append(GalleryThumbnailItemNode(item: item, synchronous: self.updateSynchronously))
}
}

View File

@ -60,7 +60,7 @@ final class ChatMediaGalleryThumbnailItem: GalleryThumbnailItem {
}
}
var image: (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) {
func image(synchronous: Bool) -> (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) {
switch self.thumbnail {
case let .image(imageReference):
if let representation = largestImageRepresentation(imageReference.media.representations) {

View File

@ -15,7 +15,7 @@ private struct InstantImageGalleryThumbnailItem: GalleryThumbnailItem {
let account: Account
let mediaReference: AnyMediaReference
var image: (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) {
func image(synchronous: Bool) -> (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) {
if let imageReferene = mediaReference.concrete(TelegramMediaImage.self), let representation = largestImageRepresentation(imageReferene.media.representations) {
return (mediaGridMessagePhoto(account: self.account, photoReference: imageReferene), representation.dimensions.cgSize)
} else if let fileReference = mediaReference.concrete(TelegramMediaFile.self), let dimensions = fileReference.media.dimensions {

View File

@ -656,6 +656,19 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
return canDelete
}
private func replaceEntries(_ entries: [AvatarGalleryEntry]) {
self.galleryNode.currentThumbnailContainerNode?.updateSynchronously = true
self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in
self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
}, edit: { [weak self] in
self?.editEntry(entry)
}) }), centralItemIndex: 0, synchronous: true)
self.entries = entries
self.galleryNode.currentThumbnailContainerNode?.updateSynchronously = false
}
private func setMainEntry(_ rawEntry: AvatarGalleryEntry) {
var entry = rawEntry
if case .topImage = entry, !self.entries.isEmpty {
@ -668,9 +681,33 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
} else {
}
case let .image(_, reference, _, _, _, _, _, _, _, _):
if self.peer.id == self.context.account.peerId {
if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer) {
if let reference = reference {
let _ = updatePeerPhotoExisting(network: self.context.account.network, reference: reference).start()
let _ = (updatePeerPhotoExisting(network: self.context.account.network, reference: reference)
|> deliverOnMainQueue).start(next: { [weak self] photo in
if let strongSelf = self, let photo = photo, let firstEntry = strongSelf.entries.first, case let .image(image) = firstEntry {
let updatedEntry = AvatarGalleryEntry.image(photo.imageId, photo.reference, photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), strongSelf.peer, image.5, image.6, image.7, photo.immediateThumbnailData, image.9)
for (lhs, rhs) in zip(firstEntry.representations, updatedEntry.representations) {
if lhs.representation.dimensions == rhs.representation.dimensions {
strongSelf.context.account.postbox.mediaBox.copyResourceData(from: lhs.representation.resource.id, to: rhs.representation.resource.id, synchronous: true)
}
}
for (lhs, rhs) in zip(firstEntry.videoRepresentations, updatedEntry.videoRepresentations) {
if lhs.representation.dimensions == rhs.representation.dimensions {
strongSelf.context.account.postbox.mediaBox.copyResourceData(from: lhs.representation.resource.id, to: rhs.representation.resource.id, synchronous: true)
}
}
var entries = strongSelf.entries
entries.remove(at: 0)
entries.insert(updatedEntry, at: 0)
strongSelf.replaceEntries(normalizeEntries(entries))
if let firstEntry = strongSelf.entries.first {
strongSelf._hiddenMedia.set(.single(firstEntry))
}
}
})
}
if let index = self.entries.firstIndex(of: entry) {
@ -684,35 +721,12 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
entries.insert(previousFirstEntry, at: index)
}
entries = normalizeEntries(entries)
self.galleryNode.pager.replaceItems(entries.map({ entry in PeerAvatarImageGalleryItem(context: self.context, peer: self.peer, presentationData: presentationData, entry: entry, sourceCorners: self.sourceCorners, delete: self.canDelete ? { [weak self] in
self?.deleteEntry(entry)
} : nil, setMain: { [weak self] in
self?.setMainEntry(entry)
}, edit: { [weak self] in
self?.editEntry(entry)
}) }), centralItemIndex: 0, synchronous: true)
self.entries = entries
self.replaceEntries(normalizeEntries(entries))
if let firstEntry = self.entries.first {
self._hiddenMedia.set(.single(firstEntry))
}
}
} else {
// if let messageId = messageId {
// let _ = deleteMessagesInteractively(account: self.context.account, messageIds: [messageId], type: .forEveryone).start()
// }
//
// if entry == self.entries.first {
// let _ = updatePeerPhoto(postbox: self.context.account.postbox, network: self.context.account.network, stateManager: self.context.account.stateManager, accountPeerId: self.context.account.peerId, peerId: self.peer.id, photo: nil, mapResourceToAvatarSizes: { _, _ in .single([:]) }).start()
// self.dismiss(forceAway: true)
// } else {
// if let index = self.entries.firstIndex(of: entry) {
// self.entries.remove(at: index)
// self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1))
// }
// }
}
}
}
}

View File

@ -25,9 +25,9 @@ private struct PeerAvatarImageGalleryThumbnailItem: GalleryThumbnailItem {
self.content = content
}
var image: (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) {
func image(synchronous: Bool) -> (Signal<(TransformImageArguments) -> DrawingContext?, NoError>, CGSize) {
if let representation = largestImageRepresentation(self.content.map({ $0.representation })) {
return (avatarGalleryThumbnailPhoto(account: self.account, representations: self.content), representation.dimensions.cgSize)
return (avatarGalleryThumbnailPhoto(account: self.account, representations: self.content, synchronousLoad: synchronous), representation.dimensions.cgSize)
} else {
return (.single({ _ in return nil }), CGSize(width: 128.0, height: 128.0))
}

View File

@ -1000,9 +1000,9 @@ public func chatSecretPhoto(account: Account, photoReference: ImageMediaReferenc
}
}
private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [ImageRepresentationWithReference], fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [ImageRepresentationWithReference], fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, synchronousLoad: Bool) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
if let smallestRepresentation = smallestImageRepresentation(representations.map({ $0.representation })), let largestRepresentation = imageRepresentationLargerThan(representations.map({ $0.representation }), size: PixelDimensions(width: Int32(fullRepresentationSize.width), height: Int32(fullRepresentationSize.height))), let smallestIndex = representations.firstIndex(where: { $0.representation == smallestRepresentation }), let largestIndex = representations.firstIndex(where: { $0.representation == largestRepresentation }) {
let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource)
let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource, attemptSynchronously: synchronousLoad)
let signal = maybeFullSize
|> take(1)
@ -1070,8 +1070,8 @@ private func avatarGalleryThumbnailDatas(postbox: Postbox, representations: [Ima
}
}
public func avatarGalleryThumbnailPhoto(account: Account, representations: [ImageRepresentationWithReference]) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = avatarGalleryThumbnailDatas(postbox: account.postbox, representations: representations, fullRepresentationSize: CGSize(width: 127.0, height: 127.0), autoFetchFullSize: true)
public func avatarGalleryThumbnailPhoto(account: Account, representations: [ImageRepresentationWithReference], synchronousLoad: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
let signal = avatarGalleryThumbnailDatas(postbox: account.postbox, representations: representations, fullRepresentationSize: CGSize(width: 127.0, height: 127.0), autoFetchFullSize: true, synchronousLoad: synchronousLoad)
return signal
|> map { value in
let thumbnailData = value._0
@ -1079,7 +1079,6 @@ public func avatarGalleryThumbnailPhoto(account: Account, representations: [Imag
let fullSizeComplete = value._2
return { arguments in
assertNotOnMainThread()
let context = DrawingContext(size: arguments.drawingSize, clear: true)
let drawingRect = arguments.drawingRect

View File

@ -339,15 +339,19 @@ public func updatePeerPhotoInternal(postbox: Postbox, network: Network, stateMan
}
}
public func updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal<Void, NoError> {
public func updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> {
switch reference {
case let .cloud(imageId, accessHash, fileReference):
return network.request(Api.functions.photos.updateProfilePhoto(id: .inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))))
|> `catch` { _ -> Signal<Api.photos.Photo, NoError> in
return .complete()
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
|> mapToSignal { photo -> Signal<TelegramMediaImage?, NoError> in
if case let .photo(photo, _) = photo {
return .single(telegramMediaImageFromApiPhoto(photo))
} else {
return .complete()
}
}
}
}

View File

@ -634,7 +634,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
if let size = self.validLayout {
self.playbackProgress = position
self.loading = loading
self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring))
self.updateStrips(size: size, itemsAdded: false, stripTransition: .animated(duration: 0.3, curve: .spring))
}
}
@ -911,17 +911,19 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
case .cancelled, .ended:
let translation = recognizer.translation(in: self.view)
let velocity = recognizer.velocity(in: self.view)
var directionIsToRight = false
var directionIsToRight: Bool?
if abs(velocity.x) > 10.0 {
directionIsToRight = velocity.x < 0.0
} else {
directionIsToRight = translation.x > self.bounds.width / 2.0
} else if abs(transitionFraction) > 0.5 {
directionIsToRight = transitionFraction < 0.0
}
var updatedIndex = self.currentIndex
if directionIsToRight {
updatedIndex = min(updatedIndex + 1, self.items.count - 1)
} else {
updatedIndex = max(updatedIndex - 1, 0)
if let directionIsToRight = directionIsToRight {
if directionIsToRight {
updatedIndex = min(updatedIndex + 1, self.items.count - 1)
} else {
updatedIndex = max(updatedIndex - 1, 0)
}
}
let previousIndex = self.currentIndex
self.currentIndex = updatedIndex
@ -1089,6 +1091,80 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.updateItems(size: size, transition: transition, stripTransition: transition)
}
private func updateStrips(size: CGSize, itemsAdded: Bool, stripTransition: ContainedViewLayoutTransition) {
let hadOneStripNode = self.stripNodes.count == 1
if self.stripNodes.count != self.items.count {
if self.stripNodes.count < self.items.count {
for _ in 0 ..< self.items.count - self.stripNodes.count {
let stripNode = ASImageNode()
stripNode.displaysAsynchronously = false
stripNode.displayWithoutProcessing = true
stripNode.image = self.activeStripImage
stripNode.alpha = 0.2
self.stripNodes.append(stripNode)
self.stripContainerNode.addSubnode(stripNode)
}
} else {
for i in (self.items.count ..< self.stripNodes.count).reversed() {
self.stripNodes[i].removeFromSupernode()
self.stripNodes.remove(at: i)
}
}
self.stripContainerNode.addSubnode(self.activeStripNode)
self.stripContainerNode.addSubnode(self.loadingStripNode)
}
if self.appliedStripNodeCurrentIndex != self.currentIndex || itemsAdded {
if !self.itemNodes.isEmpty {
self.appliedStripNodeCurrentIndex = self.currentIndex
}
if let currentItemNode = self.currentItemNode {
self.positionDisposable.set((currentItemNode.mediaStatus
|> deliverOnMainQueue).start(next: { [weak self] statusAndVideoStartTimestamp in
if let strongSelf = self {
strongSelf.playerStatus = statusAndVideoStartTimestamp
}
}))
} else {
self.positionDisposable.set(nil)
}
}
if hadOneStripNode && self.stripNodes.count > 1 {
self.stripContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
let stripInset: CGFloat = 8.0
let stripSpacing: CGFloat = 4.0
let stripWidth: CGFloat = max(5.0, floor((size.width - stripInset * 2.0 - stripSpacing * CGFloat(self.stripNodes.count - 1)) / CGFloat(self.stripNodes.count)))
let currentStripMinX = stripInset + CGFloat(self.currentIndex) * (stripWidth + stripSpacing)
let currentStripMidX = floor(currentStripMinX + stripWidth / 2.0)
let lastStripMaxX = stripInset + CGFloat(self.stripNodes.count - 1) * (stripWidth + stripSpacing) + stripWidth
let stripOffset: CGFloat = min(0.0, max(size.width - stripInset - lastStripMaxX, size.width / 2.0 - currentStripMidX))
for i in 0 ..< self.stripNodes.count {
let stripX: CGFloat = stripInset + CGFloat(i) * (stripWidth + stripSpacing)
if i == 0 && self.stripNodes.count == 1 {
self.stripNodes[i].isHidden = true
} else {
self.stripNodes[i].isHidden = false
}
let stripFrame = CGRect(origin: CGPoint(x: stripOffset + stripX, y: 0.0), size: CGSize(width: stripWidth + 1.0, height: 2.0))
stripTransition.updateFrame(node: self.stripNodes[i], frame: stripFrame)
}
if self.currentIndex >= 0 && self.currentIndex < self.stripNodes.count {
var frame = self.stripNodes[self.currentIndex].frame
stripTransition.updateFrame(node: self.loadingStripNode, frame: frame)
if let playbackProgress = self.playbackProgress {
frame.size.width = max(frame.size.height, frame.size.width * playbackProgress)
}
stripTransition.updateFrameAdditive(node: self.activeStripNode, frame: frame)
stripTransition.updateAlpha(node: self.activeStripNode, alpha: self.loading ? 0.0 : 1.0)
stripTransition.updateAlpha(node: self.loadingStripNode, alpha: self.loading ? 1.0 : 0.0)
self.activeStripNode.isHidden = self.stripNodes.count < 2
self.loadingStripNode.isHidden = self.stripNodes.count < 2 || !self.loading
}
}
private func updateItems(size: CGSize, update: Bool = false, transition: ContainedViewLayoutTransition, stripTransition: ContainedViewLayoutTransition, synchronous: Bool = false) {
var validIds: [WrappedMediaResourceId] = []
var addedItemNodesForAdditiveTransition: [PeerInfoAvatarListItemNode] = []
@ -1149,77 +1225,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
}
}
let hadOneStripNode = self.stripNodes.count == 1
if self.stripNodes.count != self.items.count {
if self.stripNodes.count < self.items.count {
for _ in 0 ..< self.items.count - self.stripNodes.count {
let stripNode = ASImageNode()
stripNode.displaysAsynchronously = false
stripNode.displayWithoutProcessing = true
stripNode.image = self.activeStripImage
stripNode.alpha = 0.2
self.stripNodes.append(stripNode)
self.stripContainerNode.addSubnode(stripNode)
}
} else {
for i in (self.items.count ..< self.stripNodes.count).reversed() {
self.stripNodes[i].removeFromSupernode()
self.stripNodes.remove(at: i)
}
}
self.stripContainerNode.addSubnode(self.activeStripNode)
self.stripContainerNode.addSubnode(self.loadingStripNode)
}
if self.appliedStripNodeCurrentIndex != self.currentIndex || itemsAdded {
if !self.itemNodes.isEmpty {
self.appliedStripNodeCurrentIndex = self.currentIndex
}
if let currentItemNode = self.currentItemNode {
self.positionDisposable.set((currentItemNode.mediaStatus
|> deliverOnMainQueue).start(next: { [weak self] statusAndVideoStartTimestamp in
if let strongSelf = self {
strongSelf.playerStatus = statusAndVideoStartTimestamp
}
}))
} else {
self.positionDisposable.set(nil)
}
}
if hadOneStripNode && self.stripNodes.count > 1 {
self.stripContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
let stripInset: CGFloat = 8.0
let stripSpacing: CGFloat = 4.0
let stripWidth: CGFloat = max(5.0, floor((size.width - stripInset * 2.0 - stripSpacing * CGFloat(self.stripNodes.count - 1)) / CGFloat(self.stripNodes.count)))
let currentStripMinX = stripInset + CGFloat(self.currentIndex) * (stripWidth + stripSpacing)
let currentStripMidX = floor(currentStripMinX + stripWidth / 2.0)
let lastStripMaxX = stripInset + CGFloat(self.stripNodes.count - 1) * (stripWidth + stripSpacing) + stripWidth
let stripOffset: CGFloat = min(0.0, max(size.width - stripInset - lastStripMaxX, size.width / 2.0 - currentStripMidX))
for i in 0 ..< self.stripNodes.count {
let stripX: CGFloat = stripInset + CGFloat(i) * (stripWidth + stripSpacing)
if i == 0 && self.stripNodes.count == 1 {
self.stripNodes[i].isHidden = true
} else {
self.stripNodes[i].isHidden = false
}
let stripFrame = CGRect(origin: CGPoint(x: stripOffset + stripX, y: 0.0), size: CGSize(width: stripWidth + 1.0, height: 2.0))
stripTransition.updateFrame(node: self.stripNodes[i], frame: stripFrame)
}
if self.currentIndex >= 0 && self.currentIndex < self.stripNodes.count {
var frame = self.stripNodes[self.currentIndex].frame
stripTransition.updateFrame(node: self.loadingStripNode, frame: frame)
if let playbackProgress = self.playbackProgress {
frame.size.width = max(frame.size.height, frame.size.width * playbackProgress)
}
stripTransition.updateFrameAdditive(node: self.activeStripNode, frame: frame)
stripTransition.updateAlpha(node: self.activeStripNode, alpha: self.loading ? 0.0 : 1.0)
stripTransition.updateAlpha(node: self.loadingStripNode, alpha: self.loading ? 1.0 : 0.0)
self.activeStripNode.isHidden = self.stripNodes.count < 2
self.loadingStripNode.isHidden = self.stripNodes.count < 2 || !self.loading
}
self.updateStrips(size: size, itemsAdded: itemsAdded, stripTransition: stripTransition)
if let item = self.items.first, let itemNode = self.itemNodes[item.id] {
if !self.didSetReady {