Settings screen fixes

This commit is contained in:
Ilya Laktyushin 2020-07-08 16:46:29 +03:00
parent 3dcc32c3c3
commit 054756fa9d
25 changed files with 5123 additions and 4593 deletions

Binary file not shown.

View File

@ -5638,8 +5638,6 @@ Any member of this group will be able to see messages in the channel.";
"Settings.ViewVideo" = "View Video"; "Settings.ViewVideo" = "View Video";
"Settings.RemoveVideo" = "Remove Video"; "Settings.RemoveVideo" = "Remove Video";
"Settings.EditProfileMedia" = "Edit";
"Conversation.Unarchive" = "Unarchive"; "Conversation.Unarchive" = "Unarchive";
"Conversation.UnarchiveDone" = "The chat was moved to your main list."; "Conversation.UnarchiveDone" = "The chat was moved to your main list.";
@ -5681,3 +5679,10 @@ Any member of this group will be able to see messages in the channel.";
"Settings.EditPhoto" = "Edit Photo"; "Settings.EditPhoto" = "Edit Photo";
"Settings.FrequentlyAskedQuestions" = "Frequently Asked Questions"; "Settings.FrequentlyAskedQuestions" = "Frequently Asked Questions";
"Notification.ChangedGroupVideo" = "%@ changed group video";
"Group.MessageVideoUpdated" = "Group video updated";
"Channel.MessageVideoUpdated" = "Channel video updated";
"Conversation.Dice.u1F3C0" = "Send a basketball emoji to shoot a free throw.";
"Conversation.Dice.u26BD" = "Send a football emoji to do a free kick.";

View File

@ -74,6 +74,10 @@ final class TabBarControllerNode: ASDisplayNode {
transition.updateAlpha(node: self.disabledOverlayNode, alpha: value ? 0.0 : 1.0) transition.updateAlpha(node: self.disabledOverlayNode, alpha: value ? 0.0 : 1.0)
} }
func updateIsTabBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition) {
transition.updateAlpha(node: self.tabBarNode, alpha: value ? 0.0 : 1.0)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) {
var tabBarHeight: CGFloat var tabBarHeight: CGFloat
var options: ContainerViewLayoutInsetOptions = [] var options: ContainerViewLayoutInsetOptions = []

View File

@ -185,6 +185,10 @@ open class TabBarController: ViewController {
self.tabBarControllerNode.updateIsTabBarEnabled(value, transition: transition) self.tabBarControllerNode.updateIsTabBarEnabled(value, transition: transition)
} }
public func updateIsTabBarHidden(_ value: Bool, transition: ContainedViewLayoutTransition) {
self.tabBarControllerNode.updateIsTabBarHidden(value, transition: transition)
}
override open func loadDisplayNode() { override open func loadDisplayNode() {
self.displayNode = TabBarControllerNode(theme: self.theme, navigationBar: self.navigationBar, itemSelected: { [weak self] index, longTap, itemNodes in self.displayNode = TabBarControllerNode(theme: self.theme, navigationBar: self.navigationBar, itemSelected: { [weak self] index, longTap, itemNodes in
if let strongSelf = self { if let strongSelf = self {

View File

@ -143,7 +143,7 @@ public extension String {
return (string, fitzModifier) return (string, fitzModifier)
} }
var strippedEmoji: (String) { var strippedEmoji: String {
var string = "" var string = ""
for scalar in self.unicodeScalars { for scalar in self.unicodeScalars {
if scalar.value != 0xfe0f { if scalar.value != 0xfe0f {

View File

@ -417,12 +417,6 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
} }
} }
if velocity.y > 0.0 || distanceFromEquilibrium > 0.0, let centralItemNode = self.pager.centralItemNode() {
if centralItemNode.alternativeDismiss() {
return
}
}
if let centralItemNode = self.pager.centralItemNode(), let (transitionNodeForCentralItem, addToTransitionSurface) = self.transitionDataForCentralItem?(), let node = transitionNodeForCentralItem { if let centralItemNode = self.pager.centralItemNode(), let (transitionNodeForCentralItem, addToTransitionSurface) = self.transitionDataForCentralItem?(), let node = transitionNodeForCentralItem {
contentAnimationCompleted = false contentAnimationCompleted = false
centralItemNode.animateOut(to: node, addToTransitionSurface: addToTransitionSurface, completion: { centralItemNode.animateOut(to: node, addToTransitionSurface: addToTransitionSurface, completion: {

View File

@ -1017,13 +1017,13 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
strongSelf.checkNode = nil strongSelf.checkNode = nil
} }
var rightLabelInset: CGFloat = 15.0 var rightLabelInset: CGFloat = 15.0 + params.rightInset
if let updatedLabelArrowNode = updatedLabelArrowNode { if let updatedLabelArrowNode = updatedLabelArrowNode {
strongSelf.labelArrowNode = updatedLabelArrowNode strongSelf.labelArrowNode = updatedLabelArrowNode
strongSelf.containerNode.addSubnode(updatedLabelArrowNode) strongSelf.containerNode.addSubnode(updatedLabelArrowNode)
if let image = updatedLabelArrowNode.image { if let image = updatedLabelArrowNode.image {
let labelArrowNodeFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightLabelInset - image.size.width + 8.0, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size) let labelArrowNodeFrame = CGRect(origin: CGPoint(x: params.width - rightLabelInset - image.size.width + 8.0, y: floor((contentSize.height - image.size.height) / 2.0)), size: image.size)
transition.updateFrame(node: updatedLabelArrowNode, frame: labelArrowNodeFrame) transition.updateFrame(node: updatedLabelArrowNode, frame: labelArrowNodeFrame)
rightLabelInset += 19.0 rightLabelInset += 19.0
} }

View File

@ -13,6 +13,8 @@ import GalleryUI
import LegacyComponents import LegacyComponents
import LegacyMediaPickerUI import LegacyMediaPickerUI
import SaveToCameraRoll import SaveToCameraRoll
import OverlayStatusController
import PresentationDataUtils
public enum AvatarGalleryEntryId: Hashable { public enum AvatarGalleryEntryId: Hashable {
case topImage case topImage
@ -188,6 +190,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>() private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>()
private let centralItemAttributesDisposable = DisposableSet(); private let centralItemAttributesDisposable = DisposableSet();
public var openAvatarSetup: ((@escaping () -> Void) -> Void)?
public var avatarPhotoEditCompletion: ((UIImage) -> Void)? public var avatarPhotoEditCompletion: ((UIImage) -> Void)?
public var avatarVideoEditCompletion: ((UIImage, URL, TGVideoEditAdjustments?) -> Void)? public var avatarVideoEditCompletion: ((UIImage, URL, TGVideoEditAdjustments?) -> Void)?
@ -349,6 +352,11 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self.dismiss(forceAway: false) self.dismiss(forceAway: false)
} }
private func dismissImmediately() {
self._hiddenMedia.set(.single(nil))
self.presentingViewController?.dismiss(animated: false, completion: nil)
}
private func dismiss(forceAway: Bool) { private func dismiss(forceAway: Bool) {
self.animatedIn.set(false) self.animatedIn.set(false)
@ -621,7 +629,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
} }
} }
private func editEntry(_ rawEntry: AvatarGalleryEntry) { private func openEntryEdit(_ rawEntry: AvatarGalleryEntry) {
let mediaReference: AnyMediaReference let mediaReference: AnyMediaReference
if let video = rawEntry.videoRepresentations.last { if let video = rawEntry.videoRepresentations.last {
mediaReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])])) mediaReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
@ -630,22 +638,15 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
mediaReference = .standalone(media: media) mediaReference = .standalone(media: media)
} }
var dismissStatus: (() -> Void)?
// var cancelImpl: (() -> Void)? let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: {
// let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } dismissStatus?()
// let progressSignal = Signal<Never, NoError> { subscriber in }))
// let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: { dismissStatus = { [weak self, weak statusController] in
// cancelImpl?() self?.editDisposable.set(nil)
// })) statusController?.dismiss()
// strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }
// return ActionDisposable { [weak controller] in self.present(statusController, in: .window(.root))
// Queue.mainQueue().async() {
// controller?.dismiss()
// }
// }
// }
// |> runOn(Queue.mainQueue())
// |> delay(0.15, queue: Queue.mainQueue())
self.editDisposable.set((fetchMediaData(context: self.context, postbox: self.context.account.postbox, mediaReference: mediaReference) self.editDisposable.set((fetchMediaData(context: self.context, postbox: self.context.account.postbox, mediaReference: mediaReference)
|> deliverOnMainQueue).start(next: { [weak self] state, isImage in |> deliverOnMainQueue).start(next: { [weak self] state, isImage in
@ -653,9 +654,11 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
return return
} }
switch state { switch state {
case let .progress(value): case .progress:
break break
case let .data(data): case let .data(data):
dismissStatus?()
let image: UIImage? let image: UIImage?
let video: URL? let video: URL?
if isImage { if isImage {
@ -677,67 +680,131 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
if let strongSelf = self { if let strongSelf = self {
strongSelf.present(c, in: .window(.root), with: a, blockInteraction: true) strongSelf.present(c, in: .window(.root), with: a, blockInteraction: true)
} }
}, imageCompletion: { image in }, imageCompletion: { image in
avatarPhotoEditCompletion?(image) avatarPhotoEditCompletion?(image)
}, videoCompletion: { image, url, adjustments in }, videoCompletion: { image, url, adjustments in
avatarVideoEditCompletion?(image, url, adjustments) avatarVideoEditCompletion?(image, url, adjustments)
}) })
Queue.mainQueue().after(0.4) { Queue.mainQueue().after(0.4) {
strongSelf.dismiss(forceAway: true) strongSelf.dismiss(forceAway: true)
} }
} }
})) }))
} }
private func editEntry(_ rawEntry: AvatarGalleryEntry) {
let actionSheet = ActionSheetController(presentationData: self.presentationData)
let dismissAction: () -> Void = { [weak actionSheet] in
actionSheet?.dismissAnimated()
}
var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: { [weak self] in
dismissAction()
self?.openAvatarSetup?({ [weak self] in
self?.dismissImmediately()
})
}))
if let position = rawEntry.indexData?.position, position > 0 {
let title: String
if let _ = rawEntry.videoRepresentations.last {
title = self.presentationData.strings.ProfilePhoto_SetMainVideo
} else {
title = self.presentationData.strings.ProfilePhoto_SetMainPhoto
}
items.append(ActionSheetButtonItem(title: title, color: .accent, action: { [weak self] in
dismissAction()
self?.setMainEntry(rawEntry)
}))
}
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ProfilePhoto_OpenInEditor, color: .accent, action: { [weak self] in
dismissAction()
self?.openEntryEdit(rawEntry)
}))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.GroupInfo_SetGroupPhotoDelete, color: .destructive, action: { [weak self] in
dismissAction()
self?.deleteEntry(rawEntry)
}))
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
self.view.endEditing(true)
self.present(actionSheet, in: .window(.root))
}
private func deleteEntry(_ rawEntry: AvatarGalleryEntry) { private func deleteEntry(_ rawEntry: AvatarGalleryEntry) {
var entry = rawEntry let proceed = {
if case .topImage = entry, !self.entries.isEmpty { var entry = rawEntry
entry = self.entries[0] if case .topImage = entry, !self.entries.isEmpty {
} entry = self.entries[0]
}
switch entry { switch entry {
case .topImage: case .topImage:
if self.peer.id == self.context.account.peerId { if self.peer.id == self.context.account.peerId {
} else {
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 { } else {
if let index = self.entries.firstIndex(of: entry) { if entry == self.entries.first {
self.entries.remove(at: index) 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.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false)) 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, synchronous: false))
}
} }
}
} }
case let .image(_, reference, _, _, _, _, _, messageId, _): case let .image(_, reference, _, _, _, _, _, messageId, _):
if self.peer.id == self.context.account.peerId { if self.peer.id == self.context.account.peerId {
if let reference = reference { if let reference = reference {
let _ = removeAccountPhoto(network: self.context.account.network, reference: reference).start() let _ = removeAccountPhoto(network: self.context.account.network, reference: reference).start()
} }
if entry == self.entries.first { if entry == self.entries.first {
self.dismiss(forceAway: true) self.dismiss(forceAway: true)
} else { } else {
if let index = self.entries.firstIndex(of: entry) { if let index = self.entries.firstIndex(of: entry) {
self.entries.remove(at: index) self.entries.remove(at: index)
self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false)) self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false))
}
}
} else {
if let messageId = messageId {
let _ = deleteMessagesInteractively(account: self.context.account, messageIds: [messageId], type: .forEveryone).start()
} }
}
} else {
if let messageId = messageId {
let _ = deleteMessagesInteractively(account: self.context.account, messageIds: [messageId], type: .forEveryone).start()
}
if entry == self.entries.first { 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() 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) self.dismiss(forceAway: true)
} else { } else {
if let index = self.entries.firstIndex(of: entry) { if let index = self.entries.firstIndex(of: entry) {
self.entries.remove(at: index) self.entries.remove(at: index)
self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false)) self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false))
}
} }
} }
} }
} }
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
let items: [ActionSheetItem] = [
ActionSheetButtonItem(title: presentationData.strings.Common_Delete, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
proceed()
})
]
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
self.present(actionSheet, in: .window(.root))
} }
} }

View File

@ -213,23 +213,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
} }
@objc private func deleteButtonPressed() { @objc private func deleteButtonPressed() {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } self.delete?()
let actionSheet = ActionSheetController(presentationData: presentationData)
let items: [ActionSheetItem] = [
ActionSheetButtonItem(title: presentationData.strings.Common_Delete, color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
self?.delete?()
})
]
actionSheet.setItemGroups([ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
self.controllerInteraction?.presentController(actionSheet, nil)
} }
@objc private func actionButtonPressed() { @objc private func actionButtonPressed() {

View File

@ -223,13 +223,10 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
self.entry = entry self.entry = entry
var barButtonItems: [UIBarButtonItem] = [] var barButtonItems: [UIBarButtonItem] = []
var footerContent: AvatarGalleryItemFooterContent let footerContent: AvatarGalleryItemFooterContent = .info
if self.peer.id == self.context.account.peerId { if self.peer.id == self.context.account.peerId {
footerContent = .own((entry.indexData?.position ?? 0) == 0) let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Settings_EditPhoto, style: .plain, target: self, action: #selector(self.editPressed))
let rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Settings_EditProfileMedia, style: .plain, target: self, action: #selector(self.editPressed))
barButtonItems.append(rightBarButtonItem) barButtonItems.append(rightBarButtonItem)
} else {
footerContent = .info
} }
self._rightBarButtonItems.set(.single(barButtonItems)) self._rightBarButtonItems.set(.single(barButtonItems))

View File

@ -336,12 +336,14 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
private var validLayout: (CGSize, CGFloat, CGFloat)? private var validLayout: (CGSize, CGFloat, CGFloat)?
private let fieldStyle: SearchBarStyle private let fieldStyle: SearchBarStyle
private let forceSeparator: Bool
private var theme: SearchBarNodeTheme? private var theme: SearchBarNodeTheme?
private var strings: PresentationStrings? private var strings: PresentationStrings?
private let cancelText: String? private let cancelText: String?
public init(theme: SearchBarNodeTheme, strings: PresentationStrings, fieldStyle: SearchBarStyle = .legacy, cancelText: String? = nil) { public init(theme: SearchBarNodeTheme, strings: PresentationStrings, fieldStyle: SearchBarStyle = .legacy, forceSeparator: Bool = false, cancelText: String? = nil) {
self.fieldStyle = fieldStyle self.fieldStyle = fieldStyle
self.forceSeparator = forceSeparator
self.cancelText = cancelText self.cancelText = cancelText
self.backgroundNode = ASDisplayNode() self.backgroundNode = ASDisplayNode()
@ -406,7 +408,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate {
} }
if self.theme != theme { if self.theme != theme {
self.backgroundNode.backgroundColor = theme.background self.backgroundNode.backgroundColor = theme.background
if self.fieldStyle != .modern { if self.fieldStyle != .modern || self.forceSeparator {
self.separatorNode.backgroundColor = theme.separator self.separatorNode.backgroundColor = theme.separator
} }
self.textBackgroundNode.backgroundColor = theme.inputFill self.textBackgroundNode.backgroundColor = theme.inputFill

View File

@ -15,6 +15,7 @@ public final class SearchDisplayController {
private let searchBar: SearchBarNode private let searchBar: SearchBarNode
private let mode: SearchDisplayControllerMode private let mode: SearchDisplayControllerMode
public let contentNode: SearchDisplayControllerContentNode public let contentNode: SearchDisplayControllerContentNode
private var hasSeparator: Bool
private var containerLayout: (ContainerViewLayout, CGFloat)? private var containerLayout: (ContainerViewLayout, CGFloat)?
@ -22,10 +23,11 @@ public final class SearchDisplayController {
private var isSearchingDisposable: Disposable? private var isSearchingDisposable: Disposable?
public init(presentationData: PresentationData, mode: SearchDisplayControllerMode = .navigation, placeholder: String? = nil, contentNode: SearchDisplayControllerContentNode, cancel: @escaping () -> Void) { public init(presentationData: PresentationData, mode: SearchDisplayControllerMode = .navigation, placeholder: String? = nil, hasSeparator: Bool = false, contentNode: SearchDisplayControllerContentNode, cancel: @escaping () -> Void) {
self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings, fieldStyle: .modern) self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: hasSeparator), strings: presentationData.strings, fieldStyle: .modern, forceSeparator: hasSeparator)
self.mode = mode self.mode = mode
self.contentNode = contentNode self.contentNode = contentNode
self.hasSeparator = hasSeparator
self.searchBar.textUpdated = { [weak contentNode] text, _ in self.searchBar.textUpdated = { [weak contentNode] text, _ in
contentNode?.searchTextUpdated(text: text) contentNode?.searchTextUpdated(text: text)
@ -68,7 +70,7 @@ public final class SearchDisplayController {
} }
public func updatePresentationData(_ presentationData: PresentationData) { public func updatePresentationData(_ presentationData: PresentationData) {
self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: false), strings: presentationData.strings) self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationData.theme, hasSeparator: self.hasSeparator), strings: presentationData.strings)
self.contentNode.updatePresentationData(presentationData) self.contentNode.updatePresentationData(presentationData)
} }

View File

@ -95,21 +95,33 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
case let .photoUpdated(image): case let .photoUpdated(image):
if authorName.isEmpty || isChannel { if authorName.isEmpty || isChannel {
if isChannel { if isChannel {
if image != nil { if let image = image {
attributedString = NSAttributedString(string: strings.Channel_MessagePhotoUpdated, font: titleFont, textColor: primaryTextColor) if !image.videoRepresentations.isEmpty {
attributedString = NSAttributedString(string: strings.Channel_MessageVideoUpdated, font: titleFont, textColor: primaryTextColor)
} else {
attributedString = NSAttributedString(string: strings.Channel_MessagePhotoUpdated, font: titleFont, textColor: primaryTextColor)
}
} else { } else {
attributedString = NSAttributedString(string: strings.Channel_MessagePhotoRemoved, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Channel_MessagePhotoRemoved, font: titleFont, textColor: primaryTextColor)
} }
} else { } else {
if image != nil { if let image = image {
attributedString = NSAttributedString(string: strings.Group_MessagePhotoUpdated, font: titleFont, textColor: primaryTextColor) if !image.videoRepresentations.isEmpty {
attributedString = NSAttributedString(string: strings.Group_MessageVideoUpdated, font: titleFont, textColor: primaryTextColor)
} else {
attributedString = NSAttributedString(string: strings.Group_MessagePhotoUpdated, font: titleFont, textColor: primaryTextColor)
}
} else { } else {
attributedString = NSAttributedString(string: strings.Group_MessagePhotoRemoved, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Group_MessagePhotoRemoved, font: titleFont, textColor: primaryTextColor)
} }
} }
} else { } else {
if image != nil { if let image = image {
attributedString = addAttributesToStringWithRanges(strings.Notification_ChangedGroupPhoto(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])) if !image.videoRepresentations.isEmpty {
attributedString = addAttributesToStringWithRanges(strings.Notification_ChangedGroupVideo(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
} else {
attributedString = addAttributesToStringWithRanges(strings.Notification_ChangedGroupPhoto(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
}
} else { } else {
attributedString = addAttributesToStringWithRanges(strings.Notification_RemovedGroupPhoto(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])) attributedString = addAttributesToStringWithRanges(strings.Notification_RemovedGroupPhoto(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
} }

View File

@ -1706,35 +1706,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return return
} }
if controllerInteraction.pollActionState.pollMessageIdsInProgress[id] == nil { if controllerInteraction.pollActionState.pollMessageIdsInProgress[id] == nil {
#if DEBUG
if false {
var found = false
strongSelf.chatDisplayNode.historyNode.forEachVisibleItemNode { itemNode in
if !found, let itemNode = itemNode as? ChatMessageBubbleItemNode, itemNode.item?.message.id == id {
found = true
if strongSelf.selectPollOptionFeedback == nil {
strongSelf.selectPollOptionFeedback = HapticFeedback()
}
strongSelf.selectPollOptionFeedback?.error()
itemNode.animateQuizInvalidOptionSelected()
}
}
return;
}
if false {
if strongSelf.selectPollOptionFeedback == nil {
strongSelf.selectPollOptionFeedback = HapticFeedback()
}
strongSelf.selectPollOptionFeedback?.success()
strongSelf.chatDisplayNode.animateQuizCorrectOptionSelected()
return;
}
if false {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(text: "controllerInteraction.pollActionState.pollMessageIdsInProgress[id] = opaqueIdentifiers"), elevatedLayout: true, action: { _ in return false }), in: .window(.root))
return;
}
#endif
controllerInteraction.pollActionState.pollMessageIdsInProgress[id] = opaqueIdentifiers controllerInteraction.pollActionState.pollMessageIdsInProgress[id] = opaqueIdentifiers
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
let disposables: DisposableDict<MessageId> let disposables: DisposableDict<MessageId>
@ -5726,7 +5697,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
statusText = strongSelf.presentationData.strings.Undo_ChatCleared statusText = strongSelf.presentationData.strings.Undo_ChatCleared
} }
strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: true, action: { value in strongSelf.present(UndoOverlayController(presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(text: statusText), elevatedLayout: false, action: { value in
if value == .commit { if value == .commit {
let _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId, type: type).start(completed: { let _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId, type: type).start(completed: {
self?.chatDisplayNode.historyNode.historyAppearsCleared = false self?.chatDisplayNode.historyNode.historyAppearsCleared = false
@ -7030,13 +7001,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}) })
let value: String? let value: String?
switch dice.emoji { let emoji = dice.emoji.strippedEmoji
switch emoji {
case "🎲": case "🎲":
value = self.presentationData.strings.Conversation_Dice_u1F3B2 value = self.presentationData.strings.Conversation_Dice_u1F3B2
case "🎯": case "🎯":
value = self.presentationData.strings.Conversation_Dice_u1F3AF value = self.presentationData.strings.Conversation_Dice_u1F3AF
case "🏀":
value = self.presentationData.strings.Conversation_Dice_u1F3C0
case "⚽️":
value = self.presentationData.strings.Conversation_Dice_u26BD
default: default:
let emojiHex = dice.emoji.unicodeScalars.map({ String(format:"%02x", $0.value) }).joined().uppercased() let emojiHex = emoji.unicodeScalars.map({ String(format:"%02x", $0.value) }).joined().uppercased()
let key = "Conversation.Dice.u\(emojiHex)" let key = "Conversation.Dice.u\(emojiHex)"
if let string = self.presentationData.strings.primaryComponent.dict[key] { if let string = self.presentationData.strings.primaryComponent.dict[key] {
value = string value = string

View File

@ -6,6 +6,7 @@ import SwiftSignalKit
import Postbox import Postbox
import TelegramCore import TelegramCore
import SyncCore import SyncCore
import AccountContext
import TelegramPresentationData import TelegramPresentationData
import TelegramUIPreferences import TelegramUIPreferences
import TextFormat import TextFormat
@ -13,6 +14,9 @@ import LocalizedPeerData
import UrlEscaping import UrlEscaping
import PhotoResources import PhotoResources
import TelegramStringFormatting import TelegramStringFormatting
import UniversalMediaPlayer
import TelegramUniversalVideoContent
import GalleryUI
private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message, accountPeerId: PeerId) -> NSAttributedString? { private func attributedServiceMessageString(theme: ChatPresentationThemeData, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, message: Message, accountPeerId: PeerId) -> NSAttributedString? {
return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId) return universalServiceMessageString(presentationData: (theme.theme, theme.wallpaper), strings: strings, nameDisplayOrder: nameDisplayOrder, message: message, accountPeerId: accountPeerId)
@ -23,6 +27,9 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
let filledBackgroundNode: LinkHighlightingNode let filledBackgroundNode: LinkHighlightingNode
var linkHighlightingNode: LinkHighlightingNode? var linkHighlightingNode: LinkHighlightingNode?
fileprivate var imageNode: TransformImageNode? fileprivate var imageNode: TransformImageNode?
fileprivate var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
private let fetchDisposable = MetaDisposable() private let fetchDisposable = MetaDisposable()
required init() { required init() {
@ -112,9 +119,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
let imageSize = layoutConstants.instantVideo.dimensions
let imageSize = CGSize(width: 70.0, height: 70.0)
let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
@ -155,6 +160,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 10 + 2), size: imageSize)
if let image = image { if let image = image {
let imageNode: TransformImageNode let imageNode: TransformImageNode
if let current = strongSelf.imageNode { if let current = strongSelf.imageNode {
@ -173,12 +179,53 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
imageNode.setSignal(updateImageSignal) imageNode.setSignal(updateImageSignal)
imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 10 + 2), size: imageSize) imageNode.frame = imageFrame
} else if let imageNode = strongSelf.imageNode { } else if let imageNode = strongSelf.imageNode {
imageNode.removeFromSupernode() imageNode.removeFromSupernode()
strongSelf.imageNode = nil strongSelf.imageNode = nil
} }
if let image = image, let video = image.videoRepresentations.last, let id = image.id?.id {
let videoFileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: image.representations, videoThumbnails: [], immediateThumbnailData: image.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
let videoContent = NativeVideoContent(id: .profileVideo(id), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear)
if videoContent.id != strongSelf.videoContent?.id {
let mediaManager = item.context.sharedContext.mediaManager
let videoNode = UniversalVideoNode(postbox: item.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .secondaryOverlay)
videoNode.isUserInteractionEnabled = false
videoNode.ownsContentNodeUpdated = { [weak self] owns in
if let strongSelf = self {
strongSelf.videoNode?.isHidden = !owns
}
}
strongSelf.videoContent = videoContent
strongSelf.videoNode = videoNode
videoNode.updateLayout(size: imageSize, transition: .immediate)
videoNode.frame = imageFrame
let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: imageSize))
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
videoNode.layer.mask = shape
strongSelf.addSubnode(videoNode)
videoNode.canAttachContent = true
if let videoStartTimestamp = video.startTimestamp {
videoNode.seek(videoStartTimestamp)
} else {
videoNode.seek(0.0)
}
videoNode.play()
}
} else if let videoNode = strongSelf.videoNode {
strongSelf.videoContent = nil
strongSelf.videoNode = nil
videoNode.removeFromSupernode()
}
let _ = apply() let _ = apply()
let _ = backgroundApply() let _ = backgroundApply()

View File

@ -70,6 +70,10 @@ private func rollingAnimationItem(account: Account, emojis: Signal<[TelegramMedi
return .single(ManagedAnimationItem(source: .local("Dice_Rolling"), loop: true)) return .single(ManagedAnimationItem(source: .local("Dice_Rolling"), loop: true))
case "🎯": case "🎯":
return .single(ManagedAnimationItem(source: .local("Darts_Aiming"), loop: true)) return .single(ManagedAnimationItem(source: .local("Darts_Aiming"), loop: true))
case "🏀":
return .single(ManagedAnimationItem(source: .local("Basketball_Bouncing"), loop: true))
case "⚽️":
return .single(ManagedAnimationItem(source: .local("Football_Bouncing"), loop: true))
default: default:
return animationItem(account: account, emojis: emojis, emoji: emoji, value: nil, loop: true) return animationItem(account: account, emojis: emojis, emoji: emoji, value: nil, loop: true)
} }

View File

@ -153,7 +153,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
if let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) { if let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) {
self.arrowNode.image = arrowImage self.arrowNode.image = arrowImage
let arrowFrame = CGRect(origin: CGPoint(x: width - 7.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size) let arrowFrame = CGRect(origin: CGPoint(x: width - 7.0 - arrowImage.size.width - safeInsets.right, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
transition.updateFrame(node: self.arrowNode, frame: arrowFrame) transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
} }

View File

@ -24,17 +24,20 @@ final class PeerInfoState {
let selectedMessageIds: Set<MessageId>? let selectedMessageIds: Set<MessageId>?
let updatingAvatar: PeerInfoUpdatingAvatar? let updatingAvatar: PeerInfoUpdatingAvatar?
let updatingBio: String? let updatingBio: String?
let avatarUploadProgress: CGFloat?
init( init(
isEditing: Bool, isEditing: Bool,
selectedMessageIds: Set<MessageId>?, selectedMessageIds: Set<MessageId>?,
updatingAvatar: PeerInfoUpdatingAvatar?, updatingAvatar: PeerInfoUpdatingAvatar?,
updatingBio: String? updatingBio: String?,
avatarUploadProgress: CGFloat?
) { ) {
self.isEditing = isEditing self.isEditing = isEditing
self.selectedMessageIds = selectedMessageIds self.selectedMessageIds = selectedMessageIds
self.updatingAvatar = updatingAvatar self.updatingAvatar = updatingAvatar
self.updatingBio = updatingBio self.updatingBio = updatingBio
self.avatarUploadProgress = avatarUploadProgress
} }
func withIsEditing(_ isEditing: Bool) -> PeerInfoState { func withIsEditing(_ isEditing: Bool) -> PeerInfoState {
@ -42,7 +45,8 @@ final class PeerInfoState {
isEditing: isEditing, isEditing: isEditing,
selectedMessageIds: self.selectedMessageIds, selectedMessageIds: self.selectedMessageIds,
updatingAvatar: self.updatingAvatar, updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio updatingBio: self.updatingBio,
avatarUploadProgress: self.avatarUploadProgress
) )
} }
@ -51,7 +55,8 @@ final class PeerInfoState {
isEditing: self.isEditing, isEditing: self.isEditing,
selectedMessageIds: selectedMessageIds, selectedMessageIds: selectedMessageIds,
updatingAvatar: self.updatingAvatar, updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio updatingBio: self.updatingBio,
avatarUploadProgress: self.avatarUploadProgress
) )
} }
@ -60,7 +65,8 @@ final class PeerInfoState {
isEditing: self.isEditing, isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds, selectedMessageIds: self.selectedMessageIds,
updatingAvatar: updatingAvatar, updatingAvatar: updatingAvatar,
updatingBio: self.updatingBio updatingBio: self.updatingBio,
avatarUploadProgress: self.avatarUploadProgress
) )
} }
@ -69,7 +75,18 @@ final class PeerInfoState {
isEditing: self.isEditing, isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds, selectedMessageIds: self.selectedMessageIds,
updatingAvatar: self.updatingAvatar, updatingAvatar: self.updatingAvatar,
updatingBio: updatingBio updatingBio: updatingBio,
avatarUploadProgress: self.avatarUploadProgress
)
}
func withAvatarUploadProgress(_ avatarUploadProgress: CGFloat?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
avatarUploadProgress: avatarUploadProgress
) )
} }
} }

View File

@ -17,6 +17,7 @@ import ActivityIndicator
import TelegramUniversalVideoContent import TelegramUniversalVideoContent
import GalleryUI import GalleryUI
import UniversalMediaPlayer import UniversalMediaPlayer
import RadialStatusNode
enum PeerInfoHeaderButtonKey: Hashable { enum PeerInfoHeaderButtonKey: Hashable {
case message case message
@ -164,7 +165,7 @@ enum PeerInfoAvatarListItem: Equatable {
case let .topImage(representations, _): case let .topImage(representations, _):
let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation
return WrappedMediaResourceId(representation.resource.id) return WrappedMediaResourceId(representation.resource.id)
case let .image(_, representations, _, _): case let .image(_, representations, _, _):
let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation
return WrappedMediaResourceId(representation.resource.id) return WrappedMediaResourceId(representation.resource.id)
} }
@ -194,6 +195,13 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
} }
} }
private var statusPromise = Promise<(MediaPlayerStatus?, Double?)?>()
var mediaStatus: Signal<(MediaPlayerStatus?, Double?)?, NoError> {
get {
return self.statusPromise.get()
}
}
var isCentral: Bool = false var isCentral: Bool = false
init(context: AccountContext) { init(context: AccountContext) {
@ -293,13 +301,18 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
self.videoContent = videoContent self.videoContent = videoContent
self.videoNode = videoNode self.videoNode = videoNode
self.statusPromise.set(videoNode.status |> map { ($0, video.startTimestamp) })
self.addSubnode(videoNode) self.addSubnode(videoNode)
} else if let videoNode = self.videoNode { } else {
self.videoContent = nil if let videoNode = self.videoNode {
self.videoNode = nil self.videoContent = nil
self.videoNode = nil
videoNode.removeFromSupernode() videoNode.removeFromSupernode()
}
self.statusPromise.set(.single(nil))
} }
} }
@ -342,6 +355,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
private var items: [PeerInfoAvatarListItem] = [] private var items: [PeerInfoAvatarListItem] = []
private var itemNodes: [WrappedMediaResourceId: PeerInfoAvatarListItemNode] = [:] private var itemNodes: [WrappedMediaResourceId: PeerInfoAvatarListItemNode] = [:]
private var stripNodes: [ASImageNode] = [] private var stripNodes: [ASImageNode] = []
private var activeStripNode: ASImageNode
private let activeStripImage: UIImage private let activeStripImage: UIImage
private var appliedStripNodeCurrentIndex: Int? private var appliedStripNodeCurrentIndex: Int?
var currentIndex: Int = 0 var currentIndex: Int = 0
@ -351,7 +365,9 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
private var isExpanded = false private var isExpanded = false
private let disposable = MetaDisposable() private let disposable = MetaDisposable()
private let positionDisposable = MetaDisposable()
private var initializedList = false private var initializedList = false
private var ignoreNextProfilePhotoUpdate = false
var itemsUpdated: (([PeerInfoAvatarListItem]) -> Void)? var itemsUpdated: (([PeerInfoAvatarListItem]) -> Void)?
let isReady = Promise<Bool>() let isReady = Promise<Bool>()
@ -373,6 +389,68 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
} }
} }
private var playerUpdateTimer: SwiftSignalKit.Timer?
private var playerStatus: (MediaPlayerStatus?, Double?)? {
didSet {
if self.playerStatus?.0 != oldValue?.0 || self.playerStatus?.1 != oldValue?.1 {
if let (playerStatus, _) = self.playerStatus, let status = playerStatus, case .playing = status.status {
self.ensureHasTimer()
} else {
self.stopTimer()
}
self.updateStatus()
}
}
}
private func ensureHasTimer() {
if self.playerUpdateTimer == nil {
let timer = SwiftSignalKit.Timer(timeout: 0.016, repeat: true, completion: { [weak self] in
self?.updateStatus()
}, queue: Queue.mainQueue())
self.playerUpdateTimer = timer
timer.start()
}
}
private var playbackProgress: CGFloat?
private func updateStatus() {
var position: CGFloat = 1.0
if let (status, videoStartTimestamp) = self.playerStatus, let playerStatus = status {
var playerPosition: Double
if !playerStatus.generationTimestamp.isZero, case .playing = playerStatus.status {
playerPosition = playerStatus.timestamp + (CACurrentMediaTime() - playerStatus.generationTimestamp)
} else {
playerPosition = playerStatus.timestamp
}
if let videoStartTimestamp = videoStartTimestamp {
playerPosition -= videoStartTimestamp
if playerPosition < 0.0 {
playerPosition = playerStatus.duration + playerPosition
}
}
if playerStatus.duration.isZero {
position = 0.0
} else {
position = CGFloat(playerPosition / playerStatus.duration)
}
} else {
self.playbackProgress = nil
}
if let size = self.validLayout {
self.playbackProgress = position
self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring))
}
}
private func stopTimer() {
self.playerUpdateTimer?.invalidate()
self.playerUpdateTimer = nil
}
init(context: AccountContext) { init(context: AccountContext) {
self.context = context self.context = context
@ -422,6 +500,9 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.contentNode.addSubnode(self.stripContainerNode) self.contentNode.addSubnode(self.stripContainerNode)
self.activeStripImage = generateSmallHorizontalStretchableFilledCircleImage(diameter: 2.0, color: .white)! self.activeStripImage = generateSmallHorizontalStretchableFilledCircleImage(diameter: 2.0, color: .white)!
self.activeStripNode = ASImageNode()
self.activeStripNode.image = self.activeStripImage
self.highlightContainerNode = ASDisplayNode() self.highlightContainerNode = ASDisplayNode()
self.highlightContainerNode.addSubnode(self.leftHighlightNode) self.highlightContainerNode.addSubnode(self.leftHighlightNode)
self.highlightContainerNode.addSubnode(self.rightHighlightNode) self.highlightContainerNode.addSubnode(self.rightHighlightNode)
@ -545,6 +626,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
deinit { deinit {
self.disposable.dispose() self.disposable.dispose()
self.positionDisposable.dispose()
} }
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@ -635,6 +717,68 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
} }
} }
func setMainItem(_ item: PeerInfoAvatarListItem) {
guard case let .image(image) = item else {
return
}
var items: [PeerInfoAvatarListItem] = []
var entries: [AvatarGalleryEntry] = []
for entry in self.galleryEntries {
switch entry {
case let .topImage(representations, _, immediateThumbnailData):
entries.append(entry)
items.append(.topImage(representations, immediateThumbnailData))
case let .image(id, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData):
if image.0 == reference {
entries.insert(entry, at: 0)
items.insert(.image(reference, representations, videoRepresentations, immediateThumbnailData), at: 0)
} else {
entries.append(entry)
items.append(.image(reference, representations, videoRepresentations, immediateThumbnailData))
}
}
}
self.galleryEntries = entries
self.items = items
self.itemsUpdated?(items)
self.currentIndex = 0
self.ignoreNextProfilePhotoUpdate = true
if let size = self.validLayout {
self.updateItems(size: size, update: true, transition: .immediate, stripTransition: .immediate, synchronous: true)
}
}
func deleteItem(_ item: PeerInfoAvatarListItem) -> Bool {
guard case let .image(image) = item else {
return
}
var items: [PeerInfoAvatarListItem] = []
var entries: [AvatarGalleryEntry] = []
var previousIndex = self.currentIndex
for entry in self.galleryEntries {
switch entry {
case let .topImage(representations, _, immediateThumbnailData):
entries.append(entry)
items.append(.topImage(representations, immediateThumbnailData))
case let .image(id, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData):
if image.0 != reference {
entries.append(entry)
items.append(.image(reference, representations, videoRepresentations, immediateThumbnailData))
}
}
}
self.galleryEntries = entries
self.items = items
self.itemsUpdated?(items)
self.currentIndex = max(0, previousIndex - 1)
self.ignoreNextProfilePhotoUpdate = true
if let size = self.validLayout {
self.updateItems(size: size, update: true, transition: .immediate, stripTransition: .immediate, synchronous: true)
}
return items.count == 0
}
func update(size: CGSize, peer: Peer?, isExpanded: Bool, transition: ContainedViewLayoutTransition) { func update(size: CGSize, peer: Peer?, isExpanded: Bool, transition: ContainedViewLayoutTransition) {
self.validLayout = size self.validLayout = size
self.isExpanded = isExpanded self.isExpanded = isExpanded
@ -659,6 +803,15 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
synchronous = true synchronous = true
} }
if strongSelf.ignoreNextProfilePhotoUpdate {
if entries.count == 1, let first = entries.first, case .topImage = first {
return
} else {
strongSelf.ignoreNextProfilePhotoUpdate = false
synchronous = true
}
}
var items: [PeerInfoAvatarListItem] = [] var items: [PeerInfoAvatarListItem] = []
for entry in entries { for entry in entries {
switch entry { switch entry {
@ -746,9 +899,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
stripNode.displaysAsynchronously = false stripNode.displaysAsynchronously = false
stripNode.displayWithoutProcessing = true stripNode.displayWithoutProcessing = true
stripNode.image = self.activeStripImage stripNode.image = self.activeStripImage
if stripNodes.count != self.currentIndex { stripNode.alpha = 0.2
stripNode.alpha = 0.2
}
self.stripNodes.append(stripNode) self.stripNodes.append(stripNode)
self.stripContainerNode.addSubnode(stripNode) self.stripContainerNode.addSubnode(stripNode)
} }
@ -758,20 +909,22 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.stripNodes.remove(at: i) self.stripNodes.remove(at: i)
} }
} }
self.stripContainerNode.addSubnode(self.activeStripNode)
} }
if self.appliedStripNodeCurrentIndex != self.currentIndex { if self.appliedStripNodeCurrentIndex != self.currentIndex {
if let appliedStripNodeCurrentIndex = self.appliedStripNodeCurrentIndex { if !self.itemNodes.isEmpty {
if appliedStripNodeCurrentIndex >= 0 && appliedStripNodeCurrentIndex < self.stripNodes.count { self.appliedStripNodeCurrentIndex = self.currentIndex
let previousAlpha = self.stripNodes[appliedStripNodeCurrentIndex].alpha
self.stripNodes[appliedStripNodeCurrentIndex].alpha = 0.2
if previousAlpha == 1.0 {
self.stripNodes[appliedStripNodeCurrentIndex].layer.animateAlpha(from: 1.0, to: 0.2, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
}
}
} }
self.appliedStripNodeCurrentIndex = self.currentIndex
if self.currentIndex >= 0 && self.currentIndex < self.stripNodes.count { if let currentItemNode = self.currentItemNode {
self.stripNodes[self.currentIndex].alpha = 1.0 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 { if hadOneStripNode && self.stripNodes.count > 1 {
@ -795,6 +948,15 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
stripTransition.updateFrame(node: self.stripNodes[i], frame: stripFrame) stripTransition.updateFrame(node: self.stripNodes[i], frame: stripFrame)
} }
if self.currentIndex >= 0 && self.currentIndex < self.stripNodes.count {
var frame = self.stripNodes[self.currentIndex].frame
if let playbackProgress = self.playbackProgress {
frame.size.width = max(frame.size.height, frame.size.width * playbackProgress)
}
stripTransition.updateFrameAdditive(node: self.activeStripNode, frame: frame)
self.activeStripNode.isHidden = self.stripNodes.count < 2
}
if let item = self.items.first, let itemNode = self.itemNodes[item.id] { if let item = self.items.first, let itemNode = self.itemNodes[item.id] {
if !self.didSetReady { if !self.didSetReady {
self.didSetReady = true self.didSetReady = true
@ -933,11 +1095,15 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
final class PeerInfoEditingAvatarNode: ASDisplayNode { final class PeerInfoEditingAvatarNode: ASDisplayNode {
let context: AccountContext let context: AccountContext
let avatarNode: AvatarNode let avatarNode: AvatarNode
fileprivate var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
private let updatingAvatarOverlay: ASImageNode private let updatingAvatarOverlay: ASImageNode
private let activityIndicator: ActivityIndicator private var statusNode: RadialStatusNode
var tapped: (() -> Void)? var tapped: (() -> Void)?
var cancel: (() -> Void)?
init(context: AccountContext) { init(context: AccountContext) {
self.context = context self.context = context
@ -949,19 +1115,21 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
self.updatingAvatarOverlay.displaysAsynchronously = false self.updatingAvatarOverlay.displaysAsynchronously = false
self.updatingAvatarOverlay.isHidden = true self.updatingAvatarOverlay.isHidden = true
self.activityIndicator = ActivityIndicator(type: .custom(.white, 22.0, 1.0, false)) self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.6))
self.activityIndicator.isHidden = true self.statusNode.isUserInteractionEnabled = false
super.init() super.init()
self.addSubnode(self.avatarNode) self.addSubnode(self.avatarNode)
self.avatarNode.frame = CGRect(origin: CGPoint(x: -50.0, y: -50.0), size: CGSize(width: 100.0, height: 100.0)) self.avatarNode.frame = CGRect(origin: CGPoint(x: -50.0, y: -50.0), size: CGSize(width: 100.0, height: 100.0))
self.updatingAvatarOverlay.frame = self.avatarNode.frame self.updatingAvatarOverlay.frame = self.avatarNode.frame
let indicatorSize = self.activityIndicator.measure(CGSize(width: 100.0, height: 100.0))
self.activityIndicator.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(self.avatarNode.frame.midX - indicatorSize.width / 2.0), y: floorToScreenPixels(self.avatarNode.frame.midY - indicatorSize.height / 2.0)), size: indicatorSize) let radialStatusSize: CGFloat = true ? 50.0 : 32.0
let imagePosition = self.avatarNode.position
statusNode.frame = CGRect(origin: CGPoint(x: floor(imagePosition.x - radialStatusSize / 2.0), y: floor(imagePosition.y - radialStatusSize / 2.0)), size: CGSize(width: radialStatusSize, height: radialStatusSize))
self.addSubnode(self.updatingAvatarOverlay) self.addSubnode(self.updatingAvatarOverlay)
self.addSubnode(self.activityIndicator) self.addSubnode(self.statusNode)
self.avatarNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) self.avatarNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
} }
@ -972,35 +1140,77 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
} }
} }
func update(peer: Peer?, updatingAvatar: PeerInfoUpdatingAvatar?, theme: PresentationTheme, avatarSize: CGFloat) { func update(peer: Peer?, item: PeerInfoAvatarListItem?, updatingAvatar: PeerInfoUpdatingAvatar?, uploadProgress: CGFloat?, theme: PresentationTheme, avatarSize: CGFloat, isEditing: Bool) {
if let peer = peer { if let peer = peer {
let overrideImage: AvatarNodeImageOverride? let overrideImage: AvatarNodeImageOverride?
var hideVideo = false
if canEditPeerInfo(context: self.context, peer: peer) { if canEditPeerInfo(context: self.context, peer: peer) {
if let updatingAvatar = updatingAvatar { if let updatingAvatar = updatingAvatar {
switch updatingAvatar { switch updatingAvatar {
case let .image(representation): case let .image(representation):
overrideImage = .image(representation) overrideImage = .image(representation)
hideVideo = true
case .none: case .none:
overrideImage = AvatarNodeImageOverride.none overrideImage = AvatarNodeImageOverride.none
} }
self.activityIndicator.isHidden = false self.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: uploadProgress ?? 0.0, cancelEnabled: true))
self.updatingAvatarOverlay.isHidden = false self.updatingAvatarOverlay.isHidden = false
if self.updatingAvatarOverlay.image == nil { if self.updatingAvatarOverlay.image == nil {
self.updatingAvatarOverlay.image = generateFilledCircleImage(diameter: avatarSize, color: UIColor(white: 0.0, alpha: 0.4), backgroundColor: nil) self.updatingAvatarOverlay.image = generateFilledCircleImage(diameter: avatarSize, color: UIColor(white: 0.0, alpha: 0.4), backgroundColor: nil)
} }
} else { } else {
overrideImage = .editAvatarIcon overrideImage = .editAvatarIcon
self.activityIndicator.isHidden = true self.statusNode.transitionToState(.none)
self.updatingAvatarOverlay.isHidden = true self.updatingAvatarOverlay.isHidden = true
} }
} else { } else {
overrideImage = nil overrideImage = nil
self.activityIndicator.isHidden = true self.statusNode.transitionToState(.none)
self.updatingAvatarOverlay.isHidden = true self.updatingAvatarOverlay.isHidden = true
} }
self.avatarNode.font = avatarPlaceholderFont(size: floor(avatarSize * 16.0 / 37.0)) self.avatarNode.font = avatarPlaceholderFont(size: floor(avatarSize * 16.0 / 37.0))
self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, overrideImage: overrideImage, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize)) self.avatarNode.setPeer(context: self.context, theme: theme, peer: peer, overrideImage: overrideImage, synchronousLoad: false, displayDimensions: CGSize(width: avatarSize, height: avatarSize))
self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize)) self.avatarNode.frame = CGRect(origin: CGPoint(x: -avatarSize / 2.0, y: -avatarSize / 2.0), size: CGSize(width: avatarSize, height: avatarSize))
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), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, autoFetchFullSizeThumbnail: true, 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)
videoNode.isUserInteractionEnabled = false
videoNode.ownsContentNodeUpdated = { [weak self] owns in
if let strongSelf = self {
strongSelf.videoNode?.isHidden = !owns
}
}
self.videoContent = videoContent
self.videoNode = videoNode
let maskPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(), size: self.avatarNode.frame.size))
let shape = CAShapeLayer()
shape.path = maskPath.cgPath
videoNode.layer.mask = shape
self.insertSubnode(videoNode, aboveSubnode: self.avatarNode)
}
} else if let videoNode = self.videoNode {
self.videoContent = nil
self.videoNode = nil
videoNode.removeFromSupernode()
}
if let videoNode = self.videoNode {
videoNode.isHidden = hideVideo
videoNode.updateLayout(size: self.avatarNode.frame.size, transition: .immediate)
videoNode.frame = self.avatarNode.frame
if isEditing != videoNode.canAttachContent {
videoNode.canAttachContent = isEditing
}
}
} }
} }
@ -1097,7 +1307,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode {
func animateAvatarCollapse(transition: ContainedViewLayoutTransition) { func animateAvatarCollapse(transition: ContainedViewLayoutTransition) {
if let currentItemNode = self.listContainerNode.currentItemNode, case .animated = transition { if let currentItemNode = self.listContainerNode.currentItemNode, case .animated = transition {
if let videoNode = self.avatarContainerNode.videoNode { if let _ = self.avatarContainerNode.videoNode {
} else if let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage { } else if let unroundedImage = self.avatarContainerNode.avatarNode.unroundedImage {
let avatarCopyView = UIImageView() let avatarCopyView = UIImageView()
@ -1673,7 +1883,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 20.0 var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 20.0
if isSettings { if canEditPeerInfo(context: self.context, peer: peer) {
if self.avatarTextNode.supernode == nil { if self.avatarTextNode.supernode == nil {
self.addSubnode(self.avatarTextNode) self.addSubnode(self.avatarTextNode)
} }
@ -1880,7 +2090,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)
self.addSubnode(self.expandedBackgroundNode) self.addSubnode(self.expandedBackgroundNode)
self.addSubnode(self.separatorNode)
self.titleNodeContainer.addSubnode(self.titleNode) self.titleNodeContainer.addSubnode(self.titleNode)
self.regularContentNode.addSubnode(self.titleNodeContainer) self.regularContentNode.addSubnode(self.titleNodeContainer)
self.subtitleNodeContainer.addSubnode(self.subtitleNode) self.subtitleNodeContainer.addSubnode(self.subtitleNode)
@ -1892,6 +2101,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.addSubnode(self.regularContentNode) self.addSubnode(self.regularContentNode)
self.addSubnode(self.editingContentNode) self.addSubnode(self.editingContentNode)
self.addSubnode(self.navigationButtonContainer) self.addSubnode(self.navigationButtonContainer)
self.addSubnode(self.separatorNode)
self.avatarListNode.avatarContainerNode.tapped = { [weak self] in self.avatarListNode.avatarContainerNode.tapped = { [weak self] in
self?.initiateAvatarExpansion() self?.initiateAvatarExpansion()
@ -2080,7 +2290,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let usernameNodeLayout = self.usernameNode.updateLayout(states: [ let usernameNodeLayout = self.usernameNode.updateLayout(states: [
TitleNodeStateRegular: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: titleConstrainedSize.width, height: titleConstrainedSize.height)), TitleNodeStateRegular: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: titleConstrainedSize.width, height: titleConstrainedSize.height)),
TitleNodeStateExpanded: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: titleConstrainedSize.width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height)) TitleNodeStateExpanded: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height))
], mainState: TitleNodeStateRegular) ], mainState: TitleNodeStateRegular)
self.titleNode.update(stateFractions: [ self.titleNode.update(stateFractions: [
@ -2182,7 +2392,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} }
self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, theme: presentationData.theme, transition: transition) self.avatarListNode.update(size: CGSize(), avatarSize: avatarSize, isExpanded: self.isAvatarExpanded, peer: peer, theme: presentationData.theme, transition: transition)
self.editingContentNode.avatarNode.update(peer: peer, updatingAvatar: state.updatingAvatar, theme: presentationData.theme, avatarSize: avatarSize) self.editingContentNode.avatarNode.update(peer: peer, item: self.avatarListNode.item, updatingAvatar: state.updatingAvatar, uploadProgress: state.avatarUploadProgress, theme: presentationData.theme, avatarSize: avatarSize, isEditing: state.isEditing)
if additive { if additive {
transition.updateSublayerTransformScaleAdditive(node: self.avatarListNode.avatarContainerNode, scale: avatarScale) transition.updateSublayerTransformScaleAdditive(node: self.avatarListNode.avatarContainerNode, scale: avatarScale)
} else { } else {
@ -2535,8 +2745,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let resolvedHeight: CGFloat let resolvedHeight: CGFloat
if state.isEditing { if state.isEditing {
resolvedHeight = editingContentHeight resolvedHeight = editingContentHeight
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + resolvedHeight - contentOffset), size: CGSize(width: width, height: 2000.0)) backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + max(navigationHeight, resolvedHeight - contentOffset)), size: CGSize(width: width, height: 2000.0))
separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: resolvedHeight - contentOffset), size: CGSize(width: width, height: UIScreenPixel)) separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: max(navigationHeight, resolvedHeight - contentOffset)), size: CGSize(width: width, height: UIScreenPixel))
} else { } else {
resolvedHeight = resolvedRegularHeight resolvedHeight = resolvedRegularHeight
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + apparentHeight), size: CGSize(width: width, height: 2000.0)) backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + apparentHeight), size: CGSize(width: width, height: 2000.0))

View File

@ -47,6 +47,7 @@ import AuthTransferUI
import DeviceAccess import DeviceAccess
import LegacyMediaPickerUI import LegacyMediaPickerUI
import TelegramNotices import TelegramNotices
import SaveToCameraRoll
protocol PeerInfoScreenItem: class { protocol PeerInfoScreenItem: class {
var id: AnyHashable { get } var id: AnyHashable { get }
@ -648,17 +649,24 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
if let settings = data.globalSettings { if let settings = data.globalSettings {
if settings.suggestPhoneNumberConfirmation { if settings.suggestPhoneNumberConfirmation, let peer = data.peer as? TelegramUser {
// let phoneNumber = formatPhoneNumber(peer.phone ?? "") //
// entries.append(.phoneInfo(presentationData.theme, presentationData.strings.Settings_CheckPhoneNumberTitle(phoneNumber).0, presentationData.strings.Settings_CheckPhoneNumberText)) // entries.append(.phoneInfo(presentationData.theme, presentationData.strings.Settings_CheckPhoneNumberTitle(phoneNumber).0, presentationData.strings.Settings_CheckPhoneNumberText))
// entries.append(.keepPhone(presentationData.theme, presentationData.strings.Settings_KeepPhoneNumber(phoneNumber).0)) // entries.append(.keepPhone(presentationData.theme, presentationData.strings.Settings_KeepPhoneNumber(phoneNumber).0))
// entries.append(.changePhone(presentationData.theme, presentationData.strings.Settings_ChangePhoneNumber)) // entries.append(.changePhone(presentationData.theme, presentationData.strings.Settings_ChangePhoneNumber))
let phoneNumber = formatPhoneNumber(peer.phone ?? "")
items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_KeepPhoneNumber(phoneNumber).0, action: {
interaction.openSettings(.addAccount)
}))
items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_ChangePhoneNumber, action: {
interaction.openSettings(.addAccount)
}))
} }
if !settings.accountsAndPeers.isEmpty { if !settings.accountsAndPeers.isEmpty {
for (peerAccount, peer, badgeCount) in settings.accountsAndPeers { for (peerAccount, peer, badgeCount) in settings.accountsAndPeers {
let member: PeerInfoMember = .account(peer: RenderedPeer(peer: peer)) let member: PeerInfoMember = .account(peer: RenderedPeer(peer: peer))
items[.accounts]!.append(PeerInfoScreenMemberItem(id: member.id, context: context, enclosingPeer: nil, member: member, badge: badgeCount > 0 ? "\(badgeCount)" : nil, action: { action in items[.accounts]!.append(PeerInfoScreenMemberItem(id: member.id, context: context.sharedContext.makeTempAccountContext(account: peerAccount), enclosingPeer: nil, member: member, badge: badgeCount > 0 ? "\(compactNumericCountString(Int(badgeCount), decimalSeparator: presentationData.dateTimeFormat.decimalSeparator))" : nil, action: { action in
switch action { switch action {
case .open: case .open:
interaction.switchToAccount(peerAccount.id) interaction.switchToAccount(peerAccount.id)
@ -1359,7 +1367,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
isEditing: false, isEditing: false,
selectedMessageIds: nil, selectedMessageIds: nil,
updatingAvatar: nil, updatingAvatar: nil,
updatingBio: nil updatingBio: nil,
avatarUploadProgress: nil
) )
private let nearbyPeerDistance: Int32? private let nearbyPeerDistance: Int32?
private var dataDisposable: Disposable? private var dataDisposable: Disposable?
@ -1371,6 +1380,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private let addMemberDisposable = MetaDisposable() private let addMemberDisposable = MetaDisposable()
private let preloadHistoryDisposable = MetaDisposable() private let preloadHistoryDisposable = MetaDisposable()
private let editAvatarDisposable = MetaDisposable()
private let updateAvatarDisposable = MetaDisposable() private let updateAvatarDisposable = MetaDisposable()
private let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil) private let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
@ -2054,14 +2064,22 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
guard let strongSelf = self, let peer = strongSelf.data?.peer, peer.smallProfileImage != nil else { guard let strongSelf = self, let peer = strongSelf.data?.peer, peer.smallProfileImage != nil else {
return return
} }
if strongSelf.hapticFeedback == nil {
strongSelf.hapticFeedback = HapticFeedback() if strongSelf.state.updatingAvatar != nil {
strongSelf.updateAvatarDisposable.set(nil)
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
return
} }
strongSelf.hapticFeedback?.tap()
let entriesPromise = Promise<[AvatarGalleryEntry]>(entries) let entriesPromise = Promise<[AvatarGalleryEntry]>(entries)
let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, sourceHasRoundCorners: !strongSelf.headerNode.isAvatarExpanded, remoteEntries: entriesPromise, centralEntryIndex: centralEntry.flatMap { entries.firstIndex(of: $0) }, replaceRootController: { controller, ready in let galleryController = AvatarGalleryController(context: strongSelf.context, peer: peer, sourceHasRoundCorners: !strongSelf.headerNode.isAvatarExpanded, remoteEntries: entriesPromise, centralEntryIndex: centralEntry.flatMap { entries.firstIndex(of: $0) }, replaceRootController: { controller, ready in
}) })
galleryController.openAvatarSetup = { [weak self] completion in
self?.openAvatarForEditing(hasRemove: false, completion: completion)
}
galleryController.avatarPhotoEditCompletion = { [weak self] image in galleryController.avatarPhotoEditCompletion = { [weak self] image in
self?.updateProfilePhoto(image) self?.updateProfilePhoto(image)
} }
@ -2084,7 +2102,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
self.headerNode.requestOpenAvatarForEditing = { [weak self] in self.headerNode.requestOpenAvatarForEditing = { [weak self] in
self?.openAvatarForEditing() guard let strongSelf = self else {
return
}
if strongSelf.state.updatingAvatar != nil {
strongSelf.updateAvatarDisposable.set(nil)
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
} else {
strongSelf.openAvatarForEditing()
}
} }
self.headerNode.requestUpdateLayout = { [weak self] in self.headerNode.requestUpdateLayout = { [weak self] in
@ -2102,6 +2131,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
switch key { switch key {
case .edit: case .edit:
(strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.2, curve: .easeInOut))
strongSelf.state = strongSelf.state.withIsEditing(true) strongSelf.state = strongSelf.state.withIsEditing(true)
if strongSelf.headerNode.isAvatarExpanded { if strongSelf.headerNode.isAvatarExpanded {
strongSelf.headerNode.updateIsAvatarExpanded(false, transition: .immediate) strongSelf.headerNode.updateIsAvatarExpanded(false, transition: .immediate)
@ -2115,6 +2145,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, completion: nil) }, completion: nil)
strongSelf.controller?.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, style: .plain, target: strongSelf, action: #selector(strongSelf.editingCancelPressed)), animated: true) strongSelf.controller?.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, style: .plain, target: strongSelf, action: #selector(strongSelf.editingCancelPressed)), animated: true)
case .done, .cancel: case .done, .cancel:
(strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.2, curve: .easeInOut))
strongSelf.view.endEditing(true) strongSelf.view.endEditing(true)
if case .done = key { if case .done = key {
guard let data = strongSelf.data else { guard let data = strongSelf.data else {
@ -2128,10 +2159,11 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
let bio = strongSelf.state.updatingBio let bio = strongSelf.state.updatingBio
if peer.firstName != firstName || peer.lastName != lastName || (bio != nil && bio != cachedData.about) { if peer.firstName != firstName || peer.lastName != lastName || (bio != nil && bio != cachedData.about) {
var updateNameSignal: Signal<Void, NoError> = .complete() var updateNameSignal: Signal<Void, NoError> = .complete()
var hasProgress = false
if peer.firstName != firstName || peer.lastName != lastName { if peer.firstName != firstName || peer.lastName != lastName {
updateNameSignal = updateAccountPeerName(account: context.account, firstName: firstName, lastName: lastName) updateNameSignal = updateAccountPeerName(account: context.account, firstName: firstName, lastName: lastName)
hasProgress = true
} }
var updateBioSignal: Signal<Void, NoError> = .complete() var updateBioSignal: Signal<Void, NoError> = .complete()
if let bio = bio, bio != cachedData.about { if let bio = bio, bio != cachedData.about {
@ -2139,6 +2171,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|> `catch` { _ -> Signal<Void, NoError> in |> `catch` { _ -> Signal<Void, NoError> in
return .complete() return .complete()
} }
hasProgress = true
} }
var dismissStatus: (() -> Void)? var dismissStatus: (() -> Void)?
@ -2149,8 +2182,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.activeActionDisposable.set(nil) self?.activeActionDisposable.set(nil)
statusController?.dismiss() statusController?.dismiss()
} }
strongSelf.controller?.present(statusController, in: .window(.root)) if hasProgress {
strongSelf.controller?.present(statusController, in: .window(.root))
}
strongSelf.activeActionDisposable.set((combineLatest(updateNameSignal, updateBioSignal) |> deliverOnMainQueue strongSelf.activeActionDisposable.set((combineLatest(updateNameSignal, updateBioSignal) |> deliverOnMainQueue
|> deliverOnMainQueue).start(error: { _ in |> deliverOnMainQueue).start(error: { _ in
dismissStatus?() dismissStatus?()
@ -2251,12 +2285,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} else { } else {
var updateDataSignals: [Signal<Never, Void>] = [] var updateDataSignals: [Signal<Never, Void>] = []
var hasProgress = false
if title != group.title { if title != group.title {
updateDataSignals.append( updateDataSignals.append(
updatePeerTitle(account: strongSelf.context.account, peerId: group.id, title: title) updatePeerTitle(account: strongSelf.context.account, peerId: group.id, title: title)
|> ignoreValues |> ignoreValues
|> mapError { _ in return Void() } |> mapError { _ in return Void() }
) )
hasProgress = true
} }
if description != (data.cachedData as? CachedGroupData)?.about { if description != (data.cachedData as? CachedGroupData)?.about {
updateDataSignals.append( updateDataSignals.append(
@ -2264,6 +2300,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|> ignoreValues |> ignoreValues
|> mapError { _ in return Void() } |> mapError { _ in return Void() }
) )
hasProgress = true
} }
var dismissStatus: (() -> Void)? var dismissStatus: (() -> Void)?
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: { let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: {
@ -2273,8 +2310,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.activeActionDisposable.set(nil) self?.activeActionDisposable.set(nil)
statusController?.dismiss() statusController?.dismiss()
} }
strongSelf.controller?.present(statusController, in: .window(.root)) if hasProgress {
strongSelf.controller?.present(statusController, in: .window(.root))
}
strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals) strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals)
|> deliverOnMainQueue).start(error: { _ in |> deliverOnMainQueue).start(error: { _ in
dismissStatus?() dismissStatus?()
@ -2305,13 +2343,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
strongSelf.headerNode.editingContentNode.shakeTextForKey(.title) strongSelf.headerNode.editingContentNode.shakeTextForKey(.title)
} else { } else {
var updateDataSignals: [Signal<Never, Void>] = [] var updateDataSignals: [Signal<Never, Void>] = []
var hasProgress = false
if title != channel.title { if title != channel.title {
updateDataSignals.append( updateDataSignals.append(
updatePeerTitle(account: strongSelf.context.account, peerId: channel.id, title: title) updatePeerTitle(account: strongSelf.context.account, peerId: channel.id, title: title)
|> ignoreValues |> ignoreValues
|> mapError { _ in return Void() } |> mapError { _ in return Void() }
) )
hasProgress = true
} }
if description != (data.cachedData as? CachedChannelData)?.about { if description != (data.cachedData as? CachedChannelData)?.about {
updateDataSignals.append( updateDataSignals.append(
@ -2319,6 +2358,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|> ignoreValues |> ignoreValues
|> mapError { _ in return Void() } |> mapError { _ in return Void() }
) )
hasProgress = true
} }
var dismissStatus: (() -> Void)? var dismissStatus: (() -> Void)?
@ -2329,8 +2369,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.activeActionDisposable.set(nil) self?.activeActionDisposable.set(nil)
statusController?.dismiss() statusController?.dismiss()
} }
strongSelf.controller?.present(statusController, in: .window(.root)) if hasProgress {
strongSelf.controller?.present(statusController, in: .window(.root))
}
strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals) strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals)
|> deliverOnMainQueue).start(error: { _ in |> deliverOnMainQueue).start(error: { _ in
dismissStatus?() dismissStatus?()
@ -2461,6 +2502,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.resolveUrlDisposable.dispose() self.resolveUrlDisposable.dispose()
self.hiddenAvatarRepresentationDisposable.dispose() self.hiddenAvatarRepresentationDisposable.dispose()
self.toggleShouldChannelMessagesSignaturesDisposable.dispose() self.toggleShouldChannelMessagesSignaturesDisposable.dispose()
self.editAvatarDisposable.dispose()
self.updateAvatarDisposable.dispose() self.updateAvatarDisposable.dispose()
self.selectAddMemberDisposable.dispose() self.selectAddMemberDisposable.dispose()
self.addMemberDisposable.dispose() self.addMemberDisposable.dispose()
@ -3651,6 +3693,122 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
} }
private func editAvatarItem(_ item: PeerInfoAvatarListItem) {
guard case let .image(reference, representations, videoRepresentations, _) = item else {
return
}
let mediaReference: AnyMediaReference
if let video = videoRepresentations.last {
mediaReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [])]))
} else {
let media = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])
mediaReference = .standalone(media: media)
}
var dismissStatus: (() -> Void)?
let statusController = OverlayStatusController(theme: self.presentationData.theme, type: .loading(cancelled: {
dismissStatus?()
}))
dismissStatus = { [weak self, weak statusController] in
self?.editAvatarDisposable.set(nil)
statusController?.dismiss()
}
self.controller?.present(statusController, in: .window(.root))
self.editAvatarDisposable.set((fetchMediaData(context: self.context, postbox: self.context.account.postbox, mediaReference: mediaReference)
|> deliverOnMainQueue).start(next: { [weak self] state, isImage in
guard let strongSelf = self else {
return
}
switch state {
case .progress:
break
case let .data(data):
dismissStatus?()
let image: UIImage?
let video: URL?
if isImage {
if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
image = UIImage(data: fileData)
} else {
image = nil
}
video = nil
} else {
image = nil
video = URL(fileURLWithPath: data.path)
}
presentLegacyAvatarEditor(theme: strongSelf.presentationData.theme, image: image, video: video, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.controller?.present(c, in: .window(.root), with: a, blockInteraction: true)
}
}, imageCompletion: { [weak self] image in
self?.updateProfilePhoto(image)
}, videoCompletion: { [weak self] image, url, adjustments in
self?.updateProfileVideo(image, url: url, adjustments: adjustments)
})
}
}))
}
private func setMainAvatar(_ item: PeerInfoAvatarListItem) {
if self.data?.peer?.id == self.context.account.peerId {
if case let .image(reference, _, _, _) = item {
if let reference = reference {
let _ = updatePeerPhotoExisting(network: self.context.account.network, reference: reference).start()
self.headerNode.avatarListNode.listContainerNode.setMainItem(item)
}
}
}
}
private func deleteAvatar(_ item: PeerInfoAvatarListItem) {
if self.data?.peer?.id == self.context.account.peerId {
if case let .image(reference, _, _, _) = item {
if let reference = reference {
let _ = removeAccountPhoto(network: self.context.account.network, reference: reference).start()
let dismiss = self.headerNode.avatarListNode.listContainerNode.deleteItem(item)
if dismiss {
if self.headerNode.isAvatarExpanded {
self.headerNode.updateIsAvatarExpanded(false, transition: .immediate)
self.updateNavigationExpansionPresentation(isExpanded: false, animated: true)
}
if let (layout, navigationHeight) = self.validLayout {
self.scrollNode.view.setContentOffset(CGPoint(), animated: false)
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
}
}
}
// if entry == self.entries.first {
// 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, synchronous: false))
// }
// }
} 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, synchronous: false))
// }
// }
}
}
private func openAvatarOptions() { private func openAvatarOptions() {
let item = self.headerNode.avatarListNode.listContainerNode.currentItemNode?.item let item = self.headerNode.avatarListNode.listContainerNode.currentItemNode?.item
let index = self.headerNode.avatarListNode.listContainerNode.currentIndex let index = self.headerNode.avatarListNode.listContainerNode.currentIndex
@ -3663,32 +3821,33 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
var items: [ActionSheetItem] = [] var items: [ActionSheetItem] = []
items.append( ActionSheetButtonItem(title: self.presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: { [weak self] in items.append( ActionSheetButtonItem(title: self.presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: { [weak self] in
dismissAction() dismissAction()
self?.openAvatarForEditing() self?.openAvatarForEditing(hasRemove: false)
})) }))
if index > 0, let item = item, case let .image(image) = item { if let item = item, case let .image(image) = item {
let title: String if index > 0 {
if image.2.isEmpty { let title: String
title = self.presentationData.strings.ProfilePhoto_SetMainPhoto if image.2.isEmpty {
} else { title = self.presentationData.strings.ProfilePhoto_SetMainPhoto
title = self.presentationData.strings.ProfilePhoto_SetMainVideo } else {
title = self.presentationData.strings.ProfilePhoto_SetMainVideo
}
items.append(ActionSheetButtonItem(title: title, color: .accent, action: { [weak self] in
dismissAction()
self?.setMainAvatar(item)
}))
} }
items.append( ActionSheetButtonItem(title: title, color: .accent, action: { [weak self] in items.append(ActionSheetButtonItem(title: self.presentationData.strings.ProfilePhoto_OpenInEditor, color: .accent, action: { [weak self] in
dismissAction() dismissAction()
self?.openAvatarForEditing() self?.editAvatarItem(item)
}))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.GroupInfo_SetGroupPhotoDelete, color: .destructive, action: { [weak self] in
dismissAction()
self?.deleteAvatar(item)
})) }))
} }
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ProfilePhoto_OpenInEditor, color: .accent, action: { [weak self] in
dismissAction()
self?.openAvatarForEditing()
}))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.GroupInfo_SetGroupPhotoDelete, color: .destructive, action: { [weak self] in
dismissAction()
self?.openAvatarForEditing()
}))
actionSheet.setItemGroups([ actionSheet.setItemGroups([
ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
@ -3702,6 +3861,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
return return
} }
if self.headerNode.isAvatarExpanded {
self.headerNode.updateIsAvatarExpanded(false, transition: .immediate)
self.updateNavigationExpansionPresentation(isExpanded: false, animated: true)
}
if let (layout, navigationHeight) = self.validLayout {
self.scrollNode.view.setContentOffset(CGPoint(), animated: false)
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
let resource = LocalFileMediaResource(fileId: arc4random64()) let resource = LocalFileMediaResource(fileId: arc4random64())
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource)
@ -3724,12 +3892,12 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
switch result { switch result {
case .complete: case .complete:
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil) strongSelf.state = strongSelf.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil)
if let (layout, navigationHeight) = strongSelf.validLayout { case let .progress(value):
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) strongSelf.state = strongSelf.state.withAvatarUploadProgress(CGFloat(value))
} }
case .progress: if let (layout, navigationHeight) = strongSelf.validLayout {
break strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
} }
})) }))
} }
@ -3739,6 +3907,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
return return
} }
if self.headerNode.isAvatarExpanded {
self.headerNode.updateIsAvatarExpanded(false, transition: .immediate)
self.updateNavigationExpansionPresentation(isExpanded: false, animated: true)
}
if let (layout, navigationHeight) = self.validLayout {
self.scrollNode.view.setContentOffset(CGPoint(), animated: false)
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
let photoResource = LocalFileMediaResource(fileId: arc4random64()) let photoResource = LocalFileMediaResource(fileId: arc4random64())
self.context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) self.context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource)
@ -3754,7 +3931,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
let account = self.context.account let account = self.context.account
let signal = Signal<TelegramMediaResource, UploadPeerPhotoError> { subscriber in let signal = Signal<TelegramMediaResource, UploadPeerPhotoError> { [weak self] subscriber in
var filteredPath = url.path var filteredPath = url.path
if filteredPath.hasPrefix("file://") { if filteredPath.hasPrefix("file://") {
filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)]) filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)])
@ -3791,6 +3968,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
} }
subscriber.putCompletion() subscriber.putCompletion()
} else if let strongSelf = self, let progress = next as? NSNumber {
Queue.mainQueue().async {
strongSelf.state = strongSelf.state.withAvatarUploadProgress(CGFloat(progress.floatValue * 0.25))
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
}
} }
}, error: { _ in }, error: { _ in
}, completed: nil) }, completed: nil)
@ -3824,17 +4008,17 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
switch result { switch result {
case .complete: case .complete:
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil) strongSelf.state = strongSelf.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil)
if let (layout, navigationHeight) = strongSelf.validLayout { case let .progress(value):
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) strongSelf.state = strongSelf.state.withAvatarUploadProgress(CGFloat(0.25 + value * 0.75))
} }
case .progress: if let (layout, navigationHeight) = strongSelf.validLayout {
break strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
} }
})) }))
} }
private func openAvatarForEditing() { private func openAvatarForEditing(hasRemove: Bool = true, completion: @escaping () -> Void = {}) {
guard let peer = self.data?.peer, canEditPeerInfo(context: self.context, peer: peer) else { guard let peer = self.data?.peer, canEditPeerInfo(context: self.context, peer: peer) else {
return return
} }
@ -3868,13 +4052,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
hasPhotos = true hasPhotos = true
} }
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos, hasViewButton: false, personalPhoto: false, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)! let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos && hasRemove, hasViewButton: false, personalPhoto: strongSelf.isSettings, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
let _ = strongSelf.currentAvatarMixin.swap(mixin) let _ = strongSelf.currentAvatarMixin.swap(mixin)
mixin.requestSearchController = { [weak self] assetsController in mixin.requestSearchController = { [weak self] assetsController in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
let controller = WebSearchController(context: strongSelf.context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), completion: { [weak self] result in let controller = WebSearchController(context: strongSelf.context, peer: peer, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: strongSelf.isSettings ? nil : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), completion: { [weak self] result in
assetsController?.dismiss() assetsController?.dismiss()
self?.updateProfilePhoto(result) self?.updateProfilePhoto(result)
})) }))
@ -3882,11 +4066,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} }
mixin.didFinishWithImage = { [weak self] image in mixin.didFinishWithImage = { [weak self] image in
if let image = image { if let image = image {
completion()
self?.updateProfilePhoto(image) self?.updateProfilePhoto(image)
} }
} }
mixin.didFinishWithVideo = { [weak self] image, url, adjustments in mixin.didFinishWithVideo = { [weak self] image, url, adjustments in
if let image = image, let url = url { if let image = image, let url = url {
completion()
self?.updateProfileVideo(image, url: url, adjustments: adjustments) self?.updateProfileVideo(image, url: url, adjustments: adjustments)
} }
} }
@ -4556,7 +4742,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if self.isSettings { if self.isSettings {
if let settings = self.data?.globalSettings { if let settings = self.data?.globalSettings {
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Settings_Search, contentNode: SettingsSearchContainerNode(context: self.context, openResult: { [weak self] result in self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, mode: .list, placeholder: self.presentationData.strings.Settings_Search, hasSeparator: true, contentNode: SettingsSearchContainerNode(context: self.context, openResult: { [weak self] result in
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController { if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
result.present(strongSelf.context, navigationController, { [weak self] mode, controller in result.present(strongSelf.context, navigationController, { [weak self] mode, controller in
if let strongSelf = self { if let strongSelf = self {
@ -4657,6 +4843,11 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition, additive: Bool = false) { func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition, additive: Bool = false) {
self.validLayout = (layout, navigationHeight) self.validLayout = (layout, navigationHeight)
if self.headerNode.isAvatarExpanded && layout.size.width > layout.size.height {
self.headerNode.updateIsAvatarExpanded(false, transition: transition)
self.updateNavigationExpansionPresentation(isExpanded: false, animated: true)
}
if let searchDisplayController = self.searchDisplayController { if let searchDisplayController = self.searchDisplayController {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition) searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition)
if !searchDisplayController.isDeactivating { if !searchDisplayController.isDeactivating {
@ -4932,7 +5123,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
effectiveAreaExpansionFraction = paneAreaExpansionFraction effectiveAreaExpansionFraction = paneAreaExpansionFraction
} }
transition.updateAlpha(node: self.headerNode.separatorNode, alpha: 1.0 - effectiveAreaExpansionFraction) if !self.isSettings {
transition.updateAlpha(node: self.headerNode.separatorNode, alpha: 1.0 - effectiveAreaExpansionFraction)
}
let visibleHeight = self.scrollNode.view.contentOffset.y + self.scrollNode.view.bounds.height - self.paneContainerNode.frame.minY let visibleHeight = self.scrollNode.view.contentOffset.y + self.scrollNode.view.bounds.height - self.paneContainerNode.frame.minY
@ -5009,8 +5202,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
let offsetY = self.scrollNode.view.contentOffset.y let offsetY = self.scrollNode.view.contentOffset.y
var shouldBeExpanded: Bool? var shouldBeExpanded: Bool?
var isLandscape = false
if let (layout, _) = self.validLayout, layout.size.width > layout.size.height {
isLandscape = true
}
if offsetY <= -32.0 && scrollView.isDragging && scrollView.isTracking { if offsetY <= -32.0 && scrollView.isDragging && scrollView.isTracking {
if let peer = self.data?.peer, peer.smallProfileImage != nil { if let peer = self.data?.peer, peer.smallProfileImage != nil && self.state.updatingAvatar == nil && !isLandscape {
shouldBeExpanded = true shouldBeExpanded = true
if self.canOpenAvatarByDragging && self.headerNode.isAvatarExpanded && offsetY <= -32.0 { if self.canOpenAvatarByDragging && self.headerNode.isAvatarExpanded && offsetY <= -32.0 {

View File

@ -40,7 +40,7 @@ public func webEmbedType(content: TelegramMediaWebpageLoadedContent, forcedTimes
return .youtube(videoId: videoId, timestamp: forcedTimestamp ?? timestamp) return .youtube(videoId: videoId, timestamp: forcedTimestamp ?? timestamp)
} else if let (videoId, timestamp) = extractVimeoVideoIdAndTimestamp(url: content.url) { } else if let (videoId, timestamp) = extractVimeoVideoIdAndTimestamp(url: content.url) {
return .vimeo(videoId: videoId, timestamp: forcedTimestamp ?? timestamp) return .vimeo(videoId: videoId, timestamp: forcedTimestamp ?? timestamp)
} else if let embedUrl = content.embedUrl, isTwitchVideoUrl(embedUrl) { } else if let embedUrl = content.embedUrl, isTwitchVideoUrl(embedUrl) && false {
return .twitch(url: embedUrl) return .twitch(url: embedUrl)
} else { } else {
return .iframe(url: content.embedUrl ?? content.url) return .iframe(url: content.embedUrl ?? content.url)

View File

@ -498,7 +498,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let rightInset: CGFloat = 16.0 let rightInset: CGFloat = 16.0
var contentHeight: CGFloat = 20.0 var contentHeight: CGFloat = 20.0
let margin: CGFloat = 12.0 let margin: CGFloat = 11.0
let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude)) let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
let buttonMinX: CGFloat let buttonMinX: CGFloat
@ -508,8 +508,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset buttonMinX = layout.size.width - layout.safeInsets.left - rightInset
} }
let titleSize = self.titleNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - margin, height: .greatestFiniteMagnitude)) let titleSize = self.titleNode.updateLayout(CGSize(width: buttonMinX - 6.0 - leftInset - layout.safeInsets.left - margin, height: .greatestFiniteMagnitude))
let textSize = self.textNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - margin, height: .greatestFiniteMagnitude)) let textSize = self.textNode.updateLayout(CGSize(width: buttonMinX - 6.0 - leftInset - layout.safeInsets.left - margin, height: .greatestFiniteMagnitude))
if !titleSize.width.isZero { if !titleSize.width.isZero {
contentHeight += titleSize.height + 1.0 contentHeight += titleSize.height + 1.0