mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Bug fixes and performance improvements
This commit is contained in:
parent
76893115f6
commit
580e1cebc5
@ -442,7 +442,7 @@ public protocol SharedAccountContext: class {
|
||||
func openChatMessage(_ params: OpenChatMessageParams) -> Bool
|
||||
func messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, account: Account, chatLocation: ChatLocation, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError>
|
||||
func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController
|
||||
func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool) -> ViewController?
|
||||
func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController?
|
||||
func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController
|
||||
func makePeersNearbyController(context: AccountContext) -> ViewController
|
||||
func makeComposeController(context: AccountContext) -> ViewController
|
||||
|
@ -149,7 +149,7 @@ public final class CallListController: ViewController {
|
||||
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if let strongSelf = self, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .calls(messages: messages), avatarInitiallyExpanded: false) {
|
||||
if let strongSelf = self, let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .calls(messages: messages), avatarInitiallyExpanded: false, fromChat: false) {
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
|
@ -530,7 +530,7 @@ public class ContactsController: ViewController {
|
||||
return
|
||||
}
|
||||
if let peer = peer {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||
}
|
||||
} else {
|
||||
|
@ -1180,7 +1180,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if let strongSelf = self {
|
||||
if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let controller = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
strongSelf.getNavigationController()?.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
|
@ -208,9 +208,9 @@ private final class LoadingShimmerNode: ASDisplayNode {
|
||||
public struct ItemListPeerItemEditing: Equatable {
|
||||
public var editable: Bool
|
||||
public var editing: Bool
|
||||
public var revealed: Bool
|
||||
public var revealed: Bool?
|
||||
|
||||
public init(editable: Bool, editing: Bool, revealed: Bool) {
|
||||
public init(editable: Bool, editing: Bool, revealed: Bool?) {
|
||||
self.editable = editable
|
||||
self.editing = editing
|
||||
self.revealed = revealed
|
||||
@ -1095,7 +1095,9 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
||||
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||
|
||||
strongSelf.setRevealOptions((left: [], right: peerRevealOptions))
|
||||
strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated)
|
||||
if let revealed = item.editing.revealed {
|
||||
strongSelf.setRevealOptionsOpened(revealed, animated: animated)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -330,7 +330,7 @@ public final class SecureIdAuthController: ViewController, StandalonePresentable
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||
}
|
||||
})
|
||||
|
@ -13,7 +13,7 @@ import GalleryUI
|
||||
|
||||
public enum AvatarGalleryEntry: Equatable {
|
||||
case topImage([ImageRepresentationWithReference], GalleryItemIndexData?)
|
||||
case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], Peer, Int32, GalleryItemIndexData?, MessageId?)
|
||||
case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], Peer?, Int32, GalleryItemIndexData?, MessageId?)
|
||||
|
||||
public var representations: [ImageRepresentationWithReference] {
|
||||
switch self {
|
||||
@ -61,7 +61,7 @@ public final class AvatarGalleryControllerPresentationArguments {
|
||||
}
|
||||
}
|
||||
|
||||
private func initialAvatarGalleryEntries(peer: Peer) -> [AvatarGalleryEntry] {
|
||||
public func initialAvatarGalleryEntries(peer: Peer) -> [AvatarGalleryEntry] {
|
||||
var initialEntries: [AvatarGalleryEntry] = []
|
||||
if !peer.profileImageRepresentations.isEmpty, let peerReference = PeerReference(peer) {
|
||||
initialEntries.append(.topImage(peer.profileImageRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), nil))
|
||||
@ -96,6 +96,33 @@ public func fetchedAvatarGalleryEntries(account: Account, peer: Peer) -> Signal<
|
||||
)
|
||||
}
|
||||
|
||||
public func fetchedAvatarGalleryEntries(account: Account, peer: Peer, firstEntry: AvatarGalleryEntry) -> Signal<[AvatarGalleryEntry], NoError> {
|
||||
let initialEntries = [firstEntry]
|
||||
return Signal<[AvatarGalleryEntry], NoError>.single(initialEntries)
|
||||
|> then(
|
||||
requestPeerPhotos(account: account, peerId: peer.id)
|
||||
|> map { photos -> [AvatarGalleryEntry] in
|
||||
var result: [AvatarGalleryEntry] = []
|
||||
let initialEntries = [firstEntry]
|
||||
if photos.isEmpty {
|
||||
result = initialEntries
|
||||
} else {
|
||||
var index: Int32 = 0
|
||||
for photo in photos {
|
||||
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
|
||||
if result.isEmpty, let first = initialEntries.first {
|
||||
result.append(.image(photo.image.reference, first.representations, peer, photo.date, indexData, photo.messageId))
|
||||
} else {
|
||||
result.append(.image(photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.standalone(resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId))
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public class AvatarGalleryController: ViewController, StandalonePresentableController {
|
||||
private var galleryNode: GalleryControllerNode {
|
||||
return self.displayNode as! GalleryControllerNode
|
||||
|
@ -85,7 +85,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
|
||||
var dateText: String?
|
||||
switch entry {
|
||||
case let .image(_, _, peer, date, _, _):
|
||||
nameText = peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
|
||||
nameText = peer?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""
|
||||
dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date)
|
||||
default:
|
||||
break
|
||||
|
@ -127,6 +127,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
self?._ready.set(.single(Void()))
|
||||
}
|
||||
|
||||
self.imageNode.contentAnimations = .subsequentUpdates
|
||||
self.imageNode.view.contentMode = .scaleAspectFill
|
||||
self.imageNode.clipsToBounds = true
|
||||
|
||||
|
@ -757,6 +757,7 @@ public func channelBannedMemberController(context: AccountContext, peerId: PeerI
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
controller.navigationPresentation = .modal
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
@ -366,7 +366,7 @@ public func channelBlacklistController(context: AccountContext, peerId: PeerId)
|
||||
}
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.GroupRemoved_ViewUserInfo, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: participant.peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: participant.peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
pushControllerImpl?(infoController)
|
||||
}
|
||||
}))
|
||||
|
@ -450,7 +450,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
||||
}
|
||||
}))
|
||||
}, openPeer: { peer in
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
}, inviteViaLink: {
|
||||
@ -502,7 +502,7 @@ public func channelMembersController(context: AccountContext, peerId: PeerId) ->
|
||||
return state.withUpdatedSearchingMembers(false)
|
||||
}
|
||||
}, openPeer: { peer, _ in
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
pushControllerImpl?(infoController)
|
||||
}
|
||||
}, pushController: { c in
|
||||
|
@ -666,7 +666,7 @@ public func channelPermissionsController(context: AccountContext, peerId origina
|
||||
}), ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
})
|
||||
}, openPeerInfo: { peer in
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
}, openKicked: {
|
||||
|
@ -599,7 +599,7 @@ private enum GroupInfoEntry: ItemListNodeEntry {
|
||||
}))
|
||||
}
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: presence, text: .presence, label: label == nil ? .none : .text(label!, .standard), editing: editing, revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: enabled, selectable: selectable, sectionId: self.section, action: {
|
||||
if let infoController = arguments.context.sharedContext.makePeerInfoController(context: arguments.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false), selectable {
|
||||
if let infoController = arguments.context.sharedContext.makePeerInfoController(context: arguments.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false), selectable {
|
||||
arguments.pushController(infoController)
|
||||
}
|
||||
}, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
|
||||
@ -2342,7 +2342,7 @@ public func groupInfoController(context: AccountContext, peerId originalPeerId:
|
||||
return state.withUpdatedSearchingMembers(false)
|
||||
}
|
||||
}, openPeer: { peer, _ in
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
arguments.pushController(infoController)
|
||||
}
|
||||
}, pushController: { c in
|
||||
|
@ -259,7 +259,7 @@ public func blockedPeersController(context: AccountContext, blockedPeersContext:
|
||||
}
|
||||
}))
|
||||
}, openPeer: { peer in
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
})
|
||||
|
@ -341,7 +341,7 @@ public func selectivePrivacyPeersController(context: AccountContext, title: Stri
|
||||
return transaction.getPeer(peerId)
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) else {
|
||||
guard let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) else {
|
||||
return
|
||||
}
|
||||
pushControllerImpl?(controller)
|
||||
|
@ -59,7 +59,7 @@ func managedChatListHoles(network: Network, postbox: Postbox, accountPeerId: Pee
|
||||
let disposable = combineLatest(postbox.chatListHolesView(), topRootHole).start(next: { view, topRootHoleView in
|
||||
var additionalLatestHole: ChatListHole?
|
||||
if let topRootHole = topRootHoleView.views[topRootHoleKey] as? AllChatListHolesView {
|
||||
additionalLatestHole = topRootHole.latestHole
|
||||
//additionalLatestHole = topRootHole.latestHole
|
||||
}
|
||||
|
||||
let (removed, added, addedAdditionalLatestHole) = state.with { state in
|
||||
|
@ -309,6 +309,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private var isDismissed = false
|
||||
|
||||
private var focusOnSearchAfterAppearance: Bool = false
|
||||
|
||||
private let keepPeerInfoScreenDataHotDisposable = MetaDisposable()
|
||||
|
||||
public override var customData: Any? {
|
||||
return self.chatLocation
|
||||
@ -2513,6 +2515,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.reportIrrelvantGeoDisposable?.dispose()
|
||||
self.reminderActivity?.invalidate()
|
||||
self.updateSlowmodeStatusDisposable.dispose()
|
||||
self.keepPeerInfoScreenDataHotDisposable.dispose()
|
||||
}
|
||||
|
||||
public func updatePresentationMode(_ mode: ChatControllerPresentationMode) {
|
||||
@ -4731,6 +4734,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||
})]), in: .window(.root))
|
||||
}))
|
||||
|
||||
if case let .peer(peerId) = self.chatLocation {
|
||||
self.keepPeerInfoScreenDataHotDisposable.set(keepPeerInfoScreenDataHot(context: self.context, peerId: peerId).start())
|
||||
}
|
||||
}
|
||||
|
||||
if self.focusOnSearchAfterAppearance {
|
||||
@ -5356,7 +5363,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if peer.smallProfileImage == nil {
|
||||
expandAvatar = false
|
||||
}
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: expandAvatar) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: expandAvatar, fromChat: true) {
|
||||
strongSelf.effectiveNavigationController?.pushViewController(infoController)
|
||||
}
|
||||
}
|
||||
@ -7113,7 +7120,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
self.navigationActionDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
if let strongSelf = self, let peer = peer {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: expandAvatar) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: expandAvatar, fromChat: true) {
|
||||
strongSelf.effectiveNavigationController?.pushViewController(infoController)
|
||||
}
|
||||
}
|
||||
@ -7530,7 +7537,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
strongSelf.effectiveNavigationController?.pushViewController(infoController)
|
||||
}
|
||||
}
|
||||
|
@ -659,7 +659,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
if peer is TelegramChannel, let navigationController = strongSelf.getNavigationController() {
|
||||
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peer.id), animated: true))
|
||||
} else {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
strongSelf.pushController(infoController)
|
||||
}
|
||||
}
|
||||
@ -681,7 +681,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
if let strongSelf = self {
|
||||
if let peer = peer {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
strongSelf.pushController(infoController)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func openAddContactImpl(context: AccountContext, firstName: String = "", lastNam
|
||||
let contactData = DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: [DeviceContactPhoneNumberData(label: label, value: phoneNumber)]), middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
||||
present(deviceContactInfoController(context: context, subject: .create(peer: nil, contactData: contactData, isSharing: false, shareViaException: false, completion: { peer, stableId, contactData in
|
||||
if let peer = peer {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
pushController(infoController)
|
||||
}
|
||||
} else {
|
||||
|
@ -209,7 +209,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
||||
case .info:
|
||||
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
context.sharedContext.applicationBindings.dismissNativeController()
|
||||
navigationController?.pushViewController(infoController)
|
||||
}
|
||||
@ -491,7 +491,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
||||
return transaction.getPeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: idValue))
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
if let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let peer = peer, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
navigationController?.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
|
@ -1,6 +1,8 @@
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import TextFormat
|
||||
|
||||
enum PeerInfoScreenLabeledValueTextColor {
|
||||
case primary
|
||||
@ -9,7 +11,7 @@ enum PeerInfoScreenLabeledValueTextColor {
|
||||
|
||||
enum PeerInfoScreenLabeledValueTextBehavior: Equatable {
|
||||
case singleLine
|
||||
case multiLine(maxLines: Int)
|
||||
case multiLine(maxLines: Int, enabledEntities: EnabledEntityTypes)
|
||||
}
|
||||
|
||||
final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
|
||||
@ -19,14 +21,27 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem {
|
||||
let textColor: PeerInfoScreenLabeledValueTextColor
|
||||
let textBehavior: PeerInfoScreenLabeledValueTextBehavior
|
||||
let action: (() -> Void)?
|
||||
let longTapAction: ((ASDisplayNode) -> Void)?
|
||||
let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)?
|
||||
|
||||
init(id: AnyHashable, label: String, text: String, textColor: PeerInfoScreenLabeledValueTextColor = .primary, textBehavior: PeerInfoScreenLabeledValueTextBehavior = .singleLine, action: (() -> Void)?) {
|
||||
init(
|
||||
id: AnyHashable,
|
||||
label: String,
|
||||
text: String,
|
||||
textColor: PeerInfoScreenLabeledValueTextColor = .primary,
|
||||
textBehavior: PeerInfoScreenLabeledValueTextBehavior = .singleLine,
|
||||
action: (() -> Void)?,
|
||||
longTapAction: ((ASDisplayNode) -> Void)? = nil,
|
||||
linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil
|
||||
) {
|
||||
self.id = id
|
||||
self.label = label
|
||||
self.text = text
|
||||
self.textColor = textColor
|
||||
self.textBehavior = textBehavior
|
||||
self.action = action
|
||||
self.longTapAction = longTapAction
|
||||
self.linkItemAction = linkItemAction
|
||||
}
|
||||
|
||||
func node() -> PeerInfoScreenItemNode {
|
||||
@ -40,11 +55,15 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
||||
private let textNode: ImmediateTextNode
|
||||
private let bottomSeparatorNode: ASDisplayNode
|
||||
|
||||
private var linkHighlightingNode: LinkHighlightingNode?
|
||||
|
||||
private var item: PeerInfoScreenLabeledValueItem?
|
||||
private var theme: PresentationTheme?
|
||||
|
||||
override init() {
|
||||
var bringToFrontForHighlightImpl: (() -> Void)?
|
||||
self.selectionNode = PeerInfoScreenSelectableBackgroundNode(bringToFrontForHighlight: { bringToFrontForHighlightImpl?() })
|
||||
self.selectionNode.isUserInteractionEnabled = false
|
||||
|
||||
self.labelNode = ImmediateTextNode()
|
||||
self.labelNode.displaysAsynchronously = false
|
||||
@ -69,12 +88,65 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
||||
recognizer.tapActionAtPoint = { [weak self] point in
|
||||
guard let strongSelf = self, let item = strongSelf.item else {
|
||||
return .keepWithSingleTap
|
||||
}
|
||||
if let _ = strongSelf.linkItemAtPoint(point) {
|
||||
return .waitForSingleTap
|
||||
}
|
||||
if item.longTapAction != nil {
|
||||
return .waitForSingleTap
|
||||
}
|
||||
if item.action != nil {
|
||||
return .keepWithSingleTap
|
||||
}
|
||||
return .fail
|
||||
}
|
||||
recognizer.highlight = { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateTouchesAtPoint(point)
|
||||
}
|
||||
self.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
switch gesture {
|
||||
case .tap, .longTap:
|
||||
if let item = self.item {
|
||||
if let linkItem = self.linkItemAtPoint(location) {
|
||||
item.linkItemAction?(gesture == .tap ? .tap : .longTap, linkItem)
|
||||
} else if case .longTap = gesture {
|
||||
item.longTapAction?(self)
|
||||
} else if case .tap = gesture {
|
||||
item.action?()
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func update(width: CGFloat, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
guard let item = item as? PeerInfoScreenLabeledValueItem else {
|
||||
return 10.0
|
||||
}
|
||||
|
||||
self.item = item
|
||||
self.theme = presentationData.theme
|
||||
|
||||
self.selectionNode.pressed = item.action
|
||||
|
||||
@ -95,10 +167,25 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
||||
switch item.textBehavior {
|
||||
case .singleLine:
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
case let .multiLine(maxLines):
|
||||
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue)
|
||||
case let .multiLine(maxLines, enabledEntities):
|
||||
self.textNode.maximumNumberOfLines = maxLines
|
||||
if enabledEntities.isEmpty {
|
||||
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue)
|
||||
} else {
|
||||
let fontSize: CGFloat = 17.0
|
||||
|
||||
var baseFont = Font.regular(fontSize)
|
||||
var linkFont = baseFont
|
||||
var boldFont = Font.medium(fontSize)
|
||||
var italicFont = Font.italic(fontSize)
|
||||
var boldItalicFont = Font.semiboldItalic(fontSize)
|
||||
let titleFixedFont = Font.monospace(fontSize)
|
||||
|
||||
let entities = generateTextEntities(item.text, enabledTypes: enabledEntities)
|
||||
self.textNode.attributedText = stringWithAppliedEntities(item.text, entities: entities, baseColor: textColorValue, linkColor: presentationData.theme.list.itemAccentColor, baseFont: baseFont, linkFont: linkFont, boldFont: boldFont, italicFont: italicFont, boldItalicFont: boldItalicFont, fixedFont: titleFixedFont, blockQuoteFont: baseFont)
|
||||
}
|
||||
}
|
||||
self.textNode.attributedText = NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: textColorValue)
|
||||
|
||||
let labelSize = self.labelNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: width - sideInset * 2.0, height: .greatestFiniteMagnitude))
|
||||
@ -120,4 +207,69 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode {
|
||||
|
||||
return height
|
||||
}
|
||||
|
||||
private func linkItemAtPoint(_ point: CGPoint) -> TextLinkItem? {
|
||||
let textNodeFrame = self.textNode.frame
|
||||
if let (_, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||
if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
return .url(url)
|
||||
} else if let peerName = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.PeerTextMention)] as? String {
|
||||
return .mention(peerName)
|
||||
} else if let hashtag = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.Hashtag)] as? TelegramHashtag {
|
||||
return .hashtag(hashtag.peerName, hashtag.hashtag)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||
guard let item = self.item, let theme = self.theme else {
|
||||
return
|
||||
}
|
||||
var rects: [CGRect]?
|
||||
if let point = point {
|
||||
let textNodeFrame = self.textNode.frame
|
||||
if let (index, attributes) = self.textNode.attributesAtPoint(CGPoint(x: point.x - textNodeFrame.minX, y: point.y - textNodeFrame.minY)) {
|
||||
let possibleNames: [String] = [
|
||||
TelegramTextAttributes.URL,
|
||||
TelegramTextAttributes.PeerMention,
|
||||
TelegramTextAttributes.PeerTextMention,
|
||||
TelegramTextAttributes.BotCommand,
|
||||
TelegramTextAttributes.Hashtag
|
||||
]
|
||||
for name in possibleNames {
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: name)] {
|
||||
rects = self.textNode.attributeRects(name: name, at: index)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let rects = rects {
|
||||
let linkHighlightingNode: LinkHighlightingNode
|
||||
if let current = self.linkHighlightingNode {
|
||||
linkHighlightingNode = current
|
||||
} else {
|
||||
linkHighlightingNode = LinkHighlightingNode(color: theme.list.itemAccentColor.withAlphaComponent(0.5))
|
||||
self.linkHighlightingNode = linkHighlightingNode
|
||||
self.insertSubnode(linkHighlightingNode, belowSubnode: self.textNode)
|
||||
}
|
||||
linkHighlightingNode.frame = self.textNode.frame
|
||||
linkHighlightingNode.updateRects(rects)
|
||||
} else if let linkHighlightingNode = self.linkHighlightingNode {
|
||||
self.linkHighlightingNode = nil
|
||||
linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in
|
||||
linkHighlightingNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
|
||||
if point != nil && rects == nil && item.action != nil {
|
||||
self.selectionNode.updateIsHighlighted(true)
|
||||
} else {
|
||||
self.selectionNode.updateIsHighlighted(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,24 +11,31 @@ import SyncCore
|
||||
import TelegramCore
|
||||
import ItemListUI
|
||||
|
||||
enum PeerInfoScreenMemberItemAction {
|
||||
case open
|
||||
case promote
|
||||
case restrict
|
||||
case remove
|
||||
}
|
||||
|
||||
final class PeerInfoScreenMemberItem: PeerInfoScreenItem {
|
||||
let id: AnyHashable
|
||||
let context: AccountContext
|
||||
let peer: Peer
|
||||
let presence: TelegramUserPresence?
|
||||
let action: (() -> Void)?
|
||||
let enclosingPeer: Peer
|
||||
let member: PeerInfoMember
|
||||
let action: ((PeerInfoScreenMemberItemAction) -> Void)?
|
||||
|
||||
init(
|
||||
id: AnyHashable,
|
||||
context: AccountContext,
|
||||
peer: Peer,
|
||||
presence: TelegramUserPresence?,
|
||||
action: (() -> Void)?
|
||||
enclosingPeer: Peer,
|
||||
member: PeerInfoMember,
|
||||
action: ((PeerInfoScreenMemberItemAction) -> Void)?
|
||||
) {
|
||||
self.id = id
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.presence = presence
|
||||
self.enclosingPeer = enclosingPeer
|
||||
self.member = member
|
||||
self.action = action
|
||||
}
|
||||
|
||||
@ -47,6 +54,7 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode {
|
||||
override init() {
|
||||
var bringToFrontForHighlightImpl: (() -> Void)?
|
||||
self.selectionNode = PeerInfoScreenSelectableBackgroundNode(bringToFrontForHighlight: { bringToFrontForHighlightImpl?() })
|
||||
self.selectionNode.isUserInteractionEnabled = false
|
||||
|
||||
self.bottomSeparatorNode = ASDisplayNode()
|
||||
self.bottomSeparatorNode.isLayerBacked = true
|
||||
@ -61,6 +69,40 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode {
|
||||
self.addSubnode(self.selectionNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
|
||||
recognizer.tapActionAtPoint = { [weak self] point in
|
||||
return .keepWithSingleTap
|
||||
}
|
||||
recognizer.highlight = { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateTouchesAtPoint(point)
|
||||
}
|
||||
self.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
@objc private func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .ended:
|
||||
if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation {
|
||||
switch gesture {
|
||||
case .tap:
|
||||
if let item = self.item {
|
||||
item.action?(.open)
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func update(width: CGFloat, presentationData: PresentationData, item: PeerInfoScreenItem, topItem: PeerInfoScreenItem?, bottomItem: PeerInfoScreenItem?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
guard let item = item as? PeerInfoScreenMemberItem else {
|
||||
return 10.0
|
||||
@ -68,13 +110,50 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode {
|
||||
|
||||
self.item = item
|
||||
|
||||
self.selectionNode.pressed = item.action
|
||||
self.selectionNode.pressed = item.action.flatMap { action in
|
||||
return {
|
||||
action(.open)
|
||||
}
|
||||
}
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
self.bottomSeparatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
|
||||
|
||||
let peerItem = ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: item.context, peer: item.peer, height: .peerList, presence: item.presence, text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: false, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
|
||||
let label: String?
|
||||
if let rank = item.member.rank {
|
||||
label = rank
|
||||
} else {
|
||||
switch item.member.role {
|
||||
case .creator:
|
||||
label = presentationData.strings.GroupInfo_LabelOwner
|
||||
case .admin:
|
||||
label = presentationData.strings.GroupInfo_LabelAdmin
|
||||
case .member:
|
||||
label = nil
|
||||
}
|
||||
}
|
||||
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: item.context.account.peerId, peer: item.enclosingPeer, member: item.member)
|
||||
|
||||
var options: [ItemListPeerItemRevealOption] = []
|
||||
if actions.contains(.promote) && item.enclosingPeer is TelegramChannel {
|
||||
options.append(ItemListPeerItemRevealOption(type: .neutral, title: presentationData.strings.GroupInfo_ActionPromote, action: {
|
||||
item.action?(.promote)
|
||||
}))
|
||||
}
|
||||
if actions.contains(.restrict) {
|
||||
if item.enclosingPeer is TelegramChannel {
|
||||
options.append(ItemListPeerItemRevealOption(type: .warning, title: presentationData.strings.GroupInfo_ActionRestrict, action: {
|
||||
item.action?(.restrict)
|
||||
}))
|
||||
}
|
||||
options.append(ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
|
||||
item.action?(.remove)
|
||||
}))
|
||||
}
|
||||
|
||||
let peerItem = ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: item.context, peer: item.member.peer, height: .peerList, presence: item.member.presence, text: .presence, label: label == nil ? .none : .text(label!, .standard), editing: ItemListPeerItemEditing(editable: !options.isEmpty, editing: false, revealed: nil), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: false, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in
|
||||
|
||||
}, removePeer: { _ in
|
||||
|
||||
@ -103,7 +182,6 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode {
|
||||
apply().1(ListViewItemApply(isOnScreen: true))
|
||||
})
|
||||
itemNode = itemNodeValue as! ItemListPeerItemNode
|
||||
itemNode.isUserInteractionEnabled = false
|
||||
self.itemNode = itemNode
|
||||
self.addSubnode(itemNode)
|
||||
}
|
||||
@ -121,4 +199,15 @@ private final class PeerInfoScreenMemberItemNode: PeerInfoScreenItemNode {
|
||||
|
||||
return height
|
||||
}
|
||||
|
||||
private func updateTouchesAtPoint(_ point: CGPoint?) {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
if point != nil && item.context.account.peerId != item.member.id {
|
||||
self.selectionNode.updateIsHighlighted(true)
|
||||
} else {
|
||||
self.selectionNode.updateIsHighlighted(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ final class PeerInfoScreenSelectableBackgroundNode: ASDisplayNode {
|
||||
|
||||
let bringToFrontForHighlight: () -> Void
|
||||
|
||||
private var isHighlighted: Bool = false
|
||||
|
||||
var pressed: (() -> Void)? {
|
||||
didSet {
|
||||
self.buttonNode.isUserInteractionEnabled = self.pressed != nil
|
||||
@ -30,16 +32,7 @@ final class PeerInfoScreenSelectableBackgroundNode: ASDisplayNode {
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.bringToFrontForHighlight()
|
||||
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.backgroundNode.alpha = 1.0
|
||||
} else {
|
||||
strongSelf.backgroundNode.alpha = 0.0
|
||||
strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
self?.updateIsHighlighted(highlighted)
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,6 +40,20 @@ final class PeerInfoScreenSelectableBackgroundNode: ASDisplayNode {
|
||||
self.pressed?()
|
||||
}
|
||||
|
||||
func updateIsHighlighted(_ isHighlighted: Bool) {
|
||||
if self.isHighlighted != isHighlighted {
|
||||
self.isHighlighted = isHighlighted
|
||||
if isHighlighted {
|
||||
self.bringToFrontForHighlight()
|
||||
self.backgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
self.backgroundNode.alpha = 1.0
|
||||
} else {
|
||||
self.backgroundNode.alpha = 0.0
|
||||
self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(size: CGSize, theme: PresentationTheme, transition: ContainedViewLayoutTransition) {
|
||||
self.backgroundNode.backgroundColor = theme.list.itemHighlightedBackgroundColor
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
@ -19,6 +19,13 @@ private struct PeerMembersListTransaction {
|
||||
let updates: [ListViewUpdateItem]
|
||||
}
|
||||
|
||||
enum PeerMembersListAction {
|
||||
case open
|
||||
case promote
|
||||
case restrict
|
||||
case remove
|
||||
}
|
||||
|
||||
private struct PeerMembersListEntry: Comparable, Identifiable {
|
||||
var index: Int
|
||||
var member: PeerInfoMember
|
||||
@ -35,10 +42,43 @@ private struct PeerMembersListEntry: Comparable, Identifiable {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void) -> ListViewItem {
|
||||
func item(context: AccountContext, presentationData: PresentationData, enclosingPeer: Peer, action: @escaping (PeerInfoMember, PeerMembersListAction) -> Void) -> ListViewItem {
|
||||
let member = self.member
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: member.peer, presence: nil, text: .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: 0, action: {
|
||||
openPeer(member.peer)
|
||||
let label: String?
|
||||
if let rank = member.rank {
|
||||
label = rank
|
||||
} else {
|
||||
switch member.role {
|
||||
case .creator:
|
||||
label = presentationData.strings.GroupInfo_LabelOwner
|
||||
case .admin:
|
||||
label = presentationData.strings.GroupInfo_LabelAdmin
|
||||
case .member:
|
||||
label = nil
|
||||
}
|
||||
}
|
||||
|
||||
let actions = availableActionsForMemberOfPeer(accountPeerId: context.account.peerId, peer: enclosingPeer, member: member)
|
||||
|
||||
var options: [ItemListPeerItemRevealOption] = []
|
||||
if actions.contains(.promote) && enclosingPeer is TelegramChannel{
|
||||
options.append(ItemListPeerItemRevealOption(type: .neutral, title: presentationData.strings.GroupInfo_ActionPromote, action: {
|
||||
action(member, .promote)
|
||||
}))
|
||||
}
|
||||
if actions.contains(.restrict) {
|
||||
if enclosingPeer is TelegramChannel {
|
||||
options.append(ItemListPeerItemRevealOption(type: .warning, title: presentationData.strings.GroupInfo_ActionRestrict, action: {
|
||||
action(member, .restrict)
|
||||
}))
|
||||
}
|
||||
options.append(ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: {
|
||||
action(member, .remove)
|
||||
}))
|
||||
}
|
||||
|
||||
return ItemListPeerItem(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, context: context, peer: member.peer, presence: member.presence, text: .presence, label: label == nil ? .none : .text(label!, .standard), editing: ItemListPeerItemEditing(editable: !options.isEmpty, editing: false, revealed: false), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, selectable: member.id != context.account.peerId, sectionId: 0, action: {
|
||||
action(member, .open)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in
|
||||
}, removePeer: { _ in
|
||||
}, contextAction: nil/*{ node, gesture in
|
||||
@ -47,12 +87,12 @@ private struct PeerMembersListEntry: Comparable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
private func preparedTransition(from fromEntries: [PeerMembersListEntry], to toEntries: [PeerMembersListEntry], context: AccountContext, presentationData: PresentationData, openPeer: @escaping (Peer) -> Void) -> PeerMembersListTransaction {
|
||||
private func preparedTransition(from fromEntries: [PeerMembersListEntry], to toEntries: [PeerMembersListEntry], context: AccountContext, presentationData: PresentationData, enclosingPeer: Peer, action: @escaping (PeerInfoMember, PeerMembersListAction) -> Void) -> PeerMembersListTransaction {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, openPeer: openPeer), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, openPeer: openPeer), directionHint: nil) }
|
||||
let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enclosingPeer: enclosingPeer, action: action), directionHint: nil) }
|
||||
let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, presentationData: presentationData, enclosingPeer: enclosingPeer, action: action), directionHint: nil) }
|
||||
|
||||
return PeerMembersListTransaction(deletions: deletions, insertions: insertions, updates: updates)
|
||||
}
|
||||
@ -60,9 +100,11 @@ private func preparedTransition(from fromEntries: [PeerMembersListEntry], to toE
|
||||
final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
private let context: AccountContext
|
||||
private let membersContext: PeerInfoMembersContext
|
||||
private let action: (PeerInfoMember, PeerMembersListAction) -> Void
|
||||
|
||||
private let listNode: ListView
|
||||
private var currentEntries: [PeerMembersListEntry] = []
|
||||
private var enclosingPeer: Peer?
|
||||
private var currentState: PeerInfoMembersState?
|
||||
private var canLoadMore: Bool = false
|
||||
private var enqueuedTransactions: [PeerMembersListTransaction] = []
|
||||
@ -77,9 +119,10 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
|
||||
private var disposable: Disposable?
|
||||
|
||||
init(context: AccountContext, membersContext: PeerInfoMembersContext) {
|
||||
init(context: AccountContext, peerId: PeerId, membersContext: PeerInfoMembersContext, action: @escaping (PeerInfoMember, PeerMembersListAction) -> Void) {
|
||||
self.context = context
|
||||
self.membersContext = membersContext
|
||||
self.action = action
|
||||
|
||||
self.listNode = ListView()
|
||||
|
||||
@ -88,14 +131,19 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
self.listNode.preloadPages = true
|
||||
self.addSubnode(self.listNode)
|
||||
|
||||
self.disposable = (membersContext.state
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state in
|
||||
guard let strongSelf = self else {
|
||||
self.disposable = (combineLatest(queue: .mainQueue(),
|
||||
membersContext.state,
|
||||
context.account.postbox.combinedView(keys: [.basicPeer(peerId)])
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] state, combinedView in
|
||||
guard let strongSelf = self, let basicPeerView = combinedView.views[.basicPeer(peerId)] as? BasicPeerView, let enclosingPeer = basicPeerView.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.enclosingPeer = enclosingPeer
|
||||
strongSelf.currentState = state
|
||||
if let (_, _, presentationData) = strongSelf.currentParams {
|
||||
strongSelf.updateState(state: state, presentationData: presentationData)
|
||||
strongSelf.updateState(enclosingPeer: enclosingPeer, state: state, presentationData: presentationData)
|
||||
}
|
||||
})
|
||||
|
||||
@ -132,19 +180,20 @@ final class PeerInfoMembersPaneNode: ASDisplayNode, PeerInfoPaneNode {
|
||||
|
||||
self.listNode.scrollEnabled = !isScrollingLockedAtTop
|
||||
|
||||
if isFirstLayout, let state = self.currentState {
|
||||
self.updateState(state: state, presentationData: presentationData)
|
||||
if isFirstLayout, let enclosingPeer = self.enclosingPeer, let state = self.currentState {
|
||||
self.updateState(enclosingPeer: enclosingPeer, state: state, presentationData: presentationData)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateState(state: PeerInfoMembersState, presentationData: PresentationData) {
|
||||
private func updateState(enclosingPeer: Peer, state: PeerInfoMembersState, presentationData: PresentationData) {
|
||||
var entries: [PeerMembersListEntry] = []
|
||||
for member in state.members {
|
||||
entries.append(PeerMembersListEntry(index: entries.count, member: member))
|
||||
}
|
||||
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, openPeer: { [weak self] peer in
|
||||
|
||||
let transaction = preparedTransition(from: self.currentEntries, to: entries, context: self.context, presentationData: presentationData, enclosingPeer: enclosingPeer, action: { [weak self] member, action in
|
||||
self?.action(member, action)
|
||||
})
|
||||
self.enclosingPeer = enclosingPeer
|
||||
self.currentEntries = entries
|
||||
self.enqueuedTransactions.append(transaction)
|
||||
self.dequeueTransaction()
|
||||
|
@ -8,6 +8,7 @@ import AccountContext
|
||||
import PeerPresenceStatusManager
|
||||
import TelegramStringFormatting
|
||||
import TelegramPresentationData
|
||||
import PeerAvatarGalleryUI
|
||||
|
||||
enum PeerInfoUpdatingAvatar {
|
||||
case none
|
||||
@ -91,17 +92,17 @@ final class PeerInfoScreenData {
|
||||
}
|
||||
}
|
||||
|
||||
enum PeerInfoScreenInputUserKind {
|
||||
private enum PeerInfoScreenInputUserKind {
|
||||
case user
|
||||
case bot
|
||||
case support
|
||||
}
|
||||
|
||||
enum PeerInfoScreenInputData: Equatable {
|
||||
private enum PeerInfoScreenInputData: Equatable {
|
||||
case none
|
||||
case user(userId: PeerId, secretChatId: PeerId?, kind: PeerInfoScreenInputUserKind)
|
||||
case channel
|
||||
case group(isSupergroup: Bool, membersContext: PeerInfoMembersContext)
|
||||
case group(groupId: PeerId)
|
||||
}
|
||||
|
||||
func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<[PeerInfoPaneKey], NoError> {
|
||||
@ -148,11 +149,20 @@ struct PeerInfoStatusData: Equatable {
|
||||
}
|
||||
|
||||
enum PeerInfoMembersData: Equatable {
|
||||
case shortList([PeerInfoMember])
|
||||
case shortList(membersContext: PeerInfoMembersContext, members: [PeerInfoMember])
|
||||
case longList(PeerInfoMembersContext)
|
||||
|
||||
var membersContext: PeerInfoMembersContext {
|
||||
switch self {
|
||||
case let .shortList(shortList):
|
||||
return shortList.membersContext
|
||||
case let .longList(membersContext):
|
||||
return membersContext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> Signal<PeerInfoScreenData, NoError> {
|
||||
private func peerInfoScreenInputData(context: AccountContext, peerId: PeerId) -> Signal<PeerInfoScreenInputData, NoError> {
|
||||
return context.account.postbox.combinedView(keys: [.basicPeer(peerId)])
|
||||
|> map { view -> PeerInfoScreenInputData in
|
||||
guard let peer = (view.views[.basicPeer(peerId)] as? BasicPeerView)?.peer else {
|
||||
@ -170,17 +180,68 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
return .user(userId: user.id, secretChatId: nil, kind: kind)
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if case .group = channel.info {
|
||||
return .group(isSupergroup: true, membersContext: PeerInfoMembersContext(context: context, peerId: channel.id))
|
||||
return .group(groupId: channel.id)
|
||||
} else {
|
||||
return .channel
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
return .group(isSupergroup: false, membersContext: PeerInfoMembersContext(context: context, peerId: group.id))
|
||||
return .group(groupId: group.id)
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
|
||||
private func peerInfoProfilePhotos(context: AccountContext, peerId: PeerId) -> Signal<Any, NoError> {
|
||||
return context.account.postbox.combinedView(keys: [.basicPeer(peerId)])
|
||||
|> map { view -> AvatarGalleryEntry? in
|
||||
guard let peer = (view.views[.basicPeer(peerId)] as? BasicPeerView)?.peer else {
|
||||
return nil
|
||||
}
|
||||
return initialAvatarGalleryEntries(peer: peer).first
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> mapToSignal { firstEntry -> Signal<[AvatarGalleryEntry], NoError> in
|
||||
if let firstEntry = firstEntry {
|
||||
return context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> mapToSignal { peer -> Signal<[AvatarGalleryEntry], NoError>in
|
||||
return fetchedAvatarGalleryEntries(account: context.account, peer: peer, firstEntry: firstEntry)
|
||||
}
|
||||
} else {
|
||||
return .single([])
|
||||
}
|
||||
}
|
||||
|> map { items -> Any in
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: PeerId) -> Signal<[AvatarGalleryEntry], NoError> {
|
||||
return context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId))
|
||||
|> map { items -> [AvatarGalleryEntry] in
|
||||
return items as? [AvatarGalleryEntry] ?? []
|
||||
}
|
||||
}
|
||||
|
||||
func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId) -> Signal<Never, NoError> {
|
||||
return peerInfoScreenInputData(context: context, peerId: peerId)
|
||||
|> mapToSignal { inputData -> Signal<Never, NoError> in
|
||||
switch inputData {
|
||||
case .none:
|
||||
return .complete()
|
||||
case .user, .channel, .group:
|
||||
return combineLatest(
|
||||
context.peerChannelMemberCategoriesContextsManager.profileData(postbox: context.account.postbox, network: context.account.network, peerId: peerId, customData: peerInfoAvailableMediaPanes(context: context, peerId: peerId) |> ignoreValues),
|
||||
context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId)) |> ignoreValues
|
||||
)
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> Signal<PeerInfoScreenData, NoError> {
|
||||
return peerInfoScreenInputData(context: context, peerId: peerId)
|
||||
|> mapToSignal { inputData -> Signal<PeerInfoScreenData, NoError> in
|
||||
switch inputData {
|
||||
case .none:
|
||||
@ -382,10 +443,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
members: nil
|
||||
)
|
||||
}
|
||||
case let .group(_, membersContext):
|
||||
let status = context.account.viewTracker.peerView(peerId, updateData: false)
|
||||
case let .group(groupId):
|
||||
let status = context.account.viewTracker.peerView(groupId, updateData: false)
|
||||
|> map { peerView -> PeerInfoStatusData? in
|
||||
guard let channel = peerView.peers[peerId] as? TelegramChannel else {
|
||||
guard let channel = peerView.peers[groupId] as? TelegramChannel else {
|
||||
return PeerInfoStatusData(text: strings.Channel_Status, isActivity: false)
|
||||
}
|
||||
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount != 0 {
|
||||
@ -396,12 +457,14 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
let membersContext = PeerInfoMembersContext(context: context, peerId: groupId)
|
||||
|
||||
let membersData: Signal<PeerInfoMembersData?, NoError> = membersContext.state
|
||||
|> map { state -> PeerInfoMembersData? in
|
||||
if state.members.count > 5 {
|
||||
return .longList(membersContext)
|
||||
} else {
|
||||
return .shortList(state.members)
|
||||
return .shortList(membersContext: membersContext, members: state.members)
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
@ -409,9 +472,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set<ValueBoxKey>([PreferencesKeys.globalNotifications]))
|
||||
var combinedKeys: [PostboxViewKey] = []
|
||||
combinedKeys.append(globalNotificationsKey)
|
||||
return combineLatest(
|
||||
context.account.viewTracker.peerView(peerId, updateData: true),
|
||||
peerInfoAvailableMediaPanes(context: context, peerId: peerId),
|
||||
return combineLatest(queue: .mainQueue(),
|
||||
context.account.viewTracker.peerView(groupId, updateData: true),
|
||||
peerInfoAvailableMediaPanes(context: context, peerId: groupId),
|
||||
context.account.postbox.combinedView(keys: combinedKeys),
|
||||
status,
|
||||
membersData
|
||||
@ -435,7 +498,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
|
||||
return PeerInfoScreenData(
|
||||
peer: peerView.peers[peerId],
|
||||
peer: peerView.peers[groupId],
|
||||
cachedData: peerView.cachedData,
|
||||
status: status,
|
||||
notificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings,
|
||||
@ -470,6 +533,80 @@ func canEditPeerInfo(peer: Peer?) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
struct PeerInfoMemberActions: OptionSet {
|
||||
var rawValue: Int32
|
||||
|
||||
init(rawValue: Int32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
static let restrict = PeerInfoMemberActions(rawValue: 1 << 0)
|
||||
static let promote = PeerInfoMemberActions(rawValue: 1 << 1)
|
||||
}
|
||||
|
||||
func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: Peer, member: PeerInfoMember) -> PeerInfoMemberActions {
|
||||
var result: PeerInfoMemberActions = []
|
||||
|
||||
if member.id != accountPeerId {
|
||||
if let channel = peer as? TelegramChannel {
|
||||
if channel.flags.contains(.isCreator) {
|
||||
result.insert(.restrict)
|
||||
result.insert(.promote)
|
||||
} else {
|
||||
switch member {
|
||||
case let .channelMember(channelMember):
|
||||
switch channelMember.participant {
|
||||
case .creator:
|
||||
break
|
||||
case let .member(member):
|
||||
if let adminInfo = member.adminInfo {
|
||||
if adminInfo.promotedBy == accountPeerId {
|
||||
result.insert(.restrict)
|
||||
if channel.hasPermission(.addAdmins) {
|
||||
result.insert(.promote)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if channel.hasPermission(.banMembers) {
|
||||
result.insert(.restrict)
|
||||
}
|
||||
}
|
||||
}
|
||||
case .legacyGroupMember:
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
switch group.role {
|
||||
case .creator:
|
||||
result.insert(.restrict)
|
||||
result.insert(.promote)
|
||||
case .admin:
|
||||
switch member {
|
||||
case let .legacyGroupMember(legacyGroupMember):
|
||||
if legacyGroupMember.invitedBy == accountPeerId {
|
||||
result.insert(.restrict)
|
||||
result.insert(.promote)
|
||||
}
|
||||
case .channelMember:
|
||||
break
|
||||
}
|
||||
case .member:
|
||||
switch member {
|
||||
case let .legacyGroupMember(legacyGroupMember):
|
||||
if legacyGroupMember.invitedBy == accountPeerId {
|
||||
result.insert(.restrict)
|
||||
}
|
||||
case .channelMember:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?) -> [PeerInfoHeaderButtonKey] {
|
||||
var result: [PeerInfoHeaderButtonKey] = []
|
||||
if let user = peer as? TelegramUser {
|
||||
|
@ -168,6 +168,7 @@ final class PeerInfoAvatarListItemNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.imageNode.contentAnimations = .subsequentUpdates
|
||||
self.addSubnode(self.imageNode)
|
||||
|
||||
self.imageNode.imageUpdated = { [weak self] _ in
|
||||
@ -420,7 +421,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
self.updateItems(size: size, transition: .immediate)
|
||||
} else if self.items.count > 1 {
|
||||
self.currentIndex = self.items.count - 1
|
||||
self.updateItems(size: size, transition: .immediate)
|
||||
self.updateItems(size: size, transition: .immediate, synchronous: true)
|
||||
}
|
||||
} else if location.x > size.width * 4.0 / 5.0 {
|
||||
if self.currentIndex < self.items.count - 1 {
|
||||
@ -486,7 +487,7 @@ final class PeerInfoAvatarListContainerNode: ASDisplayNode {
|
||||
|
||||
if let peer = peer, !self.initializedList {
|
||||
self.initializedList = true
|
||||
self.disposable.set((fetchedAvatarGalleryEntries(account: self.context.account, peer: peer)
|
||||
self.disposable.set((peerInfoProfilePhotosWithCache(context: self.context, peerId: peer.id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] entries in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -1225,6 +1226,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
private var context: AccountContext
|
||||
private var presentationData: PresentationData?
|
||||
|
||||
private let keepExpandedButtons: PeerInfoScreenKeepExpandedButtons
|
||||
|
||||
private(set) var isAvatarExpanded: Bool
|
||||
|
||||
let avatarListNode: PeerInfoAvatarListNode
|
||||
@ -1250,9 +1253,10 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
|
||||
var navigationTransition: PeerInfoHeaderNavigationTransition?
|
||||
|
||||
init(context: AccountContext, avatarInitiallyExpanded: Bool) {
|
||||
init(context: AccountContext, avatarInitiallyExpanded: Bool, keepExpandedButtons: PeerInfoScreenKeepExpandedButtons) {
|
||||
self.context = context
|
||||
self.isAvatarExpanded = avatarInitiallyExpanded
|
||||
self.keepExpandedButtons = keepExpandedButtons
|
||||
|
||||
self.avatarListNode = PeerInfoAvatarListNode(context: context, readyWhenGalleryLoads: avatarInitiallyExpanded)
|
||||
|
||||
@ -1528,7 +1532,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
transition.updateFrameAdditive(node: self.avatarListNode.listContainerNode.controlsContainerNode, frame: CGRect(origin: CGPoint(x: -controlsClippingFrame.minX, y: -controlsClippingFrame.minY), size: CGSize(width: expandedAvatarListSize.width, height: expandedAvatarListSize.height)))
|
||||
|
||||
transition.updateFrame(node: self.avatarListNode.listContainerNode.shadowNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: expandedAvatarListSize.width, height: navigationHeight + 20.0)))
|
||||
transition.updateFrame(node: self.avatarListNode.listContainerNode.stripContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusBarHeight + 2.0), size: CGSize(width: expandedAvatarListSize.width, height: 2.0)))
|
||||
transition.updateFrame(node: self.avatarListNode.listContainerNode.stripContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusBarHeight < 25.0 ? (statusBarHeight + 2.0) : (statusBarHeight - 3.0)), size: CGSize(width: expandedAvatarListSize.width, height: 2.0)))
|
||||
transition.updateFrame(node: self.avatarListNode.listContainerNode.highlightContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: expandedAvatarListSize.width, height: expandedAvatarListSize.height)))
|
||||
transition.updateAlpha(node: self.avatarListNode.listContainerNode.controlsContainerNode, alpha: self.isAvatarExpanded ? (1.0 - transitionFraction) : 0.0)
|
||||
|
||||
@ -1690,7 +1694,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
buttonText = "Message"
|
||||
buttonIcon = .message
|
||||
case .discussion:
|
||||
buttonText = "Discussion"
|
||||
buttonText = "Discuss"
|
||||
buttonIcon = .message
|
||||
case .call:
|
||||
buttonText = "Call"
|
||||
@ -1716,11 +1720,21 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
transition.updateAlpha(node: buttonNode, alpha: buttonsAlpha)
|
||||
|
||||
let hiddenWhileExpanded: Bool
|
||||
switch buttonKey {
|
||||
switch self.keepExpandedButtons {
|
||||
case .message:
|
||||
switch buttonKey {
|
||||
case .mute, .addMember:
|
||||
hiddenWhileExpanded = true
|
||||
default:
|
||||
hiddenWhileExpanded = false
|
||||
}
|
||||
case .mute:
|
||||
hiddenWhileExpanded = true
|
||||
default:
|
||||
hiddenWhileExpanded = false
|
||||
switch buttonKey {
|
||||
case .message, .addMember:
|
||||
hiddenWhileExpanded = true
|
||||
default:
|
||||
hiddenWhileExpanded = false
|
||||
}
|
||||
}
|
||||
|
||||
if self.isAvatarExpanded, hiddenWhileExpanded {
|
||||
|
@ -6,20 +6,31 @@ import TelegramCore
|
||||
import AccountContext
|
||||
import TemporaryCachedPeerDataManager
|
||||
|
||||
enum PeerInfoMemberRole {
|
||||
case creator
|
||||
case admin
|
||||
case member
|
||||
}
|
||||
|
||||
enum PeerInfoMember: Equatable {
|
||||
case channelMember(RenderedChannelParticipant)
|
||||
case legacyGroupMember(peer: RenderedPeer, role: PeerInfoMemberRole, invitedBy: PeerId?, presence: TelegramUserPresence?)
|
||||
|
||||
var id: PeerId {
|
||||
switch self {
|
||||
case let .channelMember(channelMember):
|
||||
return channelMember.peer.id
|
||||
case let .legacyGroupMember(legacyGroupMember):
|
||||
return legacyGroupMember.peer.peerId
|
||||
}
|
||||
}
|
||||
|
||||
var peer: Peer {
|
||||
switch self {
|
||||
case let .channelMember(channelMember):
|
||||
case let .channelMember(channelMember):
|
||||
return channelMember.peer
|
||||
case let .legacyGroupMember(legacyGroupMember):
|
||||
return legacyGroupMember.peer.peers[legacyGroupMember.peer.peerId]!
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +38,40 @@ enum PeerInfoMember: Equatable {
|
||||
switch self {
|
||||
case let .channelMember(channelMember):
|
||||
return channelMember.presences[channelMember.peer.id] as? TelegramUserPresence
|
||||
case let .legacyGroupMember(legacyGroupMember):
|
||||
return legacyGroupMember.presence
|
||||
}
|
||||
}
|
||||
|
||||
var role: PeerInfoMemberRole {
|
||||
switch self {
|
||||
case let .channelMember(channelMember):
|
||||
switch channelMember.participant {
|
||||
case .creator:
|
||||
return .creator
|
||||
case let .member(member):
|
||||
if member.adminInfo != nil {
|
||||
return .admin
|
||||
} else {
|
||||
return .member
|
||||
}
|
||||
}
|
||||
case let .legacyGroupMember(legacyGroupMember):
|
||||
return legacyGroupMember.role
|
||||
}
|
||||
}
|
||||
|
||||
var rank: String? {
|
||||
switch self {
|
||||
case let .channelMember(channelMember):
|
||||
switch channelMember.participant {
|
||||
case let .creator(creator):
|
||||
return creator.rank
|
||||
case let .member(member):
|
||||
return member.rank
|
||||
}
|
||||
case .legacyGroupMember:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -41,6 +86,31 @@ struct PeerInfoMembersState: Equatable {
|
||||
var dataState: PeerInfoMembersDataState
|
||||
}
|
||||
|
||||
private func membersSortedByPresence(_ members: [PeerInfoMember], accountPeerId: PeerId) -> [PeerInfoMember] {
|
||||
return members.sorted(by: { lhs, rhs in
|
||||
if lhs.id == accountPeerId {
|
||||
return true
|
||||
} else if rhs.id == accountPeerId {
|
||||
return false
|
||||
}
|
||||
|
||||
let lhsPresence = lhs.presence
|
||||
let rhsPresence = rhs.presence
|
||||
if let lhsPresence = lhsPresence, let rhsPresence = rhsPresence {
|
||||
if lhsPresence.status < rhsPresence.status {
|
||||
return false
|
||||
} else if lhsPresence.status > rhsPresence.status {
|
||||
return true
|
||||
}
|
||||
} else if let _ = lhsPresence {
|
||||
return true
|
||||
} else if let _ = rhsPresence {
|
||||
return false
|
||||
}
|
||||
return lhs.id < rhs.id
|
||||
})
|
||||
}
|
||||
|
||||
private final class PeerInfoMembersContextImpl {
|
||||
private let queue: Queue
|
||||
private let context: AccountContext
|
||||
@ -48,6 +118,7 @@ private final class PeerInfoMembersContextImpl {
|
||||
|
||||
private var members: [PeerInfoMember] = []
|
||||
private var dataState: PeerInfoMembersDataState = .loading(isInitial: true)
|
||||
private var removingMemberIds: [PeerId: Disposable] = [:]
|
||||
|
||||
private let stateValue = Promise<PeerInfoMembersState>()
|
||||
var state: Signal<PeerInfoMembersState, NoError> {
|
||||
@ -70,7 +141,14 @@ private final class PeerInfoMembersContextImpl {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.members = state.list.map(PeerInfoMember.channelMember)
|
||||
let unsortedMembers = state.list.map(PeerInfoMember.channelMember)
|
||||
let members: [PeerInfoMember]
|
||||
if unsortedMembers.count <= 50 {
|
||||
members = membersSortedByPresence(unsortedMembers, accountPeerId: strongSelf.context.account.peerId)
|
||||
} else {
|
||||
members = unsortedMembers
|
||||
}
|
||||
strongSelf.members = members
|
||||
switch state.loadingState {
|
||||
case let .loading(initial):
|
||||
strongSelf.dataState = .loading(isInitial: initial)
|
||||
@ -88,12 +166,26 @@ private final class PeerInfoMembersContextImpl {
|
||||
guard let strongSelf = self, let cachedData = view.cachedData as? CachedGroupData, let participantsData = cachedData.participants else {
|
||||
return
|
||||
}
|
||||
var members: [PeerInfoMember] = []
|
||||
var unsortedMembers: [PeerInfoMember] = []
|
||||
for participant in participantsData.participants {
|
||||
if let peer = view.peers[participant.peerId] {
|
||||
|
||||
let role: PeerInfoMemberRole
|
||||
let invitedBy: PeerId?
|
||||
switch participant {
|
||||
case .creator:
|
||||
role = .creator
|
||||
invitedBy = nil
|
||||
case let .admin(admin):
|
||||
role = .admin
|
||||
invitedBy = admin.invitedBy
|
||||
case let .member(member):
|
||||
role = .member
|
||||
invitedBy = member.invitedBy
|
||||
}
|
||||
unsortedMembers.append(.legacyGroupMember(peer: RenderedPeer(peer: peer), role: role, invitedBy: invitedBy, presence: view.peerPresences[participant.peerId] as? TelegramUserPresence))
|
||||
}
|
||||
}
|
||||
strongSelf.members = membersSortedByPresence(unsortedMembers, accountPeerId: strongSelf.context.account.peerId)
|
||||
strongSelf.dataState = .ready(canLoadMore: false)
|
||||
strongSelf.pushState()
|
||||
}))
|
||||
@ -108,7 +200,13 @@ private final class PeerInfoMembersContextImpl {
|
||||
}
|
||||
|
||||
private func pushState() {
|
||||
self.stateValue.set(.single(PeerInfoMembersState(members: self.members, dataState: self.dataState)))
|
||||
if self.removingMemberIds.isEmpty {
|
||||
self.stateValue.set(.single(PeerInfoMembersState(members: self.members, dataState: self.dataState)))
|
||||
} else {
|
||||
self.stateValue.set(.single(PeerInfoMembersState(members: self.members.filter { member in
|
||||
return self.removingMemberIds[member.id] == nil
|
||||
}, dataState: self.dataState)))
|
||||
}
|
||||
}
|
||||
|
||||
func loadMore() {
|
||||
@ -116,6 +214,38 @@ private final class PeerInfoMembersContextImpl {
|
||||
self.context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: self.peerId, control: channelMembersControl)
|
||||
}
|
||||
}
|
||||
|
||||
func removeMember(memberId: PeerId) {
|
||||
if removingMemberIds[memberId] == nil {
|
||||
let signal: Signal<Never, NoError>
|
||||
if self.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
signal = context.peerChannelMemberCategoriesContextsManager.updateMemberBannedRights(account: self.context.account, peerId: self.peerId, memberId: memberId, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: Int32.max))
|
||||
|> ignoreValues
|
||||
} else {
|
||||
signal = removePeerMember(account: self.context.account, peerId: self.peerId, memberId: memberId)
|
||||
|> ignoreValues
|
||||
}
|
||||
let completed: () -> Void = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let _ = strongSelf.removingMemberIds.removeValue(forKey: memberId) {
|
||||
strongSelf.pushState()
|
||||
}
|
||||
}
|
||||
let disposable = MetaDisposable()
|
||||
self.removingMemberIds[memberId] = disposable
|
||||
|
||||
self.pushState()
|
||||
|
||||
disposable.set((signal
|
||||
|> deliverOn(self.queue)).start(error: { _ in
|
||||
completed()
|
||||
}, completed: {
|
||||
completed()
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerInfoMembersContext: Equatable {
|
||||
@ -147,6 +277,12 @@ final class PeerInfoMembersContext: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
func removeMember(memberId: PeerId) {
|
||||
self.impl.with { impl in
|
||||
impl.removeMember(memberId: memberId)
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerInfoMembersContext, rhs: PeerInfoMembersContext) -> Bool {
|
||||
return lhs === rhs
|
||||
}
|
||||
|
@ -60,6 +60,8 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
|
||||
private var isSelected: Bool = false
|
||||
|
||||
init(pressed: @escaping () -> Void) {
|
||||
self.pressed = pressed
|
||||
|
||||
@ -74,9 +76,9 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
/*self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
if highlighted && !strongSelf.isSelected {
|
||||
strongSelf.titleNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.titleNode.alpha = 0.4
|
||||
} else {
|
||||
@ -84,7 +86,7 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
||||
strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
@ -92,6 +94,7 @@ final class PeerInfoPaneTabsContainerPaneNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func updateText(_ title: String, isSelected: Bool, presentationData: PresentationData) {
|
||||
self.isSelected = isSelected
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: isSelected ? presentationData.theme.list.itemAccentColor : presentationData.theme.list.itemSecondaryTextColor)
|
||||
}
|
||||
|
||||
@ -304,8 +307,10 @@ final class PeerInfoPaneContainerNode: ASDisplayNode {
|
||||
|
||||
var chatControllerInteraction: ChatControllerInteraction?
|
||||
var openPeerContextAction: ((Peer, ASDisplayNode, ContextGesture?) -> Void)?
|
||||
var requestPerformPeerMemberAction: ((PeerInfoMember, PeerMembersListAction) -> Void)?
|
||||
|
||||
var currentPaneUpdated: (() -> Void)?
|
||||
var requestExpandTabs: (() -> Bool)?
|
||||
|
||||
private var currentAvailablePanes: [PeerInfoPaneKey]?
|
||||
|
||||
@ -336,7 +341,10 @@ final class PeerInfoPaneContainerNode: ASDisplayNode {
|
||||
return
|
||||
}
|
||||
if strongSelf.currentPaneKey == key {
|
||||
strongSelf.currentPane?.node.scrollToTop()
|
||||
if let requestExpandTabs = strongSelf.requestExpandTabs, requestExpandTabs() {
|
||||
} else {
|
||||
strongSelf.currentPane?.node.scrollToTop()
|
||||
}
|
||||
return
|
||||
}
|
||||
if strongSelf.currentCandidatePaneKey == key {
|
||||
@ -449,10 +457,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode {
|
||||
case .music:
|
||||
paneNode = PeerInfoListPaneNode(context: self.context, chatControllerInteraction: self.chatControllerInteraction!, peerId: self.peerId, tagMask: .music)
|
||||
case .groupsInCommon:
|
||||
paneNode = PeerInfoGroupsInCommonPaneNode(context: self.context, peerId: peerId, chatControllerInteraction: self.chatControllerInteraction!, openPeerContextAction: self.openPeerContextAction!, peers: data?.groupsInCommon ?? [])
|
||||
paneNode = PeerInfoGroupsInCommonPaneNode(context: self.context, peerId: self.peerId, chatControllerInteraction: self.chatControllerInteraction!, openPeerContextAction: self.openPeerContextAction!, peers: data?.groupsInCommon ?? [])
|
||||
case .members:
|
||||
if case let .longList(membersContext) = data?.members {
|
||||
paneNode = PeerInfoMembersPaneNode(context: self.context, membersContext: membersContext)
|
||||
paneNode = PeerInfoMembersPaneNode(context: self.context, peerId: self.peerId, membersContext: membersContext, action: { [weak self] member, action in
|
||||
self?.requestPerformPeerMemberAction?(member, action)
|
||||
})
|
||||
} else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import WebSearchUI
|
||||
import LocationResources
|
||||
import LocationUI
|
||||
import Geocoding
|
||||
import TextFormat
|
||||
|
||||
protocol PeerInfoScreenItem: class {
|
||||
var id: AnyHashable { get }
|
||||
@ -53,6 +54,7 @@ private final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topSeparatorNode: ASDisplayNode
|
||||
private let bottomSeparatorNode: ASDisplayNode
|
||||
private let itemContainerNode: ASDisplayNode
|
||||
|
||||
private var currentItems: [PeerInfoScreenItem] = []
|
||||
private var itemNodes: [AnyHashable: PeerInfoScreenItemNode] = [:]
|
||||
@ -67,9 +69,13 @@ private final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode {
|
||||
self.bottomSeparatorNode = ASDisplayNode()
|
||||
self.bottomSeparatorNode.isLayerBacked = true
|
||||
|
||||
self.itemContainerNode = ASDisplayNode()
|
||||
self.itemContainerNode.clipsToBounds = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.itemContainerNode)
|
||||
self.addSubnode(self.topSeparatorNode)
|
||||
self.addSubnode(self.bottomSeparatorNode)
|
||||
}
|
||||
@ -94,7 +100,7 @@ private final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode {
|
||||
wasAdded = true
|
||||
itemNode = item.node()
|
||||
self.itemNodes[item.id] = itemNode
|
||||
self.addSubnode(itemNode)
|
||||
self.itemContainerNode.addSubnode(itemNode)
|
||||
itemNode.bringToFrontForHighlight = { [weak self, weak itemNode] in
|
||||
guard let strongSelf = self, let itemNode = itemNode else {
|
||||
return
|
||||
@ -150,12 +156,14 @@ private final class PeerInfoScreenItemSectionContainerNode: ASDisplayNode {
|
||||
}
|
||||
for id in removeIds {
|
||||
if let itemNode = self.itemNodes.removeValue(forKey: id) {
|
||||
itemNode.view.superview?.sendSubviewToBack(itemNode.view)
|
||||
transition.updateAlpha(node: itemNode, alpha: 0.0, completion: { [weak itemNode] _ in
|
||||
itemNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.itemContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: contentHeight)))
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentWithBackgroundOffset), size: CGSize(width: width, height: max(0.0, contentWithBackgroundHeight - contentWithBackgroundOffset))))
|
||||
transition.updateFrame(node: self.topSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentWithBackgroundOffset - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel)))
|
||||
transition.updateFrame(node: self.bottomSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: contentWithBackgroundHeight), size: CGSize(width: width, height: UIScreenPixel)))
|
||||
@ -445,6 +453,18 @@ private enum PeerInfoParticipantsSection {
|
||||
case banned
|
||||
}
|
||||
|
||||
private enum PeerInfoMemberAction {
|
||||
case promote
|
||||
case restrict
|
||||
case remove
|
||||
}
|
||||
|
||||
private enum PeerInfoContextSubject {
|
||||
case bio
|
||||
case phone(String)
|
||||
case link
|
||||
}
|
||||
|
||||
private final class PeerInfoInteraction {
|
||||
let openUsername: (String) -> Void
|
||||
let openPhone: (String) -> Void
|
||||
@ -468,6 +488,9 @@ private final class PeerInfoInteraction {
|
||||
let openLocation: () -> Void
|
||||
let editingOpenSetupLocation: () -> Void
|
||||
let openPeerInfo: (Peer) -> Void
|
||||
let performMemberAction: (PeerInfoMember, PeerInfoMemberAction) -> Void
|
||||
let openPeerInfoContextMenu: (PeerInfoContextSubject, ASDisplayNode) -> Void
|
||||
let performBioLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void
|
||||
|
||||
init(
|
||||
openUsername: @escaping (String) -> Void,
|
||||
@ -491,7 +514,10 @@ private final class PeerInfoInteraction {
|
||||
editingOpenStickerPackSetup: @escaping () -> Void,
|
||||
openLocation: @escaping () -> Void,
|
||||
editingOpenSetupLocation: @escaping () -> Void,
|
||||
openPeerInfo: @escaping (Peer) -> Void
|
||||
openPeerInfo: @escaping (Peer) -> Void,
|
||||
performMemberAction: @escaping (PeerInfoMember, PeerInfoMemberAction) -> Void,
|
||||
openPeerInfoContextMenu: @escaping (PeerInfoContextSubject, ASDisplayNode) -> Void,
|
||||
performBioLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void
|
||||
) {
|
||||
self.openUsername = openUsername
|
||||
self.openPhone = openPhone
|
||||
@ -515,9 +541,14 @@ private final class PeerInfoInteraction {
|
||||
self.openLocation = openLocation
|
||||
self.editingOpenSetupLocation = editingOpenSetupLocation
|
||||
self.openPeerInfo = openPeerInfo
|
||||
self.performMemberAction = performMemberAction
|
||||
self.openPeerInfoContextMenu = openPeerInfoContextMenu
|
||||
self.performBioLinkAction = performBioLinkAction
|
||||
}
|
||||
}
|
||||
|
||||
private let enabledBioEntities: EnabledEntityTypes = [.url, .mention, .hashtag]
|
||||
|
||||
private func infoItems(data: PeerInfoScreenData?, context: AccountContext, presentationData: PresentationData, interaction: PeerInfoInteraction) -> [(AnyHashable, [PeerInfoScreenItem])] {
|
||||
guard let data = data else {
|
||||
return []
|
||||
@ -534,22 +565,34 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
items[section] = []
|
||||
}
|
||||
|
||||
let bioContextAction: (ASDisplayNode) -> Void = { sourceNode in
|
||||
interaction.openPeerInfoContextMenu(.bio, sourceNode)
|
||||
}
|
||||
let bioLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void = { action, item in
|
||||
interaction.performBioLinkAction(action, item)
|
||||
}
|
||||
|
||||
if let user = data.peer as? TelegramUser {
|
||||
if let phone = user.phone {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 2, label: "mobile", text: "\(formatPhoneNumber(phone))", textColor: .accent, action: {
|
||||
let formattedPhone = formatPhoneNumber(phone)
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 2, label: "mobile", text: formattedPhone, textColor: .accent, action: {
|
||||
interaction.openPhone(phone)
|
||||
}, longTapAction: { sourceNode in
|
||||
interaction.openPeerInfoContextMenu(.phone(formattedPhone), sourceNode)
|
||||
}))
|
||||
}
|
||||
if let username = user.username {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 1, label: "username", text: "@\(username)", textColor: .accent, action: {
|
||||
interaction.openUsername(username)
|
||||
}, longTapAction: { sourceNode in
|
||||
interaction.openPeerInfoContextMenu(.link, sourceNode)
|
||||
}))
|
||||
}
|
||||
if let cachedData = data.cachedData as? CachedUserData {
|
||||
if user.isScam {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 10), action: nil))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: user.botInfo != nil ? presentationData.strings.UserInfo_ScamBotWarning : presentationData.strings.UserInfo_ScamUserWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: user.botInfo != nil ? enabledBioEntities : []), action: nil))
|
||||
} else if let about = cachedData.about, !about.isEmpty {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 10), action: nil))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction))
|
||||
}
|
||||
}
|
||||
if !data.isContact {
|
||||
@ -606,13 +649,15 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
if let username = channel.username {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemUsername, label: presentationData.strings.Channel_LinkItem, text: "https://t.me/\(username)", textColor: .accent, action: {
|
||||
interaction.openUsername(username)
|
||||
}, longTapAction: { sourceNode in
|
||||
interaction.openPeerInfoContextMenu(.link, sourceNode)
|
||||
}))
|
||||
}
|
||||
if let cachedData = data.cachedData as? CachedChannelData {
|
||||
if channel.isScam {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Profile_About, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 10), action: nil))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Profile_About, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil))
|
||||
} else if let about = cachedData.about, !about.isEmpty {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 10), action: nil))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemAbout, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction))
|
||||
}
|
||||
|
||||
if case .broadcast = channel.info {
|
||||
@ -642,21 +687,31 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
|
||||
} else if let group = data.peer as? TelegramGroup {
|
||||
if let cachedData = data.cachedData as? CachedGroupData {
|
||||
if group.isScam {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 10), action: nil))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: presentationData.strings.GroupInfo_ScamGroupWarning, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil))
|
||||
} else if let about = cachedData.about, !about.isEmpty {
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 10), action: nil))
|
||||
items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 0, label: presentationData.strings.Profile_About, text: about, textColor: .primary, textBehavior: .multiLine(maxLines: 100, enabledEntities: enabledBioEntities), action: nil, longTapAction: bioContextAction, linkItemAction: bioLinkAction))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let members = data.members, case let .shortList(memberList) = members {
|
||||
if let peer = data.peer, let members = data.members, case let .shortList(_, memberList) = members {
|
||||
for member in memberList {
|
||||
var presence = member.presence
|
||||
if member.id == context.account.peerId {
|
||||
let isAccountPeer = member.id == context.account.peerId
|
||||
if isAccountPeer {
|
||||
presence = TelegramUserPresence(status: .present(until: Int32.max - 1), lastActivity: 0)
|
||||
}
|
||||
items[.peerMembers]!.append(PeerInfoScreenMemberItem(id: member.id, context: context, peer: member.peer, presence: presence, action: {
|
||||
interaction.openPeerInfo(member.peer)
|
||||
items[.peerMembers]!.append(PeerInfoScreenMemberItem(id: member.id, context: context, enclosingPeer: peer, member: member, action: isAccountPeer ? nil : { action in
|
||||
switch action {
|
||||
case .open:
|
||||
interaction.openPeerInfo(member.peer)
|
||||
case .promote:
|
||||
interaction.performMemberAction(member, .promote)
|
||||
case .restrict:
|
||||
interaction.performMemberAction(member, .restrict)
|
||||
case .remove:
|
||||
interaction.performMemberAction(member, .remove)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -967,6 +1022,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
private let activeActionDisposable = MetaDisposable()
|
||||
private let resolveUrlDisposable = MetaDisposable()
|
||||
private let toggleShouldChannelMessagesSignaturesDisposable = MetaDisposable()
|
||||
private let selectAddMemberDisposable = MetaDisposable()
|
||||
private let addMemberDisposable = MetaDisposable()
|
||||
|
||||
private let updateAvatarDisposable = MetaDisposable()
|
||||
private let currentAvatarMixin = Atomic<TGMediaAvatarMenuMixin?>(value: nil)
|
||||
@ -977,7 +1034,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
private var didSetReady = false
|
||||
|
||||
init(controller: PeerInfoScreen, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool) {
|
||||
init(controller: PeerInfoScreen, context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool, keepExpandedButtons: PeerInfoScreenKeepExpandedButtons) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
@ -986,7 +1043,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.scrollNode.view.delaysContentTouches = false
|
||||
|
||||
self.headerNode = PeerInfoHeaderNode(context: context, avatarInitiallyExpanded: avatarInitiallyExpanded)
|
||||
self.headerNode = PeerInfoHeaderNode(context: context, avatarInitiallyExpanded: avatarInitiallyExpanded, keepExpandedButtons: keepExpandedButtons)
|
||||
self.paneContainerNode = PeerInfoPaneContainerNode(context: context, peerId: peerId)
|
||||
|
||||
super.init()
|
||||
@ -1057,6 +1114,15 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
},
|
||||
openPeerInfo: { [weak self] peer in
|
||||
self?.openPeerInfo(peer: peer)
|
||||
},
|
||||
performMemberAction: { [weak self] member, action in
|
||||
self?.performMemberAction(member: member, action: action)
|
||||
},
|
||||
openPeerInfoContextMenu: { [weak self] subject, sourceNode in
|
||||
self?.openPeerInfoContextMenu(subject: subject, sourceNode: sourceNode)
|
||||
},
|
||||
performBioLinkAction: { [weak self] action, item in
|
||||
self?.performBioLinkAction(action: action, item: item)
|
||||
}
|
||||
)
|
||||
|
||||
@ -1429,6 +1495,36 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
}
|
||||
|
||||
self.paneContainerNode.requestExpandTabs = { [weak self] in
|
||||
guard let strongSelf = self, let (_, navigationHeight) = strongSelf.validLayout else {
|
||||
return false
|
||||
}
|
||||
let contentOffset = strongSelf.scrollNode.view.contentOffset
|
||||
let paneAreaExpansionFinalPoint: CGFloat = strongSelf.paneContainerNode.frame.minY - navigationHeight
|
||||
if contentOffset.y < paneAreaExpansionFinalPoint - CGFloat.ulpOfOne {
|
||||
strongSelf.scrollNode.view.setContentOffset(CGPoint(x: 0.0, y: paneAreaExpansionFinalPoint), animated: true)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
self.paneContainerNode.requestPerformPeerMemberAction = { [weak self] member, action in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch action {
|
||||
case .open:
|
||||
strongSelf.openPeerInfo(peer: member.peer)
|
||||
case .promote:
|
||||
strongSelf.performMemberAction(member: member, action: .promote)
|
||||
case .restrict:
|
||||
strongSelf.performMemberAction(member: member, action: .restrict)
|
||||
case .remove:
|
||||
strongSelf.performMemberAction(member: member, action: .remove)
|
||||
}
|
||||
}
|
||||
|
||||
self.headerNode.performButtonAction = { [weak self] key in
|
||||
self?.performButtonAction(key: key)
|
||||
}
|
||||
@ -1612,6 +1708,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self.hiddenAvatarRepresentationDisposable.dispose()
|
||||
self.toggleShouldChannelMessagesSignaturesDisposable.dispose()
|
||||
self.updateAvatarDisposable.dispose()
|
||||
self.selectAddMemberDisposable.dispose()
|
||||
self.addMemberDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -1621,7 +1719,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
private func updateData(_ data: PeerInfoScreenData) {
|
||||
self.data = data
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
|
||||
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: self.didSetReady ? .animated(duration: 0.3, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1725,7 +1823,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
(strongSelf.controller?.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||
}
|
||||
}
|
||||
@ -1911,7 +2009,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self.view.endEditing(true)
|
||||
controller.present(actionSheet, in: .window(.root))
|
||||
case .addMember:
|
||||
break
|
||||
self.openAddMember()
|
||||
}
|
||||
}
|
||||
|
||||
@ -2482,11 +2580,91 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
private func openPeerInfo(peer: Peer) {
|
||||
if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
(self.controller?.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||
}
|
||||
}
|
||||
|
||||
private func performMemberAction(member: PeerInfoMember, action: PeerInfoMemberAction) {
|
||||
guard let data = self.data, let peer = data.peer else {
|
||||
return
|
||||
}
|
||||
switch action {
|
||||
case .promote:
|
||||
if case let .channelMember(channelMember) = member {
|
||||
self.controller?.push(channelAdminController(context: self.context, peerId: peer.id, adminId: member.id, initialParticipant: channelMember.participant, updated: { _ in
|
||||
}, upgradedToSupergroup: { _, f in f() }, transferedOwnership: { _ in }))
|
||||
}
|
||||
case .restrict:
|
||||
if case let .channelMember(channelMember) = member {
|
||||
self.controller?.push(channelBannedMemberController(context: self.context, peerId: peer.id, memberId: member.id, initialParticipant: channelMember.participant, updated: { _ in
|
||||
}, upgradedToSupergroup: { _, f in f() }))
|
||||
}
|
||||
case .remove:
|
||||
data.members?.membersContext.removeMember(memberId: member.id)
|
||||
}
|
||||
}
|
||||
|
||||
private func openPeerInfoContextMenu(subject: PeerInfoContextSubject, sourceNode: ASDisplayNode) {
|
||||
guard let data = self.data, let peer = data.peer, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
switch subject {
|
||||
case .bio:
|
||||
var text: String?
|
||||
if let cachedData = data.cachedData as? CachedUserData {
|
||||
text = cachedData.about
|
||||
} else if let cachedData = data.cachedData as? CachedGroupData {
|
||||
text = cachedData.about
|
||||
} else if let cachedData = data.cachedData as? CachedChannelData {
|
||||
text = cachedData.about
|
||||
}
|
||||
if let text = text, !text.isEmpty {
|
||||
let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: {
|
||||
UIPasteboard.general.string = text
|
||||
})])
|
||||
controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in
|
||||
if let controller = self?.controller, let sourceNode = sourceNode {
|
||||
return (sourceNode, sourceNode.bounds.insetBy(dx: 0.0, dy: -2.0), controller.displayNode, controller.view.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}))
|
||||
}
|
||||
case let .phone(phone):
|
||||
let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: {
|
||||
UIPasteboard.general.string = phone
|
||||
})])
|
||||
controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in
|
||||
if let controller = self?.controller, let sourceNode = sourceNode {
|
||||
return (sourceNode, sourceNode.bounds.insetBy(dx: 0.0, dy: -2.0), controller.displayNode, controller.view.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}))
|
||||
case .link:
|
||||
if let addressName = peer.addressName {
|
||||
let contextMenuController = ContextMenuController(actions: [ContextMenuAction(content: .text(title: self.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: self.presentationData.strings.Conversation_ContextMenuCopy), action: {
|
||||
UIPasteboard.general.string = addressName
|
||||
})])
|
||||
controller.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self, weak sourceNode] in
|
||||
if let controller = self?.controller, let sourceNode = sourceNode {
|
||||
return (sourceNode, sourceNode.bounds.insetBy(dx: 0.0, dy: -2.0), controller.displayNode, controller.view.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func performBioLinkAction(action: TextLinkItemActionType, item: TextLinkItem) {
|
||||
guard let data = self.data, let peer = data.peer, let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.handleTextLinkAction(context: self.context, peerId: peer.id, navigateDisposable: self.resolveUrlDisposable, controller: controller, action: action, itemLink: item)
|
||||
}
|
||||
|
||||
private func openDeletePeer() {
|
||||
let peerId = self.peerId
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> Peer? in
|
||||
@ -2712,7 +2890,269 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
})
|
||||
}
|
||||
|
||||
func deleteMessages(messageIds: Set<MessageId>?) {
|
||||
private func openAddMember() {
|
||||
guard let data = self.data, let groupPeer = data.peer else {
|
||||
return
|
||||
}
|
||||
|
||||
let members: Promise<[PeerId]> = Promise()
|
||||
if groupPeer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
/*var membersDisposable: Disposable?
|
||||
let (disposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { listState in
|
||||
members.set(.single(listState.list.map {$0.peer.id}))
|
||||
membersDisposable?.dispose()
|
||||
})
|
||||
membersDisposable = disposable*/
|
||||
members.set(.single([]))
|
||||
} else {
|
||||
members.set(.single([]))
|
||||
}
|
||||
|
||||
let _ = (members.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] recentIds in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var createInviteLinkImpl: (() -> Void)?
|
||||
var confirmationImpl: ((PeerId) -> Signal<Bool, NoError>)?
|
||||
var options: [ContactListAdditionalOption] = []
|
||||
let presentationData = strongSelf.presentationData
|
||||
|
||||
var canCreateInviteLink = false
|
||||
if let group = groupPeer as? TelegramGroup {
|
||||
switch group.role {
|
||||
case .creator, .admin:
|
||||
canCreateInviteLink = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if let channel = groupPeer as? TelegramChannel {
|
||||
if channel.hasPermission(.inviteMembers) {
|
||||
if channel.flags.contains(.isCreator) || (channel.adminRights != nil && channel.username == nil) {
|
||||
canCreateInviteLink = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if canCreateInviteLink {
|
||||
options.append(ContactListAdditionalOption(title: presentationData.strings.GroupInfo_InviteByLink, icon: .generic(UIImage(bundleImageName: "Contact List/LinkActionIcon")!), action: {
|
||||
createInviteLinkImpl?()
|
||||
}))
|
||||
}
|
||||
|
||||
let contactsController: ViewController
|
||||
if groupPeer.id.namespace == Namespaces.Peer.CloudGroup {
|
||||
contactsController = strongSelf.context.sharedContext.makeContactSelectionController(ContactSelectionControllerParams(context: strongSelf.context, autoDismiss: false, title: { $0.GroupInfo_AddParticipantTitle }, options: options, confirmation: { peer in
|
||||
if let confirmationImpl = confirmationImpl, case let .peer(peer, _, _) = peer {
|
||||
return confirmationImpl(peer.id)
|
||||
} else {
|
||||
return .single(false)
|
||||
}
|
||||
}))
|
||||
contactsController.navigationPresentation = .modal
|
||||
} else {
|
||||
contactsController = strongSelf.context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: strongSelf.context, mode: .peerSelection(searchChatList: false, searchGroups: false), options: options, filters: [.excludeSelf, .disable(recentIds)]))
|
||||
contactsController.navigationPresentation = .modal
|
||||
}
|
||||
|
||||
let context = strongSelf.context
|
||||
confirmationImpl = { [weak contactsController] peerId in
|
||||
return context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { peer in
|
||||
let result = ValuePromise<Bool>()
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
if let contactsController = contactsController {
|
||||
let alertController = textAlertController(context: context, title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).0, actions: [
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {
|
||||
result.set(false)
|
||||
}),
|
||||
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
|
||||
result.set(true)
|
||||
})
|
||||
])
|
||||
contactsController.present(alertController, in: .window(.root))
|
||||
}
|
||||
|
||||
return result.get()
|
||||
}
|
||||
}
|
||||
|
||||
let addMember: (ContactListPeer) -> Signal<Void, NoError> = { memberPeer -> Signal<Void, NoError> in
|
||||
if case let .peer(selectedPeer, _, _) = memberPeer {
|
||||
let memberId = selectedPeer.id
|
||||
if groupPeer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
return context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: groupPeer.id, memberId: memberId)
|
||||
|> map { _ -> Void in
|
||||
return Void()
|
||||
}
|
||||
|> `catch` { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return addGroupMember(account: context.account, peerId: groupPeer.id, memberId: memberId)
|
||||
|> deliverOnMainQueue
|
||||
|> `catch` { error -> Signal<Void, NoError> in
|
||||
switch error {
|
||||
case .generic:
|
||||
return .complete()
|
||||
case .privacy:
|
||||
let _ = (context.account.postbox.loadedPeerWithId(memberId)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
self?.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
})
|
||||
return .complete()
|
||||
case .tooManyChannels:
|
||||
let _ = (context.account.postbox.loadedPeerWithId(memberId)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
self?.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
})
|
||||
return .complete()
|
||||
case .groupFull:
|
||||
let signal = convertGroupToSupergroup(account: context.account, peerId: groupPeer.id)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error -> Signal<PeerId?, NoError> in
|
||||
switch error {
|
||||
case .tooManyChannels:
|
||||
Queue.mainQueue().async {
|
||||
self?.controller?.push(oldChannelsController(context: context, intent: .upgrade))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, NoError> in
|
||||
guard let upgradedPeerId = upgradedPeerId else {
|
||||
return .single(nil)
|
||||
}
|
||||
return context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: upgradedPeerId, memberId: memberId)
|
||||
|> `catch` { _ -> Signal<Never, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<PeerId?, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|> then(.single(upgradedPeerId))
|
||||
}
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
return signal
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|
||||
let addMembers: ([ContactListPeerId]) -> Signal<Void, AddChannelMemberError> = { members -> Signal<Void, AddChannelMemberError> in
|
||||
let memberIds = members.compactMap { contact -> PeerId? in
|
||||
switch contact {
|
||||
case let .peer(peerId):
|
||||
return peerId
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return context.account.postbox.multiplePeersView(memberIds)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
|> mapError { _ in return .generic}
|
||||
|> mapToSignal { view -> Signal<Void, AddChannelMemberError> in
|
||||
if memberIds.count == 1 {
|
||||
return context.peerChannelMemberCategoriesContextsManager.addMember(account: context.account, peerId: groupPeer.id, memberId: memberIds[0])
|
||||
|> map { _ -> Void in
|
||||
return Void()
|
||||
}
|
||||
} else {
|
||||
return context.peerChannelMemberCategoriesContextsManager.addMembers(account: context.account, peerId: groupPeer.id, memberIds: memberIds) |> map { _ in
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createInviteLinkImpl = { [weak contactsController] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let mode: ChannelVisibilityControllerMode
|
||||
if groupPeer.addressName != nil {
|
||||
mode = .generic
|
||||
} else {
|
||||
mode = .privateLink
|
||||
}
|
||||
let visibilityController = channelVisibilityController(context: strongSelf.context, peerId: groupPeer.id, mode: mode, upgradedToSupergroup: { _, f in f() })
|
||||
visibilityController.navigationPresentation = .modal
|
||||
|
||||
if let navigationController = strongSelf.controller?.navigationController as? NavigationController {
|
||||
var controllers = navigationController.viewControllers
|
||||
if let contactsController = contactsController {
|
||||
controllers.removeAll(where: { $0 === contactsController })
|
||||
}
|
||||
controllers.append(visibilityController)
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.controller?.push(contactsController)
|
||||
let selectAddMemberDisposable = strongSelf.selectAddMemberDisposable
|
||||
let addMemberDisposable = strongSelf.addMemberDisposable
|
||||
if let contactsController = contactsController as? ContactSelectionController {
|
||||
selectAddMemberDisposable.set((contactsController.result
|
||||
|> deliverOnMainQueue).start(next: { [weak contactsController] memberPeer in
|
||||
guard let memberPeer = memberPeer else {
|
||||
return
|
||||
}
|
||||
|
||||
contactsController?.displayProgress = true
|
||||
addMemberDisposable.set((addMember(memberPeer)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
contactsController?.dismiss()
|
||||
}))
|
||||
}))
|
||||
contactsController.dismissed = {
|
||||
selectAddMemberDisposable.set(nil)
|
||||
addMemberDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
if let contactsController = contactsController as? ContactMultiselectionController {
|
||||
selectAddMemberDisposable.set((contactsController.result
|
||||
|> deliverOnMainQueue).start(next: { [weak contactsController] peers in
|
||||
contactsController?.displayProgress = true
|
||||
addMemberDisposable.set((addMembers(peers)
|
||||
|> deliverOnMainQueue).start(error: { error in
|
||||
if peers.count == 1, case .restricted = error {
|
||||
switch peers[0] {
|
||||
case let .peer(peerId):
|
||||
let _ = (context.account.postbox.loadedPeerWithId(peerId)
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
self?.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Privacy_GroupsAndChannels_InviteToGroupError(peer.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
})
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if case .tooMuchJoined = error {
|
||||
self?.controller?.present(textAlertController(context: context, title: nil, text: presentationData.strings.Invite_ChannelsTooMuch, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
|
||||
contactsController?.dismiss()
|
||||
},completed: {
|
||||
contactsController?.dismiss()
|
||||
}))
|
||||
}))
|
||||
contactsController.dismissed = {
|
||||
selectAddMemberDisposable.set(nil)
|
||||
addMemberDisposable.set(nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func deleteMessages(messageIds: Set<MessageId>?) {
|
||||
if let messageIds = messageIds ?? self.state.selectedMessageIds, !messageIds.isEmpty {
|
||||
self.activeActionDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: self.context.account.postbox, accountPeerId: self.context.account.peerId, messageIds: messageIds)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] actions in
|
||||
@ -3351,10 +3791,16 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
}
|
||||
|
||||
public enum PeerInfoScreenKeepExpandedButtons {
|
||||
case message
|
||||
case mute
|
||||
}
|
||||
|
||||
public final class PeerInfoScreen: ViewController {
|
||||
private let context: AccountContext
|
||||
private let peerId: PeerId
|
||||
private let avatarInitiallyExpanded: Bool
|
||||
private let keepExpandedButtons: PeerInfoScreenKeepExpandedButtons
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
@ -3368,10 +3814,13 @@ public final class PeerInfoScreen: ViewController {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
public init(context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool = false) {
|
||||
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
|
||||
|
||||
public init(context: AccountContext, peerId: PeerId, avatarInitiallyExpanded: Bool = false, keepExpandedButtons: PeerInfoScreenKeepExpandedButtons = .message) {
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
self.avatarInitiallyExpanded = avatarInitiallyExpanded
|
||||
self.keepExpandedButtons = keepExpandedButtons
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -3439,7 +3888,7 @@ public final class PeerInfoScreen: ViewController {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded)
|
||||
self.displayNode = PeerInfoScreenNode(controller: self, context: self.context, peerId: self.peerId, avatarInitiallyExpanded: self.avatarInitiallyExpanded, keepExpandedButtons: self.keepExpandedButtons)
|
||||
|
||||
self._ready.set(self.controllerNode.ready.get())
|
||||
|
||||
@ -3448,11 +3897,17 @@ public final class PeerInfoScreen: ViewController {
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if let (layout, navigationHeight) = self.validLayout {
|
||||
self.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.validLayout = (layout, navigationHeight)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
|
||||
}
|
||||
}
|
||||
|
@ -759,7 +759,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
if let strongSelf = self, peer.restrictionText(platform: "ios", contentSettings: strongSelf.context.currentContentSettings.with { $0 }) == nil {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = strongSelf.context.sharedContext.makePeerInfoController(context: strongSelf.context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
(strongSelf.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||
}
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ public func pollResultsController(context: AccountContext, messageId: MessageId,
|
||||
})
|
||||
}, openPeer: { peer in
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
}
|
||||
|
@ -1004,8 +1004,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
handleTextLinkActionImpl(context: context, peerId: peerId, navigateDisposable: navigateDisposable, controller: controller, action: action, itemLink: itemLink)
|
||||
}
|
||||
|
||||
public func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool) -> ViewController? {
|
||||
let controller = peerInfoControllerImpl(context: context, peer: peer, mode: mode, avatarInitiallyExpanded: avatarInitiallyExpanded)
|
||||
public func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController? {
|
||||
let controller = peerInfoControllerImpl(context: context, peer: peer, mode: mode, avatarInitiallyExpanded: avatarInitiallyExpanded, keepExpandedButtons: fromChat ? .mute : .message)
|
||||
controller?.navigationPresentation = .modalInLargeLayout
|
||||
return controller
|
||||
}
|
||||
@ -1245,13 +1245,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
|
||||
private let defaultChatControllerInteraction = ChatControllerInteraction.default
|
||||
|
||||
private func peerInfoControllerImpl(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool) -> ViewController? {
|
||||
private func peerInfoControllerImpl(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, keepExpandedButtons: PeerInfoScreenKeepExpandedButtons = .message) -> ViewController? {
|
||||
if let _ = peer as? TelegramGroup {
|
||||
return PeerInfoScreen(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded)
|
||||
return PeerInfoScreen(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, keepExpandedButtons: keepExpandedButtons)
|
||||
|
||||
return groupInfoController(context: context, peerId: peer.id)
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
return PeerInfoScreen(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded)
|
||||
return PeerInfoScreen(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, keepExpandedButtons: keepExpandedButtons)
|
||||
|
||||
if case .group = channel.info {
|
||||
return groupInfoController(context: context, peerId: peer.id)
|
||||
@ -1259,7 +1259,7 @@ private func peerInfoControllerImpl(context: AccountContext, peer: Peer, mode: P
|
||||
return channelInfoController(context: context, peerId: peer.id)
|
||||
}
|
||||
} else if peer is TelegramUser {
|
||||
return PeerInfoScreen(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded)
|
||||
return PeerInfoScreen(context: context, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, keepExpandedButtons: keepExpandedButtons)
|
||||
} else if peer is TelegramSecretChat {
|
||||
return userInfoController(context: context, peerId: peer.id, mode: mode)
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate
|
||||
peerSignal = context.account.postbox.loadedPeerWithId(peerId) |> map(Optional.init)
|
||||
navigateDisposable.set((peerSignal |> take(1) |> deliverOnMainQueue).start(next: { peer in
|
||||
if let controller = controller, let peer = peer {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false) {
|
||||
if let infoController = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: false, fromChat: false) {
|
||||
(controller.navigationController as? NavigationController)?.pushViewController(infoController)
|
||||
}
|
||||
}
|
||||
|
@ -27,9 +27,34 @@ private final class PeerChannelMembersOnlineContext {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProfileDataPreloadContext {
|
||||
let subscribers = Bag<() -> Void>()
|
||||
|
||||
let disposable: Disposable
|
||||
var emptyTimer: SwiftSignalKit.Timer?
|
||||
|
||||
init(disposable: Disposable) {
|
||||
self.disposable = disposable
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProfileDataPhotoPreloadContext {
|
||||
let subscribers = Bag<(Any?) -> Void>()
|
||||
|
||||
let disposable: Disposable
|
||||
var value: Any?
|
||||
var emptyTimer: SwiftSignalKit.Timer?
|
||||
|
||||
init(disposable: Disposable) {
|
||||
self.disposable = disposable
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerChannelMemberCategoriesContextsManagerImpl {
|
||||
fileprivate var contexts: [PeerId: PeerChannelMemberCategoriesContext] = [:]
|
||||
fileprivate var onlineContexts: [PeerId: PeerChannelMembersOnlineContext] = [:]
|
||||
fileprivate var profileDataPreloadContexts: [PeerId: ProfileDataPreloadContext] = [:]
|
||||
fileprivate var profileDataPhotoPreloadContexts: [PeerId: ProfileDataPhotoPreloadContext] = [:]
|
||||
|
||||
func getContext(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, key: PeerChannelMemberContextKey, requestUpdate: Bool, updated: @escaping (ChannelMemberListState) -> Void) -> (Disposable, PeerChannelMemberCategoryControl) {
|
||||
if let current = self.contexts[peerId] {
|
||||
@ -121,6 +146,121 @@ private final class PeerChannelMemberCategoriesContextsManagerImpl {
|
||||
context.loadMore(control)
|
||||
}
|
||||
}
|
||||
|
||||
func profileData(postbox: Postbox, network: Network, peerId: PeerId, customData: Signal<Never, NoError>?) -> Disposable {
|
||||
let context: ProfileDataPreloadContext
|
||||
if let current = self.profileDataPreloadContexts[peerId] {
|
||||
context = current
|
||||
} else {
|
||||
let disposable = DisposableSet()
|
||||
context = ProfileDataPreloadContext(disposable: disposable)
|
||||
self.profileDataPreloadContexts[peerId] = context
|
||||
|
||||
if let customData = customData {
|
||||
disposable.add(customData.start())
|
||||
}
|
||||
|
||||
/*disposable.set(signal.start(next: { [weak context] value in
|
||||
guard let context = context else {
|
||||
return
|
||||
}
|
||||
context.value = value
|
||||
for f in context.subscribers.copyItems() {
|
||||
f(value)
|
||||
}
|
||||
}))*/
|
||||
}
|
||||
|
||||
if let emptyTimer = context.emptyTimer {
|
||||
emptyTimer.invalidate()
|
||||
context.emptyTimer = nil
|
||||
}
|
||||
|
||||
let index = context.subscribers.add({
|
||||
})
|
||||
//updated(context.value ?? 0)
|
||||
|
||||
return ActionDisposable { [weak self, weak context] in
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let current = strongSelf.profileDataPreloadContexts[peerId], let context = context, current === context {
|
||||
current.subscribers.remove(index)
|
||||
if current.subscribers.isEmpty {
|
||||
if current.emptyTimer == nil {
|
||||
let timer = SwiftSignalKit.Timer(timeout: 60.0, repeat: false, completion: { [weak context] in
|
||||
if let current = strongSelf.profileDataPreloadContexts[peerId], let context = context, current === context {
|
||||
if current.subscribers.isEmpty {
|
||||
strongSelf.profileDataPreloadContexts.removeValue(forKey: peerId)
|
||||
current.disposable.dispose()
|
||||
}
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
current.emptyTimer = timer
|
||||
timer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func profilePhotos(postbox: Postbox, network: Network, peerId: PeerId, fetch: Signal<Any, NoError>, updated: @escaping (Any?) -> Void) -> Disposable {
|
||||
let context: ProfileDataPhotoPreloadContext
|
||||
if let current = self.profileDataPhotoPreloadContexts[peerId] {
|
||||
context = current
|
||||
} else {
|
||||
let disposable = MetaDisposable()
|
||||
context = ProfileDataPhotoPreloadContext(disposable: disposable)
|
||||
self.profileDataPhotoPreloadContexts[peerId] = context
|
||||
|
||||
disposable.set(fetch.start(next: { [weak context] value in
|
||||
guard let context = context else {
|
||||
return
|
||||
}
|
||||
context.value = value
|
||||
for f in context.subscribers.copyItems() {
|
||||
f(value)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
if let emptyTimer = context.emptyTimer {
|
||||
emptyTimer.invalidate()
|
||||
context.emptyTimer = nil
|
||||
}
|
||||
|
||||
let index = context.subscribers.add({ next in
|
||||
updated(next)
|
||||
})
|
||||
updated(context.value)
|
||||
|
||||
return ActionDisposable { [weak self, weak context] in
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let current = strongSelf.profileDataPhotoPreloadContexts[peerId], let context = context, current === context {
|
||||
current.subscribers.remove(index)
|
||||
if current.subscribers.isEmpty {
|
||||
if current.emptyTimer == nil {
|
||||
let timer = SwiftSignalKit.Timer(timeout: 60.0, repeat: false, completion: { [weak context] in
|
||||
if let current = strongSelf.profileDataPhotoPreloadContexts[peerId], let context = context, current === context {
|
||||
if current.subscribers.isEmpty {
|
||||
strongSelf.profileDataPhotoPreloadContexts.removeValue(forKey: peerId)
|
||||
current.disposable.dispose()
|
||||
}
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
current.emptyTimer = timer
|
||||
timer.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class PeerChannelMemberCategoriesContextsManager {
|
||||
@ -394,4 +534,35 @@ public final class PeerChannelMemberCategoriesContextsManager {
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
}
|
||||
|
||||
public func profileData(postbox: Postbox, network: Network, peerId: PeerId, customData: Signal<Never, NoError>?) -> Signal<Never, NoError> {
|
||||
return Signal { [weak self] subscriber in
|
||||
guard let strongSelf = self else {
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
let disposable = strongSelf.impl.syncWith({ impl -> Disposable in
|
||||
return impl.profileData(postbox: postbox, network: network, peerId: peerId, customData: customData)
|
||||
})
|
||||
return disposable ?? EmptyDisposable
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
}
|
||||
|
||||
public func profilePhotos(postbox: Postbox, network: Network, peerId: PeerId, fetch: Signal<Any, NoError>) -> Signal<Any?, NoError> {
|
||||
return Signal { [weak self] subscriber in
|
||||
guard let strongSelf = self else {
|
||||
subscriber.putNext(0)
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
let disposable = strongSelf.impl.syncWith({ impl -> Disposable in
|
||||
return impl.profilePhotos(postbox: postbox, network: network, peerId: peerId, fetch: fetch, updated: { value in
|
||||
subscriber.putNext(value)
|
||||
})
|
||||
})
|
||||
return disposable ?? EmptyDisposable
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user