mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit 'e2e63a4ddfb52d7d97132cacb9384b7a2c628420'
This commit is contained in:
commit
50fe822e85
@ -6309,3 +6309,11 @@ Sorry for the inconvenience.";
|
||||
"UserInfo.LinkForwardTooltip.TwoChats.One" = "Link forwarded to **%@** and **%@**";
|
||||
"UserInfo.LinkForwardTooltip.ManyChats.One" = "Link forwarded to **%@** and %@ others";
|
||||
"UserInfo.LinkForwardTooltip.SavedMessages.One" = "Link forwarded to **Saved Messages**";
|
||||
|
||||
"VoiceChat.You" = "this is you";
|
||||
"VoiceChat.ChangePhoto" = "Change Photo";
|
||||
"VoiceChat.EditBio" = "Edit Bio";
|
||||
"VoiceChat.EditBioTitle" = "Bio";
|
||||
"VoiceChat.EditBioText" = "Any details such as age, occupation or city.";
|
||||
"VoiceChat.EditBioPlaceholder" = "Bio";
|
||||
"VoiceChat.EditBioSave" = "Save";
|
||||
|
@ -1625,7 +1625,9 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
if case let .extracted(source) = self.source {
|
||||
if !source.ignoreContentTouches {
|
||||
let contentPoint = self.view.convert(point, to: contentParentNode.contentNode.view)
|
||||
if let result = contentParentNode.contentNode.hitTest(contentPoint, with: event) {
|
||||
if let result = contentParentNode.contentNode.customHitTest?(contentPoint) {
|
||||
return result
|
||||
} else if let result = contentParentNode.contentNode.hitTest(contentPoint, with: event) {
|
||||
if result is TextSelectionNodeView {
|
||||
return result
|
||||
} else if contentParentNode.contentRect.contains(contentPoint) {
|
||||
|
@ -26,6 +26,7 @@ public final class ContextExtractedContentContainingNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
public final class ContextExtractedContentNode: ASDisplayNode {
|
||||
public var customHitTest: ((CGPoint) -> UIView?)?
|
||||
}
|
||||
|
||||
public final class ContextControllerContentNode: ASDisplayNode {
|
||||
|
@ -424,6 +424,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
@objc private func seekBackwardLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.interacting?(true)
|
||||
self.backwardButton.isPressing = true
|
||||
self.wasPlaying = !self.currentIsPaused
|
||||
if self.wasPlaying == true {
|
||||
@ -447,6 +448,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.seekTimer = seekTimer
|
||||
seekTimer.start()
|
||||
case .ended, .cancelled:
|
||||
self.interacting?(false)
|
||||
self.backwardButton.isPressing = false
|
||||
self.seekTimer?.invalidate()
|
||||
self.seekTimer = nil
|
||||
@ -462,6 +464,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
@objc private func seekForwardLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) {
|
||||
switch gestureRecognizer.state {
|
||||
case .began:
|
||||
self.interacting?(true)
|
||||
self.forwardButton.isPressing = true
|
||||
self.wasPlaying = !self.currentIsPaused
|
||||
if self.wasPlaying == false {
|
||||
@ -485,6 +488,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.seekTimer = seekTimer
|
||||
seekTimer.start()
|
||||
case .ended, .cancelled:
|
||||
self.interacting?(false)
|
||||
self.forwardButton.isPressing = false
|
||||
self.setPlayRate?(1.0)
|
||||
self.seekTimer?.invalidate()
|
||||
@ -1372,11 +1376,15 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
}
|
||||
|
||||
@objc func backwardButtonPressed() {
|
||||
self.interacting?(true)
|
||||
self.seekBackward?(15.0)
|
||||
self.interacting?(false)
|
||||
}
|
||||
|
||||
@objc func forwardButtonPressed() {
|
||||
self.interacting?(true)
|
||||
self.seekForward?(15.0)
|
||||
self.interacting?(false)
|
||||
}
|
||||
|
||||
@objc private func statusPressed() {
|
||||
@ -1540,7 +1548,7 @@ private final class PlaybackButtonNode: HighlightTrackingButtonNode {
|
||||
strongSelf.textNode.alpha = 1.0
|
||||
strongSelf.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .linear)
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .linear)
|
||||
transition.updateTransformRotation(node: strongSelf.backgroundIconNode, angle: 0.0)
|
||||
}
|
||||
}
|
||||
|
@ -345,20 +345,26 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
self.scrubberView.updateScrubbing = { [weak self] timecode in
|
||||
guard let strongSelf = self, let videoFramePreview = strongSelf.videoFramePreview else {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let timecode = timecode {
|
||||
if !strongSelf.scrubbingFrames {
|
||||
strongSelf.scrubbingFrames = true
|
||||
strongSelf.scrubbingFrame.set(videoFramePreview.generatedFrames
|
||||
|> map(Optional.init))
|
||||
|
||||
strongSelf.isInteractingPromise.set(timecode != nil)
|
||||
|
||||
if let videoFramePreview = strongSelf.videoFramePreview {
|
||||
if let timecode = timecode {
|
||||
if !strongSelf.scrubbingFrames {
|
||||
strongSelf.scrubbingFrames = true
|
||||
strongSelf.scrubbingFrame.set(videoFramePreview.generatedFrames
|
||||
|> map(Optional.init))
|
||||
}
|
||||
videoFramePreview.generateFrame(at: timecode)
|
||||
} else {
|
||||
strongSelf.isInteractingPromise.set(false)
|
||||
strongSelf.scrubbingFrame.set(.single(nil))
|
||||
videoFramePreview.cancelPendingFrames()
|
||||
strongSelf.scrubbingFrames = false
|
||||
}
|
||||
videoFramePreview.generateFrame(at: timecode)
|
||||
} else {
|
||||
strongSelf.scrubbingFrame.set(.single(nil))
|
||||
videoFramePreview.cancelPendingFrames()
|
||||
strongSelf.scrubbingFrames = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -577,7 +577,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
||||
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult)
|
||||
|> mapToSignal { value -> Signal<FileMediaResourceStatus, NoError> in
|
||||
if case .Fetching = value.fetchStatus {
|
||||
return .single(value) |> delay(0.25, queue: Queue.concurrentDefaultQueue())
|
||||
return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue())
|
||||
} else {
|
||||
return .single(value)
|
||||
}
|
||||
|
@ -5,9 +5,8 @@ import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import FFMpegBinding
|
||||
import UniversalMediaPlayer
|
||||
|
||||
func preloadVideoResource(postbox: Postbox, resourceReference: MediaResourceReference, duration: Double) -> Signal<Never, NoError> {
|
||||
public func preloadVideoResource(postbox: Postbox, resourceReference: MediaResourceReference, duration: Double) -> Signal<Never, NoError> {
|
||||
return Signal { subscriber in
|
||||
let queue = Queue()
|
||||
let disposable = MetaDisposable()
|
@ -22,6 +22,40 @@ public enum AvatarGalleryEntryId: Hashable {
|
||||
case resource(String)
|
||||
}
|
||||
|
||||
public func peerInfoProfilePhotos(context: AccountContext, peerId: PeerId) -> Signal<Any, NoError> {
|
||||
return context.account.postbox.combinedView(keys: [.basicPeer(peerId)])
|
||||
|> mapToSignal { view -> Signal<AvatarGalleryEntry?, NoError> in
|
||||
guard let peer = (view.views[.basicPeer(peerId)] as? BasicPeerView)?.peer else {
|
||||
return .single(nil)
|
||||
}
|
||||
return initialAvatarGalleryEntries(account: context.account, peer: peer)
|
||||
|> map { entries in
|
||||
return entries.first
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { firstEntry -> Signal<(Bool, [AvatarGalleryEntry]), NoError> in
|
||||
if let firstEntry = firstEntry {
|
||||
return context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> mapToSignal { peer -> Signal<(Bool, [AvatarGalleryEntry]), NoError>in
|
||||
return fetchedAvatarGalleryEntries(account: context.account, peer: peer, firstEntry: firstEntry)
|
||||
}
|
||||
} else {
|
||||
return .single((true, []))
|
||||
}
|
||||
}
|
||||
|> map { items -> Any in
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
public func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: PeerId) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> {
|
||||
return context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId))
|
||||
|> map { items -> (Bool, [AvatarGalleryEntry]) in
|
||||
return items as? (Bool, [AvatarGalleryEntry]) ?? (true, [])
|
||||
}
|
||||
}
|
||||
|
||||
public enum AvatarGalleryEntry: Equatable {
|
||||
case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, GalleryItemIndexData?, Data?, String?)
|
||||
case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, Int32?, GalleryItemIndexData?, MessageId?, Data?, String?)
|
||||
@ -115,20 +149,20 @@ public final class AvatarGalleryControllerPresentationArguments {
|
||||
}
|
||||
|
||||
public func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryEntry] {
|
||||
var updatedEntries: [AvatarGalleryEntry] = []
|
||||
let count: Int32 = Int32(entries.count)
|
||||
var index: Int32 = 0
|
||||
for entry in entries {
|
||||
let indexData = GalleryItemIndexData(position: index, totalCount: count)
|
||||
if case let .topImage(representations, videoRepresentations, peer, _, immediateThumbnailData, category) = entry {
|
||||
updatedEntries.append(.topImage(representations, videoRepresentations, peer, indexData, immediateThumbnailData, category))
|
||||
} else if case let .image(id, reference, representations, videoRepresentations, peer, date, _, messageId, immediateThumbnailData, category) = entry {
|
||||
updatedEntries.append(.image(id, reference, representations, videoRepresentations, peer, date, indexData, messageId, immediateThumbnailData, category))
|
||||
}
|
||||
index += 1
|
||||
var updatedEntries: [AvatarGalleryEntry] = []
|
||||
let count: Int32 = Int32(entries.count)
|
||||
var index: Int32 = 0
|
||||
for entry in entries {
|
||||
let indexData = GalleryItemIndexData(position: index, totalCount: count)
|
||||
if case let .topImage(representations, videoRepresentations, peer, _, immediateThumbnailData, category) = entry {
|
||||
updatedEntries.append(.topImage(representations, videoRepresentations, peer, indexData, immediateThumbnailData, category))
|
||||
} else if case let .image(id, reference, representations, videoRepresentations, peer, date, _, messageId, immediateThumbnailData, category) = entry {
|
||||
updatedEntries.append(.image(id, reference, representations, videoRepresentations, peer, date, indexData, messageId, immediateThumbnailData, category))
|
||||
}
|
||||
return updatedEntries
|
||||
index += 1
|
||||
}
|
||||
return updatedEntries
|
||||
}
|
||||
|
||||
public func initialAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<[AvatarGalleryEntry], NoError> {
|
||||
var initialEntries: [AvatarGalleryEntry] = []
|
||||
|
30
submodules/PeerInfoAvatarListNode/BUILD
Normal file
30
submodules/PeerInfoAvatarListNode/BUILD
Normal file
@ -0,0 +1,30 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PeerInfoAvatarListNode",
|
||||
module_name = "PeerInfoAvatarListNode",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/SyncCore:SyncCore",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AvatarNode:AvatarNode",
|
||||
"//submodules/PhotoResources:PhotoResources",
|
||||
"//submodules/RadialStatusNode:RadialStatusNode",
|
||||
"//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI",
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/TelegramUniversalVideoContent:TelegramUniversalVideoContent",
|
||||
"//submodules/GalleryUI:GalleryUI",
|
||||
"//submodules/MediaPlayer:UniversalMediaPlayer",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
File diff suppressed because it is too large
Load Diff
@ -42,6 +42,8 @@ swift_library(
|
||||
"//submodules/DeviceProximity:DeviceProximity",
|
||||
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
||||
"//submodules/TemporaryCachedPeerDataManager:TemporaryCachedPeerDataManager",
|
||||
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
|
||||
"//submodules/WebSearchUI:WebSearchUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -24,6 +24,10 @@ import DirectionalPanGesture
|
||||
import PeerInfoUI
|
||||
import AvatarNode
|
||||
import TooltipUI
|
||||
import LegacyUI
|
||||
import LegacyComponents
|
||||
import LegacyMediaPickerUI
|
||||
import WebSearchUI
|
||||
|
||||
private let panelBackgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||
private let secondaryPanelBackgroundColor = UIColor(rgb: 0x2c2c2e)
|
||||
@ -598,7 +602,9 @@ public final class VoiceChatController: ViewController {
|
||||
}
|
||||
switch state {
|
||||
case .listening:
|
||||
if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||
if peerEntry.isMyPeer {
|
||||
text = .text(presentationData.strings.VoiceChat_You, .accent)
|
||||
} else if let muteState = peerEntry.muteState, muteState.mutedByYou {
|
||||
text = .text(presentationData.strings.VoiceChat_StatusMutedForYou, .destructive)
|
||||
} else if let about = peerEntry.about, !about.isEmpty {
|
||||
text = .text(about, .generic)
|
||||
@ -629,7 +635,9 @@ public final class VoiceChatController: ViewController {
|
||||
text = .text(presentationData.strings.VoiceChat_StatusInvited, .generic)
|
||||
icon = .invite(true)
|
||||
case .raisedHand:
|
||||
if let about = peerEntry.about, !about.isEmpty && !peerEntry.displayRaisedHandStatus {
|
||||
if peerEntry.isMyPeer && !peerEntry.displayRaisedHandStatus {
|
||||
text = .text(presentationData.strings.VoiceChat_You, .accent)
|
||||
} else if let about = peerEntry.about, !about.isEmpty && !peerEntry.displayRaisedHandStatus {
|
||||
text = .text(about, .generic)
|
||||
} else {
|
||||
text = .text(presentationData.strings.VoiceChat_StatusWantsToSpeak, .accent)
|
||||
@ -668,6 +676,8 @@ public final class VoiceChatController: ViewController {
|
||||
return ListTransition(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, canInvite: canInvite, crossFade: crossFade, count: toEntries.count, animated: animated)
|
||||
}
|
||||
|
||||
private let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
|
||||
|
||||
private weak var controller: VoiceChatController?
|
||||
private let sharedContext: SharedAccountContext
|
||||
private let context: AccountContext
|
||||
@ -1221,6 +1231,35 @@ public final class VoiceChatController: ViewController {
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_ChangePhoto, icon: { theme in
|
||||
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Camera"), color: theme.actionSheet.primaryTextColor)
|
||||
// }, action: { _, f in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// f(.default)
|
||||
//
|
||||
// strongSelf.openAvatarForEditing(fromGallery: false, completion: {})
|
||||
// })))
|
||||
// items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.VoiceChat_EditBio, icon: { theme in
|
||||
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pencil"), color: theme.actionSheet.primaryTextColor)
|
||||
// }, action: { _, f in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// f(.default)
|
||||
//
|
||||
// let controller = voiceChatTitleEditController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, forceTheme: strongSelf.darkTheme, title: presentationData.strings.VoiceChat_EditBioTitle, text: presentationData.strings.VoiceChat_EditBioText, placeholder: presentationData.strings.VoiceChat_EditBioPlaceholder, doneButtonTitle: presentationData.strings.VoiceChat_EditBioSave, value: entry.about, apply: { bio in
|
||||
// if let strongSelf = self {
|
||||
// let _ = updateAbout(account: strongSelf.context.account, about: bio)
|
||||
// |> `catch` { _ -> Signal<Void, NoError> in
|
||||
// return .complete()
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// self?.controller?.present(controller, in: .window(.root))
|
||||
// })))
|
||||
} else {
|
||||
if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) {
|
||||
if callState.adminIds.contains(peer.id) {
|
||||
@ -3481,6 +3520,152 @@ public final class VoiceChatController: ViewController {
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
}
|
||||
}
|
||||
|
||||
private func openAvatarForEditing(fromGallery: Bool = false, completion: @escaping () -> Void = {}) {
|
||||
guard let peerId = self.callState?.myPeerId else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> (Peer?, SearchBotsConfiguration) in
|
||||
return (transaction.getPeer(peerId), currentSearchBotsConfiguration(transaction: transaction))
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer, searchBotsConfiguration in
|
||||
guard let strongSelf = self, let peer = peer else {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = strongSelf.presentationData
|
||||
|
||||
let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme)
|
||||
legacyController.statusBar.statusBarStyle = .Ignore
|
||||
|
||||
let emptyController = LegacyEmptyController(context: legacyController.context)!
|
||||
let navigationController = makeLegacyNavigationController(rootController: emptyController)
|
||||
navigationController.setNavigationBarHidden(true, animated: false)
|
||||
navigationController.navigationBar.transform = CGAffineTransform(translationX: -1000.0, y: 0.0)
|
||||
|
||||
legacyController.bind(controller: navigationController)
|
||||
|
||||
strongSelf.view.endEditing(true)
|
||||
strongSelf.controller?.present(legacyController, in: .window(.root))
|
||||
|
||||
var hasPhotos = false
|
||||
if !peer.profileImageRepresentations.isEmpty {
|
||||
hasPhotos = true
|
||||
}
|
||||
|
||||
let paintStickersContext = LegacyPaintStickersContext(context: strongSelf.context)
|
||||
// paintStickersContext.presentStickersController = { completion in
|
||||
// let controller = DrawingStickersScreen(context: strongSelf.context, selectSticker: { fileReference, node, rect in
|
||||
// let coder = PostboxEncoder()
|
||||
// coder.encodeRootObject(fileReference.media)
|
||||
// completion?(coder.makeData(), fileReference.media.isAnimatedSticker, node.view, rect)
|
||||
// return true
|
||||
// })
|
||||
// strongSelf.controller?.present(controller, in: .window(.root))
|
||||
// return controller
|
||||
// }
|
||||
|
||||
let mixin = TGMediaAvatarMenuMixin(context: legacyController.context, parentController: emptyController, hasSearchButton: true, hasDeleteButton: hasPhotos && !fromGallery, hasViewButton: false, personalPhoto: peerId.namespace == Namespaces.Peer.CloudUser, isVideo: false, saveEditedPhotos: false, saveCapturedMedia: false, signup: false)!
|
||||
mixin.stickersContext = paintStickersContext
|
||||
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, chatLocation: nil, configuration: searchBotsConfiguration, mode: .avatar(initialQuery: peer.id.namespace == Namespaces.Peer.CloudUser ? nil : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), completion: { [weak self] result in
|
||||
assetsController?.dismiss()
|
||||
// self?.updateProfilePhoto(result)
|
||||
}))
|
||||
controller.navigationPresentation = .modal
|
||||
strongSelf.controller?.push(controller)
|
||||
|
||||
if fromGallery {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
mixin.didFinishWithImage = { [weak self] image in
|
||||
if let image = image {
|
||||
completion()
|
||||
// self?.updateProfilePhoto(image)
|
||||
}
|
||||
}
|
||||
mixin.didFinishWithVideo = { [weak self] image, asset, adjustments in
|
||||
if let image = image, let asset = asset {
|
||||
completion()
|
||||
// self?.updateProfileVideo(image, asset: asset, adjustments: adjustments)
|
||||
}
|
||||
}
|
||||
mixin.didFinishWithDelete = {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
// let proceed = {
|
||||
// if let item = item {
|
||||
// strongSelf.deleteAvatar(item, remove: false)
|
||||
// }
|
||||
//
|
||||
// let _ = strongSelf.currentAvatarMixin.swap(nil)
|
||||
// if let _ = peer.smallProfileImage {
|
||||
// strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
|
||||
// if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||
// strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
|
||||
// }
|
||||
// }
|
||||
// let postbox = strongSelf.context.account.postbox
|
||||
// strongSelf.updateAvatarDisposable.set((updatePeerPhoto(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, stateManager: strongSelf.context.account.stateManager, accountPeerId: strongSelf.context.account.peerId, peerId: strongSelf.peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in
|
||||
// return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
|
||||
// })
|
||||
// |> deliverOnMainQueue).start(next: { result in
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// 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
|
||||
// }
|
||||
// }))
|
||||
// }
|
||||
//
|
||||
// let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||
// let items: [ActionSheetItem] = [
|
||||
// ActionSheetButtonItem(title: presentationData.strings.Settings_RemoveConfirmation, 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()
|
||||
// })
|
||||
// ])
|
||||
// ])
|
||||
// strongSelf.controller?.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
mixin.didDismiss = { [weak legacyController] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let _ = strongSelf.currentAvatarMixin.swap(nil)
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
let menuController = mixin.present()
|
||||
if let menuController = menuController {
|
||||
menuController.customRemoveFromParentViewController = { [weak legacyController] in
|
||||
legacyController?.dismiss()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private let sharedContext: SharedAccountContext
|
||||
@ -3521,6 +3706,8 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
|
||||
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
@ -3698,7 +3885,7 @@ public final class VoiceChatController: ViewController {
|
||||
|
||||
private final class VoiceChatContextExtractedContentSource: ContextExtractedContentSource {
|
||||
var keepInPlace: Bool
|
||||
let ignoreContentTouches: Bool = true
|
||||
let ignoreContentTouches: Bool = false
|
||||
let blurBackground: Bool
|
||||
|
||||
private let controller: ViewController
|
||||
|
@ -17,6 +17,7 @@ import ContextUI
|
||||
import AccountContext
|
||||
import LegacyComponents
|
||||
import AudioBlob
|
||||
import PeerInfoAvatarListNode
|
||||
|
||||
final class VoiceChatParticipantItem: ListViewItem {
|
||||
enum ParticipantText {
|
||||
@ -158,6 +159,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
private var extractedRect: CGRect?
|
||||
private var nonExtractedRect: CGRect?
|
||||
private var extractedVerticalOffset: CGFloat?
|
||||
|
||||
fileprivate let avatarNode: AvatarNode
|
||||
private let titleNode: TextNode
|
||||
@ -165,6 +167,11 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
private let expandedStatusNode: TextNode
|
||||
private var credibilityIconNode: ASImageNode?
|
||||
|
||||
private var avatarTransitionNode: ASImageNode?
|
||||
private var avatarListContainerNode: ASDisplayNode?
|
||||
private var avatarListWrapperNode: ASDisplayNode?
|
||||
private var avatarListNode: PeerInfoAvatarListContainerNode?
|
||||
|
||||
private let actionContainerNode: ASDisplayNode
|
||||
private var animationNode: VoiceChatMicrophoneNode?
|
||||
private var iconNode: ASImageNode?
|
||||
@ -201,6 +208,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
|
||||
self.extractedBackgroundImageNode = ASImageNode()
|
||||
self.extractedBackgroundImageNode.clipsToBounds = true
|
||||
self.extractedBackgroundImageNode.displaysAsynchronously = false
|
||||
self.extractedBackgroundImageNode.alpha = 0.0
|
||||
|
||||
@ -282,29 +290,187 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
strongSelf.isExtracted = isExtracted
|
||||
|
||||
let inset: CGFloat = 12.0
|
||||
let cornerRadius: CGFloat = 14.0
|
||||
if isExtracted {
|
||||
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.list.itemBlocksBackgroundColor)
|
||||
}
|
||||
|
||||
if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect {
|
||||
let rect = isExtracted ? extractedRect : nonExtractedRect
|
||||
transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: rect)
|
||||
}
|
||||
|
||||
transition.updateAlpha(node: strongSelf.statusNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
transition.updateAlpha(node: strongSelf.expandedStatusNode, alpha: isExtracted ? 1.0 : 0.0)
|
||||
|
||||
transition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? 12.0 : 0.0, y: 0.0))
|
||||
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.actionContainerNode.layer, offset: CGPoint(x: isExtracted ? -24.0 : 0.0, y: 0.0))
|
||||
|
||||
transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
|
||||
if !isExtracted {
|
||||
self?.extractedBackgroundImageNode.image = nil
|
||||
strongSelf.contextSourceNode.contentNode.customHitTest = { [weak self] point in
|
||||
if let strongSelf = self {
|
||||
if let avatarListContainerNode = strongSelf.avatarListContainerNode, avatarListContainerNode.frame.contains(point) {
|
||||
return strongSelf.avatarListNode?.view
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
} else {
|
||||
strongSelf.contextSourceNode.contentNode.customHitTest = nil
|
||||
}
|
||||
|
||||
let extractedVerticalOffset = strongSelf.extractedVerticalOffset ?? 0.0
|
||||
if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect {
|
||||
let rect: CGRect
|
||||
if isExtracted {
|
||||
if extractedVerticalOffset > 0.0 {
|
||||
rect = CGRect(x: extractedRect.minX, y: extractedRect.minY + extractedVerticalOffset, width: extractedRect.width, height: extractedRect.height - extractedVerticalOffset)
|
||||
} else {
|
||||
rect = extractedRect
|
||||
}
|
||||
} else {
|
||||
rect = nonExtractedRect
|
||||
}
|
||||
|
||||
let springDuration: Double = isExtracted ? 0.42 : 0.3
|
||||
let springDamping: CGFloat = isExtracted ? 104.0 : 1000.0
|
||||
|
||||
if !extractedVerticalOffset.isZero {
|
||||
if isExtracted {
|
||||
strongSelf.extractedBackgroundImageNode.image = generateImage(CGSize(width: cornerRadius * 2.0, height: cornerRadius * 2.0), rotatedContext: { (size, context) in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
context.setFillColor(item.presentationData.theme.list.itemBlocksBackgroundColor.cgColor)
|
||||
context.fillEllipse(in: bounds)
|
||||
context.fill(CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height / 2.0))
|
||||
})?.stretchableImage(withLeftCapWidth: Int(cornerRadius), topCapHeight: Int(cornerRadius))
|
||||
strongSelf.extractedBackgroundImageNode.cornerRadius = cornerRadius
|
||||
|
||||
var avatarInitialRect = strongSelf.avatarNode.view.convert(strongSelf.avatarNode.bounds, to: strongSelf.offsetContainerNode.supernode?.view)
|
||||
if strongSelf.avatarTransitionNode == nil {
|
||||
transition.updateCornerRadius(node: strongSelf.extractedBackgroundImageNode, cornerRadius: 0.0)
|
||||
|
||||
let targetRect = CGRect(x: extractedRect.minX, y: extractedRect.minY, width: extractedRect.width, height: extractedRect.width)
|
||||
let initialScale = avatarInitialRect.width / targetRect.width
|
||||
avatarInitialRect.origin.y += cornerRadius / 2.0 * initialScale
|
||||
|
||||
let avatarListWrapperNode = ASDisplayNode()
|
||||
avatarListWrapperNode.clipsToBounds = true
|
||||
avatarListWrapperNode.frame = CGRect(x: targetRect.minX, y: targetRect.minY, width: targetRect.width, height: targetRect.height + cornerRadius)
|
||||
avatarListWrapperNode.cornerRadius = cornerRadius
|
||||
|
||||
let transitionNode = ASImageNode()
|
||||
transitionNode.clipsToBounds = true
|
||||
transitionNode.displaysAsynchronously = false
|
||||
transitionNode.displayWithoutProcessing = true
|
||||
transitionNode.image = strongSelf.avatarNode.unroundedImage
|
||||
transitionNode.frame = CGRect(origin: CGPoint(), size: targetRect.size)
|
||||
transitionNode.cornerRadius = targetRect.width / 2.0
|
||||
transition.updateCornerRadius(node: transitionNode, cornerRadius: 0.0)
|
||||
|
||||
strongSelf.avatarNode.isHidden = true
|
||||
|
||||
avatarListWrapperNode.addSubnode(transitionNode)
|
||||
strongSelf.avatarTransitionNode = transitionNode
|
||||
|
||||
let avatarListContainerNode = ASDisplayNode()
|
||||
avatarListContainerNode.clipsToBounds = true
|
||||
avatarListContainerNode.frame = CGRect(origin: CGPoint(), size: targetRect.size)
|
||||
avatarListContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
avatarListContainerNode.cornerRadius = targetRect.width / 2.0
|
||||
|
||||
avatarListWrapperNode.layer.animateSpring(from: initialScale as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||
avatarListWrapperNode.layer.animateSpring(from: NSValue(cgPoint: avatarInitialRect.center), to: NSValue(cgPoint: avatarListWrapperNode.position), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||
transition.updateCornerRadius(node: avatarListContainerNode, cornerRadius: 0.0)
|
||||
|
||||
let avatarListNode = PeerInfoAvatarListContainerNode(context: item.context)
|
||||
avatarListNode.peer = item.peer
|
||||
avatarListNode.frame = CGRect(x: targetRect.width / 2.0, y: targetRect.height / 2.0, width: targetRect.width, height: targetRect.height)
|
||||
avatarListNode.controlsClippingNode.frame = CGRect(x: -targetRect.width / 2.0, y: -targetRect.height / 2.0, width: targetRect.width, height: targetRect.height)
|
||||
avatarListNode.controlsClippingOffsetNode.frame = CGRect(origin: CGPoint(x: targetRect.width / 2.0, y: targetRect.height / 2.0), size: CGSize())
|
||||
avatarListNode.stripContainerNode.frame = CGRect(x: 0.0, y: 13.0, width: targetRect.width, height: 2.0)
|
||||
|
||||
avatarListContainerNode.addSubnode(avatarListNode)
|
||||
avatarListContainerNode.addSubnode(avatarListNode.controlsClippingOffsetNode)
|
||||
avatarListWrapperNode.addSubnode(avatarListContainerNode)
|
||||
|
||||
avatarListNode.update(size: targetRect.size, peer: item.peer, isExpanded: true, transition: .immediate)
|
||||
strongSelf.offsetContainerNode.supernode?.addSubnode(avatarListWrapperNode)
|
||||
|
||||
strongSelf.avatarListWrapperNode = avatarListWrapperNode
|
||||
strongSelf.avatarListContainerNode = avatarListContainerNode
|
||||
strongSelf.avatarListNode = avatarListNode
|
||||
}
|
||||
} else if let transitionNode = strongSelf.avatarTransitionNode, let avatarListWrapperNode = strongSelf.avatarListWrapperNode, let avatarListContainerNode = strongSelf.avatarListContainerNode {
|
||||
transition.updateCornerRadius(node: strongSelf.extractedBackgroundImageNode, cornerRadius: cornerRadius)
|
||||
|
||||
var avatarInitialRect = CGRect(origin: strongSelf.avatarNode.frame.origin, size: strongSelf.avatarNode.frame.size)
|
||||
let targetScale = avatarInitialRect.width / avatarListContainerNode.frame.width
|
||||
avatarInitialRect.origin.y += cornerRadius / 2.0 * targetScale
|
||||
|
||||
strongSelf.avatarTransitionNode = nil
|
||||
strongSelf.avatarListWrapperNode = nil
|
||||
strongSelf.avatarListContainerNode = nil
|
||||
strongSelf.avatarListNode = nil
|
||||
|
||||
avatarListContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak avatarListContainerNode] _ in
|
||||
avatarListContainerNode?.removeFromSupernode()
|
||||
})
|
||||
|
||||
avatarListWrapperNode.layer.animateSpring(from: 1.0 as NSNumber, to: targetScale as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false)
|
||||
avatarListWrapperNode.layer.animateSpring(from: NSValue(cgPoint: avatarListWrapperNode.position), to: NSValue(cgPoint: avatarInitialRect.center), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, removeOnCompletion: false, completion: { [weak transitionNode, weak self] _ in
|
||||
transitionNode?.removeFromSupernode()
|
||||
self?.avatarNode.isHidden = false
|
||||
})
|
||||
|
||||
transition.updateCornerRadius(node: avatarListContainerNode, cornerRadius: avatarListContainerNode.frame.width / 2.0)
|
||||
transition.updateCornerRadius(node: transitionNode, cornerRadius: avatarListContainerNode.frame.width / 2.0)
|
||||
}
|
||||
|
||||
transition.updateAlpha(node: strongSelf.statusNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
transition.updateAlpha(node: strongSelf.expandedStatusNode, alpha: isExtracted ? 1.0 : 0.0)
|
||||
transition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
|
||||
let offsetInitialSublayerTransform = strongSelf.offsetContainerNode.layer.sublayerTransform
|
||||
strongSelf.offsetContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? -33 : 0.0, isExtracted ? extractedVerticalOffset : 0.0, 0.0)
|
||||
|
||||
let actionInitialSublayerTransform = strongSelf.actionContainerNode.layer.sublayerTransform
|
||||
strongSelf.actionContainerNode.layer.sublayerTransform = CATransform3DMakeTranslation(isExtracted ? 21.0 : 0.0, 0.0, 0.0)
|
||||
|
||||
let extractedInitialBackgroundPosition = strongSelf.extractedBackgroundImageNode.position
|
||||
strongSelf.extractedBackgroundImageNode.layer.position = rect.center
|
||||
let extractedInitialBackgroundBounds = strongSelf.extractedBackgroundImageNode.bounds
|
||||
strongSelf.extractedBackgroundImageNode.layer.bounds = CGRect(origin: CGPoint(), size: rect.size)
|
||||
if isExtracted {
|
||||
strongSelf.offsetContainerNode.layer.animateSpring(from: NSValue(caTransform3D: offsetInitialSublayerTransform), to: NSValue(caTransform3D: strongSelf.offsetContainerNode.layer.sublayerTransform), keyPath: "sublayerTransform", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping)
|
||||
strongSelf.actionContainerNode.layer.animateSpring(from: NSValue(caTransform3D: actionInitialSublayerTransform), to: NSValue(caTransform3D: strongSelf.actionContainerNode.layer.sublayerTransform), keyPath: "sublayerTransform", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping)
|
||||
strongSelf.extractedBackgroundImageNode.layer.animateSpring(from: NSValue(cgPoint: extractedInitialBackgroundPosition), to: NSValue(cgPoint: strongSelf.extractedBackgroundImageNode.position), keyPath: "position", duration: springDuration, delay: 0.0, initialVelocity: 0.0, damping: springDamping)
|
||||
strongSelf.extractedBackgroundImageNode.layer.animateSpring(from: NSValue(cgRect: extractedInitialBackgroundBounds), to: NSValue(cgRect: strongSelf.extractedBackgroundImageNode.bounds), keyPath: "bounds", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||
} else {
|
||||
strongSelf.offsetContainerNode.layer.animate(from: NSValue(caTransform3D: offsetInitialSublayerTransform), to: NSValue(caTransform3D: strongSelf.offsetContainerNode.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.25)
|
||||
strongSelf.actionContainerNode.layer.animate(from: NSValue(caTransform3D: actionInitialSublayerTransform), to: NSValue(caTransform3D: strongSelf.actionContainerNode.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.25)
|
||||
strongSelf.extractedBackgroundImageNode.layer.animate(from: NSValue(cgPoint: extractedInitialBackgroundPosition), to: NSValue(cgPoint: strongSelf.extractedBackgroundImageNode.position), keyPath: "position", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.25)
|
||||
strongSelf.extractedBackgroundImageNode.layer.animate(from: NSValue(cgRect: extractedInitialBackgroundBounds), to: NSValue(cgRect: strongSelf.extractedBackgroundImageNode.bounds), keyPath: "bounds", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 0.25)
|
||||
}
|
||||
|
||||
if isExtracted {
|
||||
strongSelf.extractedBackgroundImageNode.alpha = 1.0
|
||||
strongSelf.extractedBackgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.06, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
} else {
|
||||
strongSelf.extractedBackgroundImageNode.alpha = 0.0
|
||||
strongSelf.extractedBackgroundImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, delay: 0.15, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
self?.extractedBackgroundImageNode.image = nil
|
||||
self?.extractedBackgroundImageNode.layer.removeAllAnimations()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if isExtracted {
|
||||
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: cornerRadius * 2.0, color: item.presentationData.theme.list.itemBlocksBackgroundColor)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: strongSelf.extractedBackgroundImageNode, frame: rect)
|
||||
|
||||
transition.updateAlpha(node: strongSelf.statusNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
transition.updateAlpha(node: strongSelf.expandedStatusNode, alpha: isExtracted ? 1.0 : 0.0)
|
||||
transition.updateAlpha(node: strongSelf.actionContainerNode, alpha: isExtracted ? 0.0 : 1.0)
|
||||
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.offsetContainerNode.layer, offset: CGPoint(x: isExtracted ? inset : 0.0, y: isExtracted ? extractedVerticalOffset : 0.0))
|
||||
transition.updateSublayerTransformOffset(layer: strongSelf.actionContainerNode.layer, offset: CGPoint(x: isExtracted ? -24.0 : 0.0, y: 0.0))
|
||||
|
||||
transition.updateAlpha(node: strongSelf.extractedBackgroundImageNode, alpha: isExtracted ? 1.0 : 0.0, completion: { _ in
|
||||
if !isExtracted {
|
||||
self?.extractedBackgroundImageNode.image = nil
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,7 +478,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
self.audioLevelDisposable.dispose()
|
||||
self.raiseHandTimer?.invalidate()
|
||||
}
|
||||
|
||||
|
||||
override func selected() {
|
||||
super.selected()
|
||||
self.layoutParams?.0.action?(self.contextSourceNode)
|
||||
@ -515,12 +681,19 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.layoutParams = (item, params, first, last)
|
||||
strongSelf.wavesColor = wavesColor
|
||||
|
||||
let nonExtractedRect = CGRect(origin: CGPoint(), size: CGSize(width: layout.contentSize.width - 16.0, height: layout.contentSize.height))
|
||||
let nonExtractedRect = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: CGSize(width: layout.contentSize.width - 32.0, height: layout.contentSize.height))
|
||||
|
||||
var extractedRect = CGRect(origin: CGPoint(), size: layout.contentSize).insetBy(dx: 16.0 + params.leftInset, dy: 0.0)
|
||||
let extractedHeight = extractedRect.height + expandedStatusLayout.size.height - statusLayout.size.height
|
||||
var extractedHeight = extractedRect.height + expandedStatusLayout.size.height - statusLayout.size.height
|
||||
var extractedVerticalOffset: CGFloat = 0.0
|
||||
if item.peer.smallProfileImage != nil {
|
||||
extractedVerticalOffset = extractedRect.width
|
||||
extractedHeight += extractedVerticalOffset
|
||||
}
|
||||
|
||||
extractedRect.size.height = extractedHeight
|
||||
|
||||
strongSelf.extractedVerticalOffset = extractedVerticalOffset
|
||||
strongSelf.extractedRect = extractedRect
|
||||
strongSelf.nonExtractedRect = nonExtractedRect
|
||||
|
||||
@ -702,7 +875,7 @@ class VoiceChatParticipantItemNode: ItemListRevealOptionsItemNode {
|
||||
if item.peer.isDeleted {
|
||||
overrideImage = .deletedIcon
|
||||
}
|
||||
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad)
|
||||
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: overrideImage, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad, storeUnrounded: true)
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))
|
||||
|
||||
|
@ -408,7 +408,7 @@ private final class VoiceChatTitleEditAlertContentNode: AlertContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
func voiceChatTitleEditController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, text: String, placeholder: String, value: String?, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
func voiceChatTitleEditController(sharedContext: SharedAccountContext, account: Account, forceTheme: PresentationTheme?, title: String, text: String, placeholder: String, doneButtonTitle: String? = nil, value: String?, apply: @escaping (String?) -> Void) -> AlertController {
|
||||
var presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||
if let forceTheme = forceTheme {
|
||||
presentationData = presentationData.withUpdated(theme: forceTheme)
|
||||
@ -419,7 +419,7 @@ func voiceChatTitleEditController(sharedContext: SharedAccountContext, account:
|
||||
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?(true)
|
||||
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Done, action: {
|
||||
}), TextAlertAction(type: .defaultAction, title: doneButtonTitle ?? presentationData.strings.Common_Done, action: {
|
||||
applyImpl?()
|
||||
})]
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -218,6 +218,7 @@ swift_library(
|
||||
"//submodules/DatePickerNode:DatePickerNode",
|
||||
"//submodules/ConfettiEffect:ConfettiEffect",
|
||||
"//submodules/Speak:Speak",
|
||||
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Camera.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Camera.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic_menu_camera.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Camera.imageset/ic_menu_camera.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Camera.imageset/ic_menu_camera.pdf
vendored
Normal file
Binary file not shown.
Binary file not shown.
@ -64,6 +64,7 @@ import ChatHistoryImportTasks
|
||||
import Markdown
|
||||
import TelegramPermissionsUI
|
||||
import Speak
|
||||
import UniversalMediaPlayer
|
||||
|
||||
extension ChatLocation {
|
||||
var peerId: PeerId {
|
||||
@ -1627,7 +1628,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
actionSheet?.dismissAnimated()
|
||||
UIPasteboard.general.string = mention
|
||||
|
||||
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_UsernameCopied)
|
||||
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_TextCopied)
|
||||
self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
}))
|
||||
}
|
||||
@ -1652,7 +1653,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
actionSheet?.dismissAnimated()
|
||||
UIPasteboard.general.string = mention
|
||||
|
||||
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_TextCopied)
|
||||
let content: UndoOverlayContent = .copy(text: presentationData.strings.Conversation_UsernameCopied)
|
||||
self?.present(UndoOverlayController(presentationData: presentationData, content: content, elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
})
|
||||
]), ActionSheetItemGroup(items: [
|
||||
|
@ -6,6 +6,7 @@ import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import UniversalMediaPlayer
|
||||
|
||||
private struct FetchManagerLocationEntryId: Hashable {
|
||||
let location: FetchManagerLocation
|
||||
|
@ -6,6 +6,7 @@ import SyncCore
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import PhotoResources
|
||||
import UniversalMediaPlayer
|
||||
|
||||
private final class PrefetchMediaContext {
|
||||
let fetchDisposable = MetaDisposable()
|
||||
|
@ -338,40 +338,6 @@ private func peerInfoScreenInputData(context: AccountContext, peerId: PeerId, is
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
|
||||
private func peerInfoProfilePhotos(context: AccountContext, peerId: PeerId) -> Signal<Any, NoError> {
|
||||
return context.account.postbox.combinedView(keys: [.basicPeer(peerId)])
|
||||
|> mapToSignal { view -> Signal<AvatarGalleryEntry?, NoError> in
|
||||
guard let peer = (view.views[.basicPeer(peerId)] as? BasicPeerView)?.peer else {
|
||||
return .single(nil)
|
||||
}
|
||||
return initialAvatarGalleryEntries(account: context.account, peer: peer)
|
||||
|> map { entries in
|
||||
return entries.first
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { firstEntry -> Signal<(Bool, [AvatarGalleryEntry]), NoError> in
|
||||
if let firstEntry = firstEntry {
|
||||
return context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> mapToSignal { peer -> Signal<(Bool, [AvatarGalleryEntry]), NoError>in
|
||||
return fetchedAvatarGalleryEntries(account: context.account, peer: peer, firstEntry: firstEntry)
|
||||
}
|
||||
} else {
|
||||
return .single((true, []))
|
||||
}
|
||||
}
|
||||
|> map { items -> Any in
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: PeerId) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> {
|
||||
return context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId))
|
||||
|> map { items -> (Bool, [AvatarGalleryEntry]) in
|
||||
return items as? (Bool, [AvatarGalleryEntry]) ?? (true, [])
|
||||
}
|
||||
}
|
||||
|
||||
func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId) -> Signal<Never, NoError> {
|
||||
return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: false)
|
||||
|> mapToSignal { inputData -> Signal<Never, NoError> in
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -59,6 +59,7 @@ import MediaResources
|
||||
import HashtagSearchUI
|
||||
import ActionSheetPeerItem
|
||||
import TelegramCallsUI
|
||||
import PeerInfoAvatarListNode
|
||||
|
||||
protocol PeerInfoScreenItem: class {
|
||||
var id: AnyHashable { get }
|
||||
|
@ -7,6 +7,7 @@ import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import PhotoResources
|
||||
import Emoji
|
||||
import UniversalMediaPlayer
|
||||
|
||||
private final class PrefetchMediaContext {
|
||||
let fetchDisposable = MetaDisposable()
|
||||
|
Loading…
x
Reference in New Issue
Block a user