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.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.";
|
||||
|
@ -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 = []
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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)]))
|
||||
}
|
||||
|
Binary file not shown.
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user