Video avatar fixes

This commit is contained in:
Ilya Laktyushin 2020-07-10 15:41:51 +03:00
parent 688d692993
commit ab41dbe22a
10 changed files with 104 additions and 64 deletions

View File

@ -113,7 +113,7 @@ private func mappedInsertEntries(context: AccountContext, presentationData: Item
return entries.map { entry -> ListViewInsertItem in
switch entry.entry {
case let .displayTab(theme, text, value):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, sectionId: 0, style: .blocks, updated: { value in
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: true, sectionId: 0, style: .blocks, updated: { value in
nodeInteraction.updateShowCallsTab(value)
}), directionHint: entry.directionHint)
case let .displayTabInfo(theme, text):
@ -130,7 +130,7 @@ private func mappedUpdateEntries(context: AccountContext, presentationData: Item
return entries.map { entry -> ListViewUpdateItem in
switch entry.entry {
case let .displayTab(theme, text, value):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, sectionId: 0, style: .blocks, updated: { value in
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ItemListSwitchItem(presentationData: presentationData, title: text, value: value, enabled: true, noCorners: true, sectionId: 0, style: .blocks, updated: { value in
nodeInteraction.updateShowCallsTab(value)
}), directionHint: entry.directionHint)
case let .displayTabInfo(theme, text):

View File

@ -948,7 +948,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params) || !item.noInsets
let hasCorners = itemListHasRoundedBlockLayout(params) && !item.noInsets
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {

View File

@ -20,13 +20,14 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
let enabled: Bool
let disableLeadingInset: Bool
let maximumNumberOfLines: Int
let noCorners: Bool
public let sectionId: ItemListSectionId
let style: ItemListStyle
let updated: (Bool) -> Void
let activatedWhileDisabled: () -> Void
public let tag: ItemListItemTag?
public init(presentationData: ItemListPresentationData, title: String, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) {
public init(presentationData: ItemListPresentationData, title: String, value: Bool, type: ItemListSwitchItemNodeType = .regular, enableInteractiveChanges: Bool = true, enabled: Bool = true, disableLeadingInset: Bool = false, maximumNumberOfLines: Int = 1, noCorners: Bool = false, sectionId: ItemListSectionId, style: ItemListStyle, updated: @escaping (Bool) -> Void, activatedWhileDisabled: @escaping () -> Void = {}, tag: ItemListItemTag? = nil) {
self.presentationData = presentationData
self.title = title
self.value = value
@ -35,6 +36,7 @@ public class ItemListSwitchItem: ListViewItem, ItemListItem {
self.enabled = enabled
self.disableLeadingInset = disableLeadingInset
self.maximumNumberOfLines = maximumNumberOfLines
self.noCorners = noCorners
self.sectionId = sectionId
self.style = style
self.updated = updated
@ -329,7 +331,7 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
}
let hasCorners = itemListHasRoundedBlockLayout(params)
let hasCorners = itemListHasRoundedBlockLayout(params) && !item.noCorners
var hasTopCorners = false
var hasBottomCorners = false
switch neighbors.top {

View File

@ -22,9 +22,17 @@ public func updateAccountPhoto(account: Account, resource: MediaResource?, video
public struct UploadedPeerPhotoData {
fileprivate let resource: MediaResource
fileprivate let content: UploadedPeerPhotoDataContent
public var isCompleted: Bool {
if case let .result(result) = content, case .inputFile = result {
return true
} else {
return false
}
}
}
private enum UploadedPeerPhotoDataContent {
enum UploadedPeerPhotoDataContent {
case result(MultipartUploadResult)
case error
}

View File

@ -132,7 +132,7 @@ final class AuthorizationSequenceSignUpControllerNode: ASDisplayNode, UITextFiel
self.currentPhotoNode.displayWithoutProcessing = true
self.addPhotoButton = HighlightableButtonNode()
self.addPhotoButton.setAttributedTitle(NSAttributedString(string: "\(self.strings.Login_InfoAvatarAdd)\n\(self.strings.Login_InfoAvatarPhoto)", font: Font.regular(16.0), textColor: self.theme.list.itemPlaceholderTextColor, paragraphAlignment: .center), for: .normal)
self.addPhotoButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIconLarge"), color: self.theme.list.itemPlaceholderTextColor), for: .normal)
self.addPhotoButton.setBackgroundImage(generateCircleImage(diameter: 110.0, lineWidth: 1.0, color: self.theme.list.itemPlaceholderTextColor), for: .normal)
self.addPhotoButton.addSubnode(self.currentPhotoNode)

View File

@ -367,10 +367,8 @@ public func createChannelController(context: AccountContext) -> ViewController {
context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
}
updateState { state in
var state = state
state.avatar = .image(representation, false)
return state
if let timestamp = videoStartTimestamp {
videoStartTimestamp = max(0.0, min(timestamp, result.duration))
}
var value = stat()
@ -413,6 +411,14 @@ public func createChannelController(context: AccountContext) -> ViewController {
} else {
return .single(nil)
}
} |> afterNext { next in
if let next = next, next.isCompleted {
updateState { state in
var state = state
state.avatar = .image(representation, false)
return state
}
}
})
uploadedVideoAvatar = (promise, videoStartTimestamp)
}

View File

@ -625,12 +625,10 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
}
updateState { state in
var state = state
state.avatar = .image(representation, false)
return state
if let timestamp = videoStartTimestamp {
videoStartTimestamp = max(0.0, min(timestamp, result.duration))
}
var value = stat()
if stat(result.fileURL.path, &value) == 0 {
if let data = try? Data(contentsOf: result.fileURL) {
@ -671,6 +669,14 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
} else {
return .single(nil)
}
} |> afterNext { next in
if let next = next, next.isCompleted {
updateState { state in
var state = state
state.avatar = .image(representation, false)
return state
}
}
})
uploadedVideoAvatar = (promise, videoStartTimestamp)
}

View File

@ -268,42 +268,44 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
if let video = videoRepresentations.last, let id = id {
let mediaManager = self.context.sharedContext.mediaManager
let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
videoNode.isUserInteractionEnabled = false
videoNode.isHidden = true
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
if let startTimestamp = video.startTimestamp {
self.playbackStatusDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.15) {
strongSelf.videoNode?.isHidden = false
if videoContent.id != self.videoContent?.id {
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
videoNode.isUserInteractionEnabled = false
videoNode.isHidden = true
if let _ = video.startTimestamp {
self.playbackStatusDisposable.set((videoNode.status
|> map { status -> Bool in
if let status = status, case .playing = status.status {
return true
} else {
return false
}
}
}))
videoNode.seek(startTimestamp)
} else {
self.playbackStatusDisposable.set(nil)
videoNode.isHidden = false
|> filter { playing in
return playing
}
|> take(1)
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
Queue.mainQueue().after(0.12) {
strongSelf.videoNode?.isHidden = false
}
}
}))
} else {
self.playbackStatusDisposable.set(nil)
videoNode.isHidden = false
}
self.videoContent = videoContent
self.videoNode = videoNode
self.statusPromise.set(videoNode.status |> map { ($0, video.startTimestamp) })
self.addSubnode(videoNode)
}
self.videoContent = videoContent
self.videoNode = videoNode
self.statusPromise.set(videoNode.status |> map { ($0, video.startTimestamp) })
self.addSubnode(videoNode)
} else {
if let videoNode = self.videoNode {
self.videoContent = nil
@ -1114,7 +1116,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if let item = item, case let .image(reference, representations, videoRepresentations, immediateThumbnailData) = item, let video = videoRepresentations.last, case let .cloud(imageId, _, _) = reference {
let id = imageId
let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
if videoContent.id != self.videoContent?.id {
let mediaManager = self.context.sharedContext.mediaManager
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .embedded)
@ -1215,14 +1217,14 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode {
self.updatingAvatarOverlay = ASImageNode()
self.updatingAvatarOverlay.displayWithoutProcessing = true
self.updatingAvatarOverlay.displaysAsynchronously = false
self.updatingAvatarOverlay.isHidden = true
self.updatingAvatarOverlay.alpha = 0.0
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.6))
self.statusNode.isUserInteractionEnabled = false
self.iconNode = ASImageNode()
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIconLarge"), color: .white)
self.iconNode.isHidden = true
self.iconNode.alpha = 0.0
super.init()
@ -1251,6 +1253,8 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode {
self.imageNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize))
self.updatingAvatarOverlay.frame = self.imageNode.frame
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .linear)
if canEditPeerInfo(context: self.context, peer: peer) {
var overlayHidden = false
var iconHidden = false
@ -1268,8 +1272,9 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode {
}
}
}
self.iconNode.isHidden = iconHidden
self.updatingAvatarOverlay.isHidden = overlayHidden
transition.updateAlpha(node: self.iconNode, alpha: iconHidden ? 0.0 : 1.0)
transition.updateAlpha(node: self.updatingAvatarOverlay, alpha: overlayHidden ? 0.0 : 1.0)
} else {
if isEditing {
iconHidden = peer.profileImageRepresentations.isEmpty
@ -1279,11 +1284,14 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode {
overlayHidden = true
}
Queue.mainQueue().after(0.15) { [weak self] in
self?.statusNode.transitionToState(.none)
self?.currentRepresentation = nil
self?.imageNode.setSignal(.single(nil))
self?.iconNode.isHidden = iconHidden
self?.updatingAvatarOverlay.isHidden = overlayHidden
guard let strongSelf = self else {
return
}
strongSelf.statusNode.transitionToState(.none)
strongSelf.currentRepresentation = nil
strongSelf.imageNode.setSignal(.single(nil))
transition.updateAlpha(node: strongSelf.iconNode, alpha: iconHidden ? 0.0 : 1.0)
transition.updateAlpha(node: strongSelf.updatingAvatarOverlay, alpha: overlayHidden ? 0.0 : 1.0)
}
}
if !overlayHidden && self.updatingAvatarOverlay.image == nil {
@ -1291,9 +1299,9 @@ final class PeerInfoEditingAvatarOverlayNode: ASDisplayNode {
}
} else {
self.statusNode.transitionToState(.none)
self.iconNode.isHidden = true
self.updatingAvatarOverlay.isHidden = true
self.currentRepresentation = nil
transition.updateAlpha(node: self.iconNode, alpha: 0.0)
transition.updateAlpha(node: self.updatingAvatarOverlay, alpha: 0.0)
}
}
}
@ -1346,7 +1354,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
if let item = item, case let .image(reference, representations, videoRepresentations, immediateThumbnailData) = item, let video = videoRepresentations.last, case let .cloud(imageId, _, _) = reference {
let id = imageId
let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
let videoContent = NativeVideoContent(id: .profileVideo(id, nil), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
if videoContent.id != self.videoContent?.id {
let mediaManager = self.context.sharedContext.mediaManager
let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay)

View File

@ -4067,6 +4067,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
}
if let timestamp = videoStartTimestamp {
videoStartTimestamp = max(0.0, min(timestamp, result.duration))
}
var value = stat()
if stat(result.fileURL.path, &value) == 0 {
if let data = try? Data(contentsOf: result.fileURL) {

View File

@ -32,11 +32,12 @@ public final class NativeVideoContent: UniversalVideoContent {
let fetchAutomatically: Bool
let onlyFullSizeThumbnail: Bool
let autoFetchFullSizeThumbnail: Bool
let startTimestamp: Double?
let continuePlayingWithoutSoundOnLostAudioSession: Bool
let placeholderColor: UIColor
let tempFilePath: String?
public init(id: NativeVideoContentId, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: MediaPlayerStreaming = .none, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true, onlyFullSizeThumbnail: Bool = false, autoFetchFullSizeThumbnail: Bool = false, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor = .white, tempFilePath: String? = nil) {
public init(id: NativeVideoContentId, fileReference: FileMediaReference, imageReference: ImageMediaReference? = nil, streamVideo: MediaPlayerStreaming = .none, loopVideo: Bool = false, enableSound: Bool = true, baseRate: Double = 1.0, fetchAutomatically: Bool = true, onlyFullSizeThumbnail: Bool = false, autoFetchFullSizeThumbnail: Bool = false, startTimestamp: Double? = nil, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor = .white, tempFilePath: String? = nil) {
self.id = id
self.nativeId = id
self.fileReference = fileReference
@ -62,13 +63,14 @@ public final class NativeVideoContent: UniversalVideoContent {
self.fetchAutomatically = fetchAutomatically
self.onlyFullSizeThumbnail = onlyFullSizeThumbnail
self.autoFetchFullSizeThumbnail = autoFetchFullSizeThumbnail
self.startTimestamp = startTimestamp
self.continuePlayingWithoutSoundOnLostAudioSession = continuePlayingWithoutSoundOnLostAudioSession
self.placeholderColor = placeholderColor
self.tempFilePath = tempFilePath
}
public func makeContentNode(postbox: Postbox, audioSession: ManagedAudioSession) -> UniversalVideoContentNode & ASDisplayNode {
return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, continuePlayingWithoutSoundOnLostAudioSession: self.continuePlayingWithoutSoundOnLostAudioSession, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath)
return NativeVideoContentNode(postbox: postbox, audioSessionManager: audioSession, fileReference: self.fileReference, imageReference: self.imageReference, streamVideo: self.streamVideo, loopVideo: self.loopVideo, enableSound: self.enableSound, baseRate: self.baseRate, fetchAutomatically: self.fetchAutomatically, onlyFullSizeThumbnail: self.onlyFullSizeThumbnail, autoFetchFullSizeThumbnail: self.autoFetchFullSizeThumbnail, startTimestamp: self.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: self.continuePlayingWithoutSoundOnLostAudioSession, placeholderColor: self.placeholderColor, tempFilePath: self.tempFilePath)
}
public func isEqual(to other: UniversalVideoContent) -> Bool {
@ -141,7 +143,7 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
private var shouldPlay: Bool = false
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, autoFetchFullSizeThumbnail: Bool, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?) {
init(postbox: Postbox, audioSessionManager: ManagedAudioSession, fileReference: FileMediaReference, imageReference: ImageMediaReference?, streamVideo: MediaPlayerStreaming, loopVideo: Bool, enableSound: Bool, baseRate: Double, fetchAutomatically: Bool, onlyFullSizeThumbnail: Bool, autoFetchFullSizeThumbnail: Bool, startTimestamp: Double?, continuePlayingWithoutSoundOnLostAudioSession: Bool = false, placeholderColor: UIColor, tempFilePath: String?) {
self.postbox = postbox
self.fileReference = fileReference
self.placeholderColor = placeholderColor
@ -226,6 +228,10 @@ private final class NativeVideoContentNode: ASDisplayNode, UniversalVideoContent
self.imageNode.imageUpdated = { [weak self] _ in
self?._ready.set(.single(Void()))
}
if let startTimestamp = startTimestamp {
self.seek(startTimestamp)
}
}
deinit {