Settings screen fixes

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

Binary file not shown.

View File

@ -5638,8 +5638,6 @@ Any member of this group will be able to see messages in the channel.";
"Settings.ViewVideo" = "View Video";
"Settings.RemoveVideo" = "Remove Video";
"Settings.EditProfileMedia" = "Edit";
"Conversation.Unarchive" = "Unarchive";
"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.FrequentlyAskedQuestions" = "Frequently Asked Questions";
"Notification.ChangedGroupVideo" = "%@ changed group video";
"Group.MessageVideoUpdated" = "Group video updated";
"Channel.MessageVideoUpdated" = "Channel video updated";
"Conversation.Dice.u1F3C0" = "Send a basketball emoji to shoot a free throw.";
"Conversation.Dice.u26BD" = "Send a football emoji to do a free kick.";

View File

@ -74,6 +74,10 @@ final class TabBarControllerNode: ASDisplayNode {
transition.updateAlpha(node: self.disabledOverlayNode, alpha: value ? 0.0 : 1.0)
}
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) {
var tabBarHeight: CGFloat
var options: ContainerViewLayoutInsetOptions = []

View File

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

View File

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

View File

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

View File

@ -1017,13 +1017,13 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
strongSelf.checkNode = nil
}
var rightLabelInset: CGFloat = 15.0
var rightLabelInset: CGFloat = 15.0 + params.rightInset
if let updatedLabelArrowNode = updatedLabelArrowNode {
strongSelf.labelArrowNode = updatedLabelArrowNode
strongSelf.containerNode.addSubnode(updatedLabelArrowNode)
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)
rightLabelInset += 19.0
}

View File

@ -13,6 +13,8 @@ import GalleryUI
import LegacyComponents
import LegacyMediaPickerUI
import SaveToCameraRoll
import OverlayStatusController
import PresentationDataUtils
public enum AvatarGalleryEntryId: Hashable {
case topImage
@ -188,6 +190,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
private let centralItemFooterContentNode = Promise<(GalleryFooterContentNode?, GalleryOverlayContentNode?)>()
private let centralItemAttributesDisposable = DisposableSet();
public var openAvatarSetup: ((@escaping () -> Void) -> Void)?
public var avatarPhotoEditCompletion: ((UIImage) -> Void)?
public var avatarVideoEditCompletion: ((UIImage, URL, TGVideoEditAdjustments?) -> Void)?
@ -349,6 +352,11 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
self.dismiss(forceAway: false)
}
private func dismissImmediately() {
self._hiddenMedia.set(.single(nil))
self.presentingViewController?.dismiss(animated: false, completion: nil)
}
private func dismiss(forceAway: Bool) {
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
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: [])]))
@ -630,32 +638,27 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
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)
|> deliverOnMainQueue).start(next: { [weak self] state, isImage in
guard let strongSelf = self else {
return
}
switch state {
case let .progress(value):
case .progress:
break
case let .data(data):
dismissStatus?()
let image: UIImage?
let video: URL?
if isImage {
@ -677,67 +680,131 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
if let strongSelf = self {
strongSelf.present(c, in: .window(.root), with: a, blockInteraction: true)
}
}, imageCompletion: { image in
avatarPhotoEditCompletion?(image)
}, imageCompletion: { image in
avatarPhotoEditCompletion?(image)
}, videoCompletion: { image, url, adjustments in
avatarVideoEditCompletion?(image, url, adjustments)
})
Queue.mainQueue().after(0.4) {
strongSelf.dismiss(forceAway: true)
}
}
}
}))
}
private func deleteEntry(_ rawEntry: AvatarGalleryEntry) {
var entry = rawEntry
if case .topImage = entry, !self.entries.isEmpty {
entry = self.entries[0]
private func editEntry(_ rawEntry: AvatarGalleryEntry) {
let actionSheet = ActionSheetController(presentationData: self.presentationData)
let dismissAction: () -> Void = { [weak actionSheet] in
actionSheet?.dismissAnimated()
}
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))
}
}
}
var items: [ActionSheetItem] = []
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: { [weak self] in
dismissAction()
self?.openAvatarSetup?({ [weak self] in
self?.dismissImmediately()
})
}))
if let position = rawEntry.indexData?.position, position > 0 {
let title: String
if let _ = rawEntry.videoRepresentations.last {
title = self.presentationData.strings.ProfilePhoto_SetMainVideo
} else {
title = self.presentationData.strings.ProfilePhoto_SetMainPhoto
}
items.append(ActionSheetButtonItem(title: title, color: .accent, action: { [weak self] in
dismissAction()
self?.setMainEntry(rawEntry)
}))
}
items.append(ActionSheetButtonItem(title: self.presentationData.strings.ProfilePhoto_OpenInEditor, color: .accent, action: { [weak self] in
dismissAction()
self?.openEntryEdit(rawEntry)
}))
items.append(ActionSheetButtonItem(title: self.presentationData.strings.GroupInfo_SetGroupPhotoDelete, color: .destructive, action: { [weak self] in
dismissAction()
self?.deleteEntry(rawEntry)
}))
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
self.view.endEditing(true)
self.present(actionSheet, in: .window(.root))
}
private func deleteEntry(_ rawEntry: AvatarGalleryEntry) {
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))
}
}

View File

@ -213,23 +213,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
}
@objc private func deleteButtonPressed() {
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 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)
self.delete?()
}
@objc private func actionButtonPressed() {

View File

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

View File

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

View File

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

View File

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

View File

@ -1706,35 +1706,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
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
strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id)
let disposables: DisposableDict<MessageId>
@ -5726,7 +5697,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
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 {
let _ = clearHistoryInteractively(postbox: account.postbox, peerId: peerId, type: type).start(completed: {
self?.chatDisplayNode.historyNode.historyAppearsCleared = false
@ -7030,13 +7001,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
let value: String?
switch dice.emoji {
let emoji = dice.emoji.strippedEmoji
switch emoji {
case "🎲":
value = self.presentationData.strings.Conversation_Dice_u1F3B2
case "🎯":
value = self.presentationData.strings.Conversation_Dice_u1F3AF
case "🏀":
value = self.presentationData.strings.Conversation_Dice_u1F3C0
case "⚽️":
value = self.presentationData.strings.Conversation_Dice_u26BD
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)"
if let string = self.presentationData.strings.primaryComponent.dict[key] {
value = string

View File

@ -6,6 +6,7 @@ import SwiftSignalKit
import Postbox
import TelegramCore
import SyncCore
import AccountContext
import TelegramPresentationData
import TelegramUIPreferences
import TextFormat
@ -13,6 +14,9 @@ import LocalizedPeerData
import UrlEscaping
import PhotoResources
import TelegramStringFormatting
import UniversalMediaPlayer
import TelegramUniversalVideoContent
import GalleryUI
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)
@ -23,6 +27,9 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
let filledBackgroundNode: LinkHighlightingNode
var linkHighlightingNode: LinkHighlightingNode?
fileprivate var imageNode: TransformImageNode?
fileprivate var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
private let fetchDisposable = MetaDisposable()
required init() {
@ -112,9 +119,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
}
}
let imageSize = CGSize(width: 70.0, height: 70.0)
let imageSize = layoutConstants.instantVideo.dimensions
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 {
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 {
let imageNode: TransformImageNode
if let current = strongSelf.imageNode {
@ -173,12 +179,53 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
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 {
imageNode.removeFromSupernode()
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 _ = backgroundApply()

View File

@ -70,6 +70,10 @@ private func rollingAnimationItem(account: Account, emojis: Signal<[TelegramMedi
return .single(ManagedAnimationItem(source: .local("Dice_Rolling"), loop: true))
case "🎯":
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:
return animationItem(account: account, emojis: emojis, emoji: emoji, value: nil, loop: true)
}

View File

@ -153,7 +153,7 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
if let arrowImage = PresentationResourcesItemList.disclosureArrowImage(presentationData.theme) {
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)
}

View File

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

View File

@ -17,6 +17,7 @@ import ActivityIndicator
import TelegramUniversalVideoContent
import GalleryUI
import UniversalMediaPlayer
import RadialStatusNode
enum PeerInfoHeaderButtonKey: Hashable {
case message
@ -164,7 +165,7 @@ enum PeerInfoAvatarListItem: Equatable {
case let .topImage(representations, _):
let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation
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
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
init(context: AccountContext) {
@ -293,13 +301,18 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
self.videoContent = videoContent
self.videoNode = videoNode
self.statusPromise.set(videoNode.status |> map { ($0, video.startTimestamp) })
self.addSubnode(videoNode)
} else if let videoNode = self.videoNode {
self.videoContent = nil
self.videoNode = nil
} else {
if let videoNode = self.videoNode {
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 itemNodes: [WrappedMediaResourceId: PeerInfoAvatarListItemNode] = [:]
private var stripNodes: [ASImageNode] = []
private var activeStripNode: ASImageNode
private let activeStripImage: UIImage
private var appliedStripNodeCurrentIndex: Int?
var currentIndex: Int = 0
@ -351,7 +365,9 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
private var isExpanded = false
private let disposable = MetaDisposable()
private let positionDisposable = MetaDisposable()
private var initializedList = false
private var ignoreNextProfilePhotoUpdate = false
var itemsUpdated: (([PeerInfoAvatarListItem]) -> Void)?
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) {
self.context = context
@ -422,6 +500,9 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.contentNode.addSubnode(self.stripContainerNode)
self.activeStripImage = generateSmallHorizontalStretchableFilledCircleImage(diameter: 2.0, color: .white)!
self.activeStripNode = ASImageNode()
self.activeStripNode.image = self.activeStripImage
self.highlightContainerNode = ASDisplayNode()
self.highlightContainerNode.addSubnode(self.leftHighlightNode)
self.highlightContainerNode.addSubnode(self.rightHighlightNode)
@ -545,6 +626,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
deinit {
self.disposable.dispose()
self.positionDisposable.dispose()
}
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) {
self.validLayout = size
self.isExpanded = isExpanded
@ -659,6 +803,15 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
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] = []
for entry in entries {
switch entry {
@ -746,9 +899,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
stripNode.displaysAsynchronously = false
stripNode.displayWithoutProcessing = true
stripNode.image = self.activeStripImage
if stripNodes.count != self.currentIndex {
stripNode.alpha = 0.2
}
stripNode.alpha = 0.2
self.stripNodes.append(stripNode)
self.stripContainerNode.addSubnode(stripNode)
}
@ -758,20 +909,22 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.stripNodes.remove(at: i)
}
}
self.stripContainerNode.addSubnode(self.activeStripNode)
}
if self.appliedStripNodeCurrentIndex != self.currentIndex {
if let appliedStripNodeCurrentIndex = self.appliedStripNodeCurrentIndex {
if appliedStripNodeCurrentIndex >= 0 && appliedStripNodeCurrentIndex < self.stripNodes.count {
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)
}
}
if !self.itemNodes.isEmpty {
self.appliedStripNodeCurrentIndex = self.currentIndex
}
self.appliedStripNodeCurrentIndex = self.currentIndex
if self.currentIndex >= 0 && self.currentIndex < self.stripNodes.count {
self.stripNodes[self.currentIndex].alpha = 1.0
if let currentItemNode = self.currentItemNode {
self.positionDisposable.set((currentItemNode.mediaStatus
|> deliverOnMainQueue).start(next: { [weak self] statusAndVideoStartTimestamp in
if let strongSelf = self {
strongSelf.playerStatus = statusAndVideoStartTimestamp
}
}))
} else {
self.positionDisposable.set(nil)
}
}
if hadOneStripNode && self.stripNodes.count > 1 {
@ -795,6 +948,15 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
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 !self.didSetReady {
self.didSetReady = true
@ -933,11 +1095,15 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
final class PeerInfoEditingAvatarNode: ASDisplayNode {
let context: AccountContext
let avatarNode: AvatarNode
fileprivate var videoNode: UniversalVideoNode?
private var videoContent: NativeVideoContent?
private var videoStartTimestamp: Double?
private let updatingAvatarOverlay: ASImageNode
private let activityIndicator: ActivityIndicator
private var statusNode: RadialStatusNode
var tapped: (() -> Void)?
var cancel: (() -> Void)?
init(context: AccountContext) {
self.context = context
@ -949,19 +1115,21 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
self.updatingAvatarOverlay.displaysAsynchronously = false
self.updatingAvatarOverlay.isHidden = true
self.activityIndicator = ActivityIndicator(type: .custom(.white, 22.0, 1.0, false))
self.activityIndicator.isHidden = true
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.6))
self.statusNode.isUserInteractionEnabled = false
super.init()
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.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.activityIndicator)
self.addSubnode(self.statusNode)
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 {
let overrideImage: AvatarNodeImageOverride?
var hideVideo = false
if canEditPeerInfo(context: self.context, peer: peer) {
if let updatingAvatar = updatingAvatar {
switch updatingAvatar {
case let .image(representation):
overrideImage = .image(representation)
hideVideo = true
case .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
if self.updatingAvatarOverlay.image == nil {
self.updatingAvatarOverlay.image = generateFilledCircleImage(diameter: avatarSize, color: UIColor(white: 0.0, alpha: 0.4), backgroundColor: nil)
}
} else {
overrideImage = .editAvatarIcon
self.activityIndicator.isHidden = true
self.statusNode.transitionToState(.none)
self.updatingAvatarOverlay.isHidden = true
}
} else {
overrideImage = nil
self.activityIndicator.isHidden = true
self.statusNode.transitionToState(.none)
self.updatingAvatarOverlay.isHidden = true
}
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.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) {
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 {
let avatarCopyView = UIImageView()
@ -1673,7 +1883,7 @@ final class PeerInfoHeaderEditingContentNode: ASDisplayNode {
var contentHeight: CGFloat = statusBarHeight + 10.0 + avatarSize + 20.0
if isSettings {
if canEditPeerInfo(context: self.context, peer: peer) {
if self.avatarTextNode.supernode == nil {
self.addSubnode(self.avatarTextNode)
}
@ -1880,7 +2090,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.addSubnode(self.backgroundNode)
self.addSubnode(self.expandedBackgroundNode)
self.addSubnode(self.separatorNode)
self.titleNodeContainer.addSubnode(self.titleNode)
self.regularContentNode.addSubnode(self.titleNodeContainer)
self.subtitleNodeContainer.addSubnode(self.subtitleNode)
@ -1892,6 +2101,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.addSubnode(self.regularContentNode)
self.addSubnode(self.editingContentNode)
self.addSubnode(self.navigationButtonContainer)
self.addSubnode(self.separatorNode)
self.avatarListNode.avatarContainerNode.tapped = { [weak self] in
self?.initiateAvatarExpansion()
@ -2080,7 +2290,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let usernameNodeLayout = self.usernameNode.updateLayout(states: [
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)
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.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 {
transition.updateSublayerTransformScaleAdditive(node: self.avatarListNode.avatarContainerNode, scale: avatarScale)
} else {
@ -2535,8 +2745,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let resolvedHeight: CGFloat
if state.isEditing {
resolvedHeight = editingContentHeight
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + 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))
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: max(navigationHeight, resolvedHeight - contentOffset)), size: CGSize(width: width, height: UIScreenPixel))
} else {
resolvedHeight = resolvedRegularHeight
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -2000.0 + apparentHeight), size: CGSize(width: width, height: 2000.0))

View File

@ -47,6 +47,7 @@ import AuthTransferUI
import DeviceAccess
import LegacyMediaPickerUI
import TelegramNotices
import SaveToCameraRoll
protocol PeerInfoScreenItem: class {
var id: AnyHashable { get }
@ -648,17 +649,24 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
if let settings = data.globalSettings {
if settings.suggestPhoneNumberConfirmation {
// let phoneNumber = formatPhoneNumber(peer.phone ?? "")
if settings.suggestPhoneNumberConfirmation, let peer = data.peer as? TelegramUser {
//
// 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(.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 {
for (peerAccount, peer, badgeCount) in settings.accountsAndPeers {
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 {
case .open:
interaction.switchToAccount(peerAccount.id)
@ -1359,7 +1367,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
isEditing: false,
selectedMessageIds: nil,
updatingAvatar: nil,
updatingBio: nil
updatingBio: nil,
avatarUploadProgress: nil
)
private let nearbyPeerDistance: Int32?
private var dataDisposable: Disposable?
@ -1371,6 +1380,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
private let addMemberDisposable = MetaDisposable()
private let preloadHistoryDisposable = MetaDisposable()
private let editAvatarDisposable = MetaDisposable()
private let updateAvatarDisposable = MetaDisposable()
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 {
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 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
self?.updateProfilePhoto(image)
}
@ -2084,7 +2102,18 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
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
@ -2102,6 +2131,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
switch key {
case .edit:
(strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.2, curve: .easeInOut))
strongSelf.state = strongSelf.state.withIsEditing(true)
if strongSelf.headerNode.isAvatarExpanded {
strongSelf.headerNode.updateIsAvatarExpanded(false, transition: .immediate)
@ -2115,6 +2145,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, completion: nil)
strongSelf.controller?.navigationItem.setLeftBarButton(UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, style: .plain, target: strongSelf, action: #selector(strongSelf.editingCancelPressed)), animated: true)
case .done, .cancel:
(strongSelf.controller?.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.2, curve: .easeInOut))
strongSelf.view.endEditing(true)
if case .done = key {
guard let data = strongSelf.data else {
@ -2128,10 +2159,11 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
let bio = strongSelf.state.updatingBio
if peer.firstName != firstName || peer.lastName != lastName || (bio != nil && bio != cachedData.about) {
var updateNameSignal: Signal<Void, NoError> = .complete()
var hasProgress = false
if peer.firstName != firstName || peer.lastName != lastName {
updateNameSignal = updateAccountPeerName(account: context.account, firstName: firstName, lastName: lastName)
hasProgress = true
}
var updateBioSignal: Signal<Void, NoError> = .complete()
if let bio = bio, bio != cachedData.about {
@ -2139,6 +2171,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|> `catch` { _ -> Signal<Void, NoError> in
return .complete()
}
hasProgress = true
}
var dismissStatus: (() -> Void)?
@ -2149,8 +2182,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.activeActionDisposable.set(nil)
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
|> deliverOnMainQueue).start(error: { _ in
dismissStatus?()
@ -2251,12 +2285,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
} else {
var updateDataSignals: [Signal<Never, Void>] = []
var hasProgress = false
if title != group.title {
updateDataSignals.append(
updatePeerTitle(account: strongSelf.context.account, peerId: group.id, title: title)
|> ignoreValues
|> mapError { _ in return Void() }
)
hasProgress = true
}
if description != (data.cachedData as? CachedGroupData)?.about {
updateDataSignals.append(
@ -2264,6 +2300,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|> ignoreValues
|> mapError { _ in return Void() }
)
hasProgress = true
}
var dismissStatus: (() -> Void)?
let statusController = OverlayStatusController(theme: strongSelf.presentationData.theme, type: .loading(cancelled: {
@ -2273,8 +2310,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.activeActionDisposable.set(nil)
statusController?.dismiss()
}
strongSelf.controller?.present(statusController, in: .window(.root))
if hasProgress {
strongSelf.controller?.present(statusController, in: .window(.root))
}
strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals)
|> deliverOnMainQueue).start(error: { _ in
dismissStatus?()
@ -2305,13 +2343,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
strongSelf.headerNode.editingContentNode.shakeTextForKey(.title)
} else {
var updateDataSignals: [Signal<Never, Void>] = []
var hasProgress = false
if title != channel.title {
updateDataSignals.append(
updatePeerTitle(account: strongSelf.context.account, peerId: channel.id, title: title)
|> ignoreValues
|> mapError { _ in return Void() }
)
hasProgress = true
}
if description != (data.cachedData as? CachedChannelData)?.about {
updateDataSignals.append(
@ -2319,6 +2358,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|> ignoreValues
|> mapError { _ in return Void() }
)
hasProgress = true
}
var dismissStatus: (() -> Void)?
@ -2329,8 +2369,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self?.activeActionDisposable.set(nil)
statusController?.dismiss()
}
strongSelf.controller?.present(statusController, in: .window(.root))
if hasProgress {
strongSelf.controller?.present(statusController, in: .window(.root))
}
strongSelf.activeActionDisposable.set((combineLatest(updateDataSignals)
|> deliverOnMainQueue).start(error: { _ in
dismissStatus?()
@ -2461,6 +2502,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
self.resolveUrlDisposable.dispose()
self.hiddenAvatarRepresentationDisposable.dispose()
self.toggleShouldChannelMessagesSignaturesDisposable.dispose()
self.editAvatarDisposable.dispose()
self.updateAvatarDisposable.dispose()
self.selectAddMemberDisposable.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() {
let item = self.headerNode.avatarListNode.listContainerNode.currentItemNode?.item
let index = self.headerNode.avatarListNode.listContainerNode.currentIndex
@ -3663,32 +3821,33 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
var items: [ActionSheetItem] = []
items.append( ActionSheetButtonItem(title: self.presentationData.strings.Settings_SetNewProfilePhotoOrVideo, color: .accent, action: { [weak self] in
dismissAction()
self?.openAvatarForEditing()
self?.openAvatarForEditing(hasRemove: false)
}))
if index > 0, let item = item, case let .image(image) = item {
let title: String
if image.2.isEmpty {
title = self.presentationData.strings.ProfilePhoto_SetMainPhoto
} else {
title = self.presentationData.strings.ProfilePhoto_SetMainVideo
if let item = item, case let .image(image) = item {
if index > 0 {
let title: String
if image.2.isEmpty {
title = self.presentationData.strings.ProfilePhoto_SetMainPhoto
} 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()
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([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
@ -3702,6 +3861,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
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())
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource)
@ -3724,12 +3892,12 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
switch result {
case .complete:
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
case .progress:
break
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil)
case let .progress(value):
strongSelf.state = strongSelf.state.withAvatarUploadProgress(CGFloat(value))
}
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
}))
}
@ -3739,6 +3907,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
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())
self.context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
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 signal = Signal<TelegramMediaResource, UploadPeerPhotoError> { subscriber in
let signal = Signal<TelegramMediaResource, UploadPeerPhotoError> { [weak self] subscriber in
var filteredPath = url.path
if filteredPath.hasPrefix("file://") {
filteredPath = String(filteredPath[filteredPath.index(filteredPath.startIndex, offsetBy: "file://".count)])
@ -3791,6 +3968,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
}
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
}, completed: nil)
@ -3824,17 +4008,17 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
switch result {
case .complete:
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
case .progress:
break
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil).withAvatarUploadProgress(nil)
case let .progress(value):
strongSelf.state = strongSelf.state.withAvatarUploadProgress(CGFloat(0.25 + value * 0.75))
}
if let (layout, navigationHeight) = strongSelf.validLayout {
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 {
return
}
@ -3868,13 +4052,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
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)
mixin.requestSearchController = { [weak self] assetsController in
guard let strongSelf = self else {
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()
self?.updateProfilePhoto(result)
}))
@ -3882,11 +4066,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
mixin.didFinishWithImage = { [weak self] image in
if let image = image {
completion()
self?.updateProfilePhoto(image)
}
}
mixin.didFinishWithVideo = { [weak self] image, url, adjustments in
if let image = image, let url = url {
completion()
self?.updateProfileVideo(image, url: url, adjustments: adjustments)
}
}
@ -4556,7 +4742,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
if self.isSettings {
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 {
result.present(strongSelf.context, navigationController, { [weak self] mode, controller in
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) {
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 {
searchDisplayController.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: transition)
if !searchDisplayController.isDeactivating {
@ -4932,7 +5123,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
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
@ -5009,8 +5202,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
let offsetY = self.scrollNode.view.contentOffset.y
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 let peer = self.data?.peer, peer.smallProfileImage != nil {
if let peer = self.data?.peer, peer.smallProfileImage != nil && self.state.updatingAvatar == nil && !isLandscape {
shouldBeExpanded = true
if self.canOpenAvatarByDragging && self.headerNode.isAvatarExpanded && offsetY <= -32.0 {

View File

@ -40,7 +40,7 @@ public func webEmbedType(content: TelegramMediaWebpageLoadedContent, forcedTimes
return .youtube(videoId: videoId, timestamp: forcedTimestamp ?? timestamp)
} else if let (videoId, timestamp) = extractVimeoVideoIdAndTimestamp(url: content.url) {
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)
} else {
return .iframe(url: content.embedUrl ?? content.url)

View File

@ -498,7 +498,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
let rightInset: CGFloat = 16.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 buttonMinX: CGFloat
@ -508,8 +508,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
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 textSize = self.textNode.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 - 6.0 - leftInset - layout.safeInsets.left - margin, height: .greatestFiniteMagnitude))
if !titleSize.width.isZero {
contentHeight += titleSize.height + 1.0