mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Settings screen fixes
This commit is contained in:
parent
3dcc32c3c3
commit
054756fa9d
BIN
Telegram/Telegram-iOS/Resources/Basketball_Bouncing.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Basketball_Bouncing.tgs
Normal file
Binary file not shown.
BIN
Telegram/Telegram-iOS/Resources/Football_Bouncing.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/Football_Bouncing.tgs
Normal file
Binary file not shown.
@ -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.";
|
||||||
|
@ -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 = []
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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: {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,32 +638,27 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
|
|||||||
mediaReference = .standalone(media: media)
|
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?.editDisposable.set(nil)
|
||||||
|
statusController?.dismiss()
|
||||||
|
}
|
||||||
|
self.present(statusController, in: .window(.root))
|
||||||
|
|
||||||
// var cancelImpl: (() -> Void)?
|
|
||||||
// let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
// let progressSignal = Signal<Never, NoError> { subscriber in
|
|
||||||
// let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
|
||||||
// cancelImpl?()
|
|
||||||
// }))
|
|
||||||
// strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
|
||||||
// return ActionDisposable { [weak controller] in
|
|
||||||
// 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
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
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 deleteEntry(_ rawEntry: AvatarGalleryEntry) {
|
private func editEntry(_ rawEntry: AvatarGalleryEntry) {
|
||||||
var entry = rawEntry
|
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||||
if case .topImage = entry, !self.entries.isEmpty {
|
let dismissAction: () -> Void = { [weak actionSheet] in
|
||||||
entry = self.entries[0]
|
actionSheet?.dismissAnimated()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch entry {
|
var items: [ActionSheetItem] = []
|
||||||
case .topImage:
|
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: { [weak self] in
|
||||||
if self.peer.id == self.context.account.peerId {
|
dismissAction()
|
||||||
} else {
|
self?.openAvatarSetup?({ [weak self] in
|
||||||
if entry == self.entries.first {
|
self?.dismissImmediately()
|
||||||
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) {
|
if let position = rawEntry.indexData?.position, position > 0 {
|
||||||
self.entries.remove(at: index)
|
let title: String
|
||||||
self.galleryNode.pager.transaction(GalleryPagerTransaction(deleteItems: [index], insertItems: [], updateItems: [], focusOnItem: index - 1, synchronous: false))
|
if let _ = rawEntry.videoRepresentations.last {
|
||||||
}
|
title = self.presentationData.strings.ProfilePhoto_SetMainVideo
|
||||||
}
|
} else {
|
||||||
}
|
title = self.presentationData.strings.ProfilePhoto_SetMainPhoto
|
||||||
case let .image(_, reference, _, _, _, _, _, messageId, _):
|
}
|
||||||
if self.peer.id == self.context.account.peerId {
|
items.append(ActionSheetButtonItem(title: title, color: .accent, action: { [weak self] in
|
||||||
if let reference = reference {
|
dismissAction()
|
||||||
let _ = removeAccountPhoto(network: self.context.account.network, reference: reference).start()
|
self?.setMainEntry(rawEntry)
|
||||||
}
|
}))
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
let proceed = {
|
||||||
|
var entry = rawEntry
|
||||||
|
if case .topImage = entry, !self.entries.isEmpty {
|
||||||
|
entry = self.entries[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch entry {
|
||||||
|
case .topImage:
|
||||||
|
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 {
|
||||||
|
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, _):
|
||||||
|
if self.peer.id == self.context.account.peerId {
|
||||||
|
if let reference = reference {
|
||||||
|
let _ = removeAccountPhoto(network: self.context.account.network, reference: reference).start()
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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)]))
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user