mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
606076c877
@ -2,7 +2,7 @@
|
||||
def appConfig():
|
||||
apiId = native.read_config("custom", "apiId")
|
||||
apiHash = native.read_config("custom", "apiHash")
|
||||
hockeyAppId = native.read_config("custom", "hockeyAppId")
|
||||
appCenterId = native.read_config("custom", "appCenterId")
|
||||
isInternalBuild = native.read_config("custom", "isInternalBuild")
|
||||
isAppStoreBuild = native.read_config("custom", "isAppStoreBuild")
|
||||
appStoreId = native.read_config("custom", "appStoreId")
|
||||
@ -11,7 +11,7 @@ def appConfig():
|
||||
return {
|
||||
"apiId": apiId,
|
||||
"apiHash": apiHash,
|
||||
"hockeyAppId": hockeyAppId,
|
||||
"appCenterId": appCenterId,
|
||||
"isInternalBuild": isInternalBuild,
|
||||
"isAppStoreBuild": isAppStoreBuild,
|
||||
"appStoreId": appStoreId,
|
||||
|
2
Makefile
2
Makefile
@ -10,7 +10,7 @@ BUCK_OPTIONS=\
|
||||
--config custom.baseApplicationBundleId="${BUNDLE_ID}" \
|
||||
--config custom.apiId="${API_ID}" \
|
||||
--config custom.apiHash="${API_HASH}" \
|
||||
--config custom.hockeyAppId="${HOCKEYAPP_ID}" \
|
||||
--config custom.appCenterId="${APP_CENTER_ID}" \
|
||||
--config custom.isInternalBuild="${IS_INTERNAL_BUILD}" \
|
||||
--config custom.isAppStoreBuild="${IS_APPSTORE_BUILD}" \
|
||||
--config custom.appStoreId="${APPSTORE_ID}" \
|
||||
|
@ -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
|
||||
|
@ -9,7 +9,7 @@ static_library(
|
||||
compiler_flags = [
|
||||
'-DAPP_CONFIG_API_ID=' + appConfig()["apiId"],
|
||||
'-DAPP_CONFIG_API_HASH="' + appConfig()["apiHash"] + '"',
|
||||
'-DAPP_CONFIG_HOCKEYAPP_ID="' + appConfig()["hockeyAppId"] + '"',
|
||||
'-DAPP_CONFIG_APP_CENTER_ID="' + appConfig()["appCenterId"] + '"',
|
||||
'-DAPP_CONFIG_IS_INTERNAL_BUILD=' + appConfig()["isInternalBuild"],
|
||||
'-DAPP_CONFIG_IS_APPSTORE_BUILD=' + appConfig()["isAppStoreBuild"],
|
||||
'-DAPP_CONFIG_APPSTORE_ID=' + appConfig()["appStoreId"],
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
- (instancetype _Nonnull)initWithBaseAppBundleId:(NSString * _Nonnull)baseAppBundleId;
|
||||
|
||||
@property (nonatomic, strong, readonly) NSString * _Nullable hockeyAppId;
|
||||
@property (nonatomic, strong, readonly) NSString * _Nullable appCenterId;
|
||||
@property (nonatomic, readonly) int32_t apiId;
|
||||
@property (nonatomic, strong, readonly) NSString * _Nonnull apiHash;
|
||||
@property (nonatomic, readonly) bool isInternalBuild;
|
||||
|
@ -72,7 +72,7 @@ API_AVAILABLE(ios(10))
|
||||
NSData * _Nullable _bundleData;
|
||||
int32_t _apiId;
|
||||
NSString * _Nonnull _apiHash;
|
||||
NSString * _Nullable _hockeyAppId;
|
||||
NSString * _Nullable _appCenterId;
|
||||
NSMutableDictionary * _Nonnull _dataDict;
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ API_AVAILABLE(ios(10))
|
||||
if (self != nil) {
|
||||
_apiId = APP_CONFIG_API_ID;
|
||||
_apiHash = @(APP_CONFIG_API_HASH);
|
||||
_hockeyAppId = @(APP_CONFIG_HOCKEYAPP_ID);
|
||||
_appCenterId = @(APP_CONFIG_APP_CENTER_ID);
|
||||
|
||||
_dataDict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
@ -163,8 +163,8 @@ API_AVAILABLE(ios(10))
|
||||
return _apiHash;
|
||||
}
|
||||
|
||||
- (NSString * _Nullable)hockeyAppId {
|
||||
return _hockeyAppId;
|
||||
- (NSString * _Nullable)appCenterId {
|
||||
return _appCenterId;
|
||||
}
|
||||
|
||||
- (bool)isInternalBuild {
|
||||
|
@ -154,7 +154,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)
|
||||
}
|
||||
})
|
||||
|
@ -95,7 +95,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
|
||||
}
|
||||
}
|
||||
|
||||
public class ChatListControllerImpl: TelegramBaseController, ChatListController, UIViewControllerPreviewingDelegate, TabBarContainedController {
|
||||
public class ChatListControllerImpl: TelegramBaseController, ChatListController, UIViewControllerPreviewingDelegate/*, TabBarContainedController*/ {
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
public let context: AccountContext
|
||||
@ -1804,7 +1804,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
}
|
||||
}
|
||||
|
||||
public func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) {
|
||||
/*public func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) {
|
||||
if self.isNodeLoaded {
|
||||
let _ = (self.context.account.postbox.transaction { transaction -> [ChatListFilter] in
|
||||
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters) as? ChatListFiltersState ?? ChatListFiltersState.default
|
||||
@ -1849,5 +1849,5 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
|
||||
|
||||
public func updateTabBarPreviewingControllerPresentation(_ update: TabBarContainedControllerPresentationUpdate) {
|
||||
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Foundation
|
||||
/*import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
@ -405,3 +405,4 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
||||
return controller
|
||||
}
|
||||
|
||||
*/
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Foundation
|
||||
/*import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
@ -206,3 +206,4 @@ func chatListFilterPresetListController(context: AccountContext, updated: @escap
|
||||
|
||||
return controller
|
||||
}
|
||||
*/
|
||||
|
@ -535,7 +535,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
|
||||
|
@ -636,7 +636,7 @@ public func peersNearbyController(context: AccountContext) -> ViewController {
|
||||
controller?.clearItemNodesHighlight(animated: true)
|
||||
}
|
||||
navigateToProfileImpl = { [weak controller] peer in
|
||||
if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil) {
|
||||
if let navigationController = controller?.navigationController as? NavigationController, let controller = context.sharedContext.makePeerInfoController(context: context, peer: peer, mode: .generic, avatarInitiallyExpanded: peer.largeProfileImage != nil, fromChat: false) {
|
||||
(navigationController as? NavigationController)?.pushViewController(controller)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -247,9 +247,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($0) }
|
||||
dict[1448076945] = { return Api.Update.parse_updateLoginToken($0) }
|
||||
dict[1123585836] = { return Api.Update.parse_updateMessagePollVote($0) }
|
||||
dict[654302845] = { return Api.Update.parse_updateDialogFilter($0) }
|
||||
dict[-1512627963] = { return Api.Update.parse_updateDialogFilterOrder($0) }
|
||||
dict[889491791] = { return Api.Update.parse_updateDialogFilters($0) }
|
||||
dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
|
||||
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
|
||||
@ -415,9 +412,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[2103482845] = { return Api.SecurePlainData.parse_securePlainPhone($0) }
|
||||
dict[569137759] = { return Api.SecurePlainData.parse_securePlainEmail($0) }
|
||||
dict[-1269012015] = { return Api.messages.AffectedHistory.parse_affectedHistory($0) }
|
||||
dict[1244130093] = { return Api.StatsGraph.parse_statsGraphAsync($0) }
|
||||
dict[-1092839390] = { return Api.StatsGraph.parse_statsGraphError($0) }
|
||||
dict[-1057809608] = { return Api.StatsGraph.parse_statsGraph($0) }
|
||||
dict[-1036572727] = { return Api.account.PasswordInputSettings.parse_passwordInputSettings($0) }
|
||||
dict[878078826] = { return Api.PageTableCell.parse_pageTableCell($0) }
|
||||
dict[-1626209256] = { return Api.ChatBannedRights.parse_chatBannedRights($0) }
|
||||
@ -486,7 +480,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-668391402] = { return Api.InputUser.parse_inputUser($0) }
|
||||
dict[-1366746132] = { return Api.Page.parse_page($0) }
|
||||
dict[871426631] = { return Api.SecureCredentialsEncrypted.parse_secureCredentialsEncrypted($0) }
|
||||
dict[-875679776] = { return Api.StatsPercentValue.parse_statsPercentValue($0) }
|
||||
dict[157948117] = { return Api.upload.File.parse_file($0) }
|
||||
dict[-242427324] = { return Api.upload.File.parse_fileCdnRedirect($0) }
|
||||
dict[-1078612597] = { return Api.ChannelLocation.parse_channelLocationEmpty($0) }
|
||||
@ -513,7 +506,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1160215659] = { return Api.InputMessage.parse_inputMessageReplyTo($0) }
|
||||
dict[-2037963464] = { return Api.InputMessage.parse_inputMessagePinned($0) }
|
||||
dict[-1564789301] = { return Api.PhoneCallProtocol.parse_phoneCallProtocol($0) }
|
||||
dict[-1237848657] = { return Api.StatsDateRangeDays.parse_statsDateRangeDays($0) }
|
||||
dict[-1567175714] = { return Api.MessageFwdAuthor.parse_messageFwdAuthor($0) }
|
||||
dict[-1539849235] = { return Api.WallPaper.parse_wallPaper($0) }
|
||||
dict[-1963717851] = { return Api.WallPaper.parse_wallPaperNoFile($0) }
|
||||
@ -528,7 +520,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1837345356] = { return Api.InputChatPhoto.parse_inputChatUploadedPhoto($0) }
|
||||
dict[-1991004873] = { return Api.InputChatPhoto.parse_inputChatPhoto($0) }
|
||||
dict[-368917890] = { return Api.PaymentCharge.parse_paymentCharge($0) }
|
||||
dict[205195937] = { return Api.stats.BroadcastStats.parse_broadcastStats($0) }
|
||||
dict[-484987010] = { return Api.Updates.parse_updatesTooLong($0) }
|
||||
dict[-1857044719] = { return Api.Updates.parse_updateShortMessage($0) }
|
||||
dict[377562760] = { return Api.Updates.parse_updateShortChatMessage($0) }
|
||||
@ -536,7 +527,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1918567619] = { return Api.Updates.parse_updatesCombined($0) }
|
||||
dict[1957577280] = { return Api.Updates.parse_updates($0) }
|
||||
dict[301019932] = { return Api.Updates.parse_updateShortSentMessage($0) }
|
||||
dict[-884757282] = { return Api.StatsAbsValueAndPrev.parse_statsAbsValueAndPrev($0) }
|
||||
dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) }
|
||||
dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) }
|
||||
dict[-1618676578] = { return Api.MessageMedia.parse_messageMediaUnsupported($0) }
|
||||
@ -600,7 +590,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1551583367] = { return Api.ReceivedNotifyMessage.parse_receivedNotifyMessage($0) }
|
||||
dict[-57668565] = { return Api.ChatParticipants.parse_chatParticipantsForbidden($0) }
|
||||
dict[1061556205] = { return Api.ChatParticipants.parse_chatParticipants($0) }
|
||||
dict[351868460] = { return Api.DialogFilter.parse_dialogFilter($0) }
|
||||
dict[-1056001329] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsSaved($0) }
|
||||
dict[873977640] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentials($0) }
|
||||
dict[178373535] = { return Api.InputPaymentCredentials.parse_inputPaymentCredentialsApplePay($0) }
|
||||
@ -773,7 +762,6 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1041346555] = { return Api.updates.ChannelDifference.parse_channelDifferenceEmpty($0) }
|
||||
dict[543450958] = { return Api.updates.ChannelDifference.parse_channelDifference($0) }
|
||||
dict[-1531132162] = { return Api.updates.ChannelDifference.parse_channelDifferenceTooLong($0) }
|
||||
dict[-581804346] = { return Api.StatsRowAbsValueAndPrev.parse_statsRowAbsValueAndPrev($0) }
|
||||
dict[-309659827] = { return Api.channels.AdminLogResults.parse_adminLogResults($0) }
|
||||
dict[-264117680] = { return Api.ChatOnlines.parse_chatOnlines($0) }
|
||||
dict[488313413] = { return Api.InputAppEvent.parse_inputAppEvent($0) }
|
||||
@ -1097,8 +1085,6 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.messages.AffectedHistory:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsGraph:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.account.PasswordInputSettings:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PageTableCell:
|
||||
@ -1169,8 +1155,6 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.SecureCredentialsEncrypted:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsPercentValue:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.upload.File:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ChannelLocation:
|
||||
@ -1205,8 +1189,6 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PhoneCallProtocol:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsDateRangeDays:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.MessageFwdAuthor:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.WallPaper:
|
||||
@ -1223,12 +1205,8 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PaymentCharge:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.stats.BroadcastStats:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.Updates:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsAbsValueAndPrev:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.MessageMedia:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.PaymentSavedCredentials:
|
||||
@ -1279,8 +1257,6 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ChatParticipants:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.DialogFilter:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.InputPaymentCredentials:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ShippingOption:
|
||||
@ -1411,8 +1387,6 @@ public struct Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.updates.ChannelDifference:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StatsRowAbsValueAndPrev:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.channels.AdminLogResults:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.ChatOnlines:
|
||||
|
@ -5861,9 +5861,6 @@ public extension Api {
|
||||
case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32)
|
||||
case updateLoginToken
|
||||
case updateMessagePollVote(pollId: Int64, userId: Int32, options: [Buffer])
|
||||
case updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?)
|
||||
case updateDialogFilterOrder(order: [Int32])
|
||||
case updateDialogFilters
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
@ -6512,30 +6509,6 @@ public extension Api {
|
||||
for item in options {
|
||||
serializeBytes(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
break
|
||||
case .updateDialogFilter(let flags, let id, let filter):
|
||||
if boxed {
|
||||
buffer.appendInt32(654302845)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {filter!.serialize(buffer, true)}
|
||||
break
|
||||
case .updateDialogFilterOrder(let order):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1512627963)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(order.count))
|
||||
for item in order {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
break
|
||||
case .updateDialogFilters:
|
||||
if boxed {
|
||||
buffer.appendInt32(889491791)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -6696,12 +6669,6 @@ public extension Api {
|
||||
return ("updateLoginToken", [])
|
||||
case .updateMessagePollVote(let pollId, let userId, let options):
|
||||
return ("updateMessagePollVote", [("pollId", pollId), ("userId", userId), ("options", options)])
|
||||
case .updateDialogFilter(let flags, let id, let filter):
|
||||
return ("updateDialogFilter", [("flags", flags), ("id", id), ("filter", filter)])
|
||||
case .updateDialogFilterOrder(let order):
|
||||
return ("updateDialogFilterOrder", [("order", order)])
|
||||
case .updateDialogFilters:
|
||||
return ("updateDialogFilters", [])
|
||||
}
|
||||
}
|
||||
|
||||
@ -7998,41 +7965,6 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateDialogFilter(_ reader: BufferReader) -> Update? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Api.DialogFilter?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.DialogFilter
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.Update.updateDialogFilter(flags: _1!, id: _2!, filter: _3)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateDialogFilterOrder(_ reader: BufferReader) -> Update? {
|
||||
var _1: [Int32]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.Update.updateDialogFilterOrder(order: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_updateDialogFilters(_ reader: BufferReader) -> Update? {
|
||||
return Api.Update.updateDialogFilters
|
||||
}
|
||||
|
||||
}
|
||||
public enum PopularContact: TypeConstructorDescription {
|
||||
@ -12024,82 +11956,6 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsGraph: TypeConstructorDescription {
|
||||
case statsGraphAsync(token: String)
|
||||
case statsGraphError(error: String)
|
||||
case statsGraph(json: Api.DataJSON)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .statsGraphAsync(let token):
|
||||
if boxed {
|
||||
buffer.appendInt32(1244130093)
|
||||
}
|
||||
serializeString(token, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .statsGraphError(let error):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1092839390)
|
||||
}
|
||||
serializeString(error, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .statsGraph(let json):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1057809608)
|
||||
}
|
||||
json.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .statsGraphAsync(let token):
|
||||
return ("statsGraphAsync", [("token", token)])
|
||||
case .statsGraphError(let error):
|
||||
return ("statsGraphError", [("error", error)])
|
||||
case .statsGraph(let json):
|
||||
return ("statsGraph", [("json", json)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_statsGraphAsync(_ reader: BufferReader) -> StatsGraph? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StatsGraph.statsGraphAsync(token: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_statsGraphError(_ reader: BufferReader) -> StatsGraph? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StatsGraph.statsGraphError(error: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_statsGraph(_ reader: BufferReader) -> StatsGraph? {
|
||||
var _1: Api.DataJSON?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StatsGraph.statsGraph(json: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum PageTableCell: TypeConstructorDescription {
|
||||
case pageTableCell(flags: Int32, text: Api.RichText?, colspan: Int32?, rowspan: Int32?)
|
||||
@ -13806,44 +13662,6 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsPercentValue: TypeConstructorDescription {
|
||||
case statsPercentValue(part: Double, total: Double)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .statsPercentValue(let part, let total):
|
||||
if boxed {
|
||||
buffer.appendInt32(-875679776)
|
||||
}
|
||||
serializeDouble(part, buffer: buffer, boxed: false)
|
||||
serializeDouble(total, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .statsPercentValue(let part, let total):
|
||||
return ("statsPercentValue", [("part", part), ("total", total)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_statsPercentValue(_ reader: BufferReader) -> StatsPercentValue? {
|
||||
var _1: Double?
|
||||
_1 = reader.readDouble()
|
||||
var _2: Double?
|
||||
_2 = reader.readDouble()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StatsPercentValue.statsPercentValue(part: _1!, total: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum ChannelLocation: TypeConstructorDescription {
|
||||
case channelLocationEmpty
|
||||
@ -14616,44 +14434,6 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsDateRangeDays: TypeConstructorDescription {
|
||||
case statsDateRangeDays(minDate: Int32, maxDate: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .statsDateRangeDays(let minDate, let maxDate):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1237848657)
|
||||
}
|
||||
serializeInt32(minDate, buffer: buffer, boxed: false)
|
||||
serializeInt32(maxDate, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .statsDateRangeDays(let minDate, let maxDate):
|
||||
return ("statsDateRangeDays", [("minDate", minDate), ("maxDate", maxDate)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_statsDateRangeDays(_ reader: BufferReader) -> StatsDateRangeDays? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StatsDateRangeDays.statsDateRangeDays(minDate: _1!, maxDate: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum MessageFwdAuthor: TypeConstructorDescription {
|
||||
case messageFwdAuthor(channelId: Int32)
|
||||
@ -15318,44 +15098,6 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsAbsValueAndPrev: TypeConstructorDescription {
|
||||
case statsAbsValueAndPrev(current: Double, previous: Double)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .statsAbsValueAndPrev(let current, let previous):
|
||||
if boxed {
|
||||
buffer.appendInt32(-884757282)
|
||||
}
|
||||
serializeDouble(current, buffer: buffer, boxed: false)
|
||||
serializeDouble(previous, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .statsAbsValueAndPrev(let current, let previous):
|
||||
return ("statsAbsValueAndPrev", [("current", current), ("previous", previous)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_statsAbsValueAndPrev(_ reader: BufferReader) -> StatsAbsValueAndPrev? {
|
||||
var _1: Double?
|
||||
_1 = reader.readDouble()
|
||||
var _2: Double?
|
||||
_2 = reader.readDouble()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StatsAbsValueAndPrev.statsAbsValueAndPrev(current: _1!, previous: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum MessageMedia: TypeConstructorDescription {
|
||||
case messageMediaEmpty
|
||||
@ -17110,58 +16852,6 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum DialogFilter: TypeConstructorDescription {
|
||||
case dialogFilter(flags: Int32, id: Int32, title: String, includePeers: [Api.InputPeer])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .dialogFilter(let flags, let id, let title, let includePeers):
|
||||
if boxed {
|
||||
buffer.appendInt32(351868460)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
serializeString(title, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(includePeers.count))
|
||||
for item in includePeers {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .dialogFilter(let flags, let id, let title, let includePeers):
|
||||
return ("dialogFilter", [("flags", flags), ("id", id), ("title", title), ("includePeers", includePeers)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_dialogFilter(_ reader: BufferReader) -> DialogFilter? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: [Api.InputPeer]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.DialogFilter.dialogFilter(flags: _1!, id: _2!, title: _3!, includePeers: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum InputPaymentCredentials: TypeConstructorDescription {
|
||||
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
|
||||
@ -21228,54 +20918,6 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum StatsRowAbsValueAndPrev: TypeConstructorDescription {
|
||||
case statsRowAbsValueAndPrev(id: String, title: String, shortTitle: String, values: Api.StatsAbsValueAndPrev)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .statsRowAbsValueAndPrev(let id, let title, let shortTitle, let values):
|
||||
if boxed {
|
||||
buffer.appendInt32(-581804346)
|
||||
}
|
||||
serializeString(id, buffer: buffer, boxed: false)
|
||||
serializeString(title, buffer: buffer, boxed: false)
|
||||
serializeString(shortTitle, buffer: buffer, boxed: false)
|
||||
values.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .statsRowAbsValueAndPrev(let id, let title, let shortTitle, let values):
|
||||
return ("statsRowAbsValueAndPrev", [("id", id), ("title", title), ("shortTitle", shortTitle), ("values", values)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_statsRowAbsValueAndPrev(_ reader: BufferReader) -> StatsRowAbsValueAndPrev? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: String?
|
||||
_2 = parseString(reader)
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.StatsRowAbsValueAndPrev.statsRowAbsValueAndPrev(id: _1!, title: _2!, shortTitle: _3!, values: _4!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum ChatOnlines: TypeConstructorDescription {
|
||||
case chatOnlines(onlines: Int32)
|
||||
|
@ -537,130 +537,6 @@ public struct payments {
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
public struct stats {
|
||||
public enum BroadcastStats: TypeConstructorDescription {
|
||||
case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, viewsBySource: [Api.StatsRowAbsValueAndPrev], newFollowersBySource: [Api.StatsRowAbsValueAndPrev], languages: [Api.StatsRowAbsValueAndPrev], growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let viewsBySource, let newFollowersBySource, let languages, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph):
|
||||
if boxed {
|
||||
buffer.appendInt32(205195937)
|
||||
}
|
||||
period.serialize(buffer, true)
|
||||
followers.serialize(buffer, true)
|
||||
viewsPerPost.serialize(buffer, true)
|
||||
sharesPerPost.serialize(buffer, true)
|
||||
enabledNotifications.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(viewsBySource.count))
|
||||
for item in viewsBySource {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(newFollowersBySource.count))
|
||||
for item in newFollowersBySource {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(languages.count))
|
||||
for item in languages {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
growthGraph.serialize(buffer, true)
|
||||
followersGraph.serialize(buffer, true)
|
||||
muteGraph.serialize(buffer, true)
|
||||
topHoursGraph.serialize(buffer, true)
|
||||
interactionsGraph.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let viewsBySource, let newFollowersBySource, let languages, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph):
|
||||
return ("broadcastStats", [("period", period), ("followers", followers), ("viewsPerPost", viewsPerPost), ("sharesPerPost", sharesPerPost), ("enabledNotifications", enabledNotifications), ("viewsBySource", viewsBySource), ("newFollowersBySource", newFollowersBySource), ("languages", languages), ("growthGraph", growthGraph), ("followersGraph", followersGraph), ("muteGraph", muteGraph), ("topHoursGraph", topHoursGraph), ("interactionsGraph", interactionsGraph)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_broadcastStats(_ reader: BufferReader) -> BroadcastStats? {
|
||||
var _1: Api.StatsDateRangeDays?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays
|
||||
}
|
||||
var _2: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _3: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _4: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _5: Api.StatsPercentValue?
|
||||
if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue
|
||||
}
|
||||
var _6: [Api.StatsRowAbsValueAndPrev]?
|
||||
if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsRowAbsValueAndPrev.self)
|
||||
}
|
||||
var _7: [Api.StatsRowAbsValueAndPrev]?
|
||||
if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsRowAbsValueAndPrev.self)
|
||||
}
|
||||
var _8: [Api.StatsRowAbsValueAndPrev]?
|
||||
if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StatsRowAbsValueAndPrev.self)
|
||||
}
|
||||
var _9: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_9 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _10: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_10 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _11: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_11 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _12: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_12 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _13: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_13 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
let _c10 = _10 != nil
|
||||
let _c11 = _11 != nil
|
||||
let _c12 = _12 != nil
|
||||
let _c13 = _13 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 {
|
||||
return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, enabledNotifications: _5!, viewsBySource: _6!, newFollowersBySource: _7!, languages: _8!, growthGraph: _9!, followersGraph: _10!, muteGraph: _11!, topHoursGraph: _12!, interactionsGraph: _13!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
public struct auth {
|
||||
public enum LoginToken: TypeConstructorDescription {
|
||||
case loginToken(expires: Int32, token: Buffer)
|
||||
|
@ -3214,81 +3214,6 @@ public extension Api {
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func getDialogFilters() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<[Api.DialogFilter]>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-241247891)
|
||||
|
||||
return (FunctionDescription(name: "messages.getDialogFilters", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> [Api.DialogFilter]? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: [Api.DialogFilter]?
|
||||
if let _ = reader.readInt32() {
|
||||
result = Api.parseVector(reader, elementSignature: 0, elementType: Api.DialogFilter.self)
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func updateDialogFilter(flags: Int32, id: Int32, filter: Api.DialogFilter?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(450142282)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(id, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {filter!.serialize(buffer, true)}
|
||||
return (FunctionDescription(name: "messages.updateDialogFilter", parameters: [("flags", flags), ("id", id), ("filter", filter)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func updateDialogFiltersOrder(order: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-983318044)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(order.count))
|
||||
for item in order {
|
||||
serializeInt32(item, buffer: buffer, boxed: false)
|
||||
}
|
||||
return (FunctionDescription(name: "messages.updateDialogFiltersOrder", parameters: [("order", order)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Bool
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public static func inputMediaDice() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.InputMedia>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1358977017)
|
||||
|
||||
return (FunctionDescription(name: "inputMediaDice", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.InputMedia? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.InputMedia?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.InputMedia
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func messageMediaDice(value: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.MessageMedia>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1670374507)
|
||||
serializeInt32(value, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "messageMediaDice", parameters: [("value", value)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.MessageMedia? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.MessageMedia?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.MessageMedia
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
public struct channels {
|
||||
public static func readHistory(channel: Api.InputChannel, maxId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
@ -3975,36 +3900,6 @@ public extension Api {
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct stats {
|
||||
public static func getBroadcastStats(flags: Int32, channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stats.BroadcastStats>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1421720550)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
channel.serialize(buffer, true)
|
||||
return (FunctionDescription(name: "stats.getBroadcastStats", parameters: [("flags", flags), ("channel", channel)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.stats.BroadcastStats? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.stats.BroadcastStats?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.stats.BroadcastStats
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
public static func loadAsyncGraph(token: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.StatsGraph>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1749505346)
|
||||
serializeString(token, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "stats.loadAsyncGraph", parameters: [("token", token)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.StatsGraph? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public struct auth {
|
||||
public static func checkPhone(phoneNumber: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.CheckedPhone>) {
|
||||
let buffer = Buffer()
|
||||
|
@ -1044,7 +1044,7 @@ public class Account {
|
||||
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.postbox, network: self.network).start())
|
||||
self.managedOperationsDisposable.add(managedApplyPendingScheduledMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network).start())
|
||||
//self.managedOperationsDisposable.add(managedChatListFilters(postbox: self.postbox, network: self.network).start())
|
||||
|
||||
let importantBackgroundOperations: [Signal<AccountRunningImportantTasks, NoError>] = [
|
||||
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
|
||||
|
@ -152,7 +152,7 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(EmbeddedMediaStickersMessageAttribute.self, f: { EmbeddedMediaStickersMessageAttribute(decoder: $0) })
|
||||
declareEncodable(TelegramMediaWebpageAttribute.self, f: { TelegramMediaWebpageAttribute(decoder: $0) })
|
||||
declareEncodable(CachedPollOptionResult.self, f: { CachedPollOptionResult(decoder: $0) })
|
||||
declareEncodable(ChatListFiltersState.self, f: { ChatListFiltersState(decoder: $0) })
|
||||
//declareEncodable(ChatListFiltersState.self, f: { ChatListFiltersState(decoder: $0) })
|
||||
|
||||
return
|
||||
}()
|
||||
|
@ -133,7 +133,8 @@ public struct ChannelStatsContextState: Equatable {
|
||||
}
|
||||
|
||||
private func requestStats(network: Network, datacenterId: Int32, peer: Peer, dark: Bool = false) -> Signal<ChannelStats?, NoError> {
|
||||
guard let inputChannel = apiInputChannel(peer) else {
|
||||
return .never()
|
||||
/*guard let inputChannel = apiInputChannel(peer) else {
|
||||
return .never()
|
||||
}
|
||||
|
||||
@ -159,11 +160,12 @@ private func requestStats(network: Network, datacenterId: Int32, peer: Peer, dar
|
||||
}
|
||||
|> `catch` { _ -> Signal<ChannelStats?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private func requestGraph(network: Network, datacenterId: Int32, token: String) -> Signal<ChannelStatsGraph?, NoError> {
|
||||
let signal: Signal<Api.StatsGraph, MTRpcError>
|
||||
return .never()
|
||||
/*let signal: Signal<Api.StatsGraph, MTRpcError>
|
||||
if network.datacenterId != datacenterId {
|
||||
signal = network.download(datacenterId: Int(datacenterId), isMedia: false, tag: nil)
|
||||
|> castError(MTRpcError.self)
|
||||
@ -180,7 +182,7 @@ private func requestGraph(network: Network, datacenterId: Int32, token: String)
|
||||
}
|
||||
|> `catch` { _ -> Signal<ChannelStatsGraph?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
private final class ChannelStatsContextImpl {
|
||||
@ -361,7 +363,7 @@ public final class ChannelStatsContext {
|
||||
}
|
||||
}
|
||||
|
||||
extension ChannelStatsGraph {
|
||||
/*extension ChannelStatsGraph {
|
||||
init(apiStatsGraph: Api.StatsGraph) {
|
||||
switch apiStatsGraph {
|
||||
case let .statsGraph(json):
|
||||
@ -422,3 +424,4 @@ extension ChannelStats {
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -140,7 +140,7 @@ public struct ChatListFilter: PostboxCoding, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
extension ChatListFilter {
|
||||
/*extension ChatListFilter {
|
||||
init(apiFilter: Api.DialogFilter) {
|
||||
switch apiFilter {
|
||||
case let .dialogFilter(flags, id, title, includePeers):
|
||||
@ -179,13 +179,13 @@ extension ChatListFilter {
|
||||
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||
})
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
public enum RequestUpdateChatListFilterError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public func requestUpdateChatListFilter(account: Account, id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
|
||||
/*public func requestUpdateChatListFilter(account: Account, id: Int32, filter: ChatListFilter?) -> Signal<Never, RequestUpdateChatListFilterError> {
|
||||
return account.postbox.transaction { transaction -> Api.DialogFilter? in
|
||||
return filter?.apiFilter(transaction: transaction)
|
||||
}
|
||||
@ -376,3 +376,4 @@ public func updateChatListFilterSettingsInteractively(postbox: Postbox, _ f: @es
|
||||
return result ?? .default
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -5,7 +5,6 @@ import TelegramCore
|
||||
import SyncCore
|
||||
import UserNotifications
|
||||
import Intents
|
||||
//import HockeySDK
|
||||
import Postbox
|
||||
import PushKit
|
||||
import AsyncDisplayKit
|
||||
@ -157,12 +156,13 @@ final class SharedApplicationContext {
|
||||
}
|
||||
}
|
||||
|
||||
@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate/*, BITHockeyManagerDelegate*/, UNUserNotificationCenterDelegate, UIAlertViewDelegate {
|
||||
@objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate, UIAlertViewDelegate {
|
||||
@objc var window: UIWindow?
|
||||
var nativeWindow: (UIWindow & WindowHost)?
|
||||
var mainWindow: Window1!
|
||||
private var dataImportSplash: LegacyDataImportSplash?
|
||||
|
||||
private var buildConfig: BuildConfig?
|
||||
let episodeId = arc4random()
|
||||
|
||||
private let isInForegroundPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
@ -369,6 +369,7 @@ final class SharedApplicationContext {
|
||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
|
||||
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
||||
self.buildConfig = buildConfig
|
||||
let signatureDict = BuildConfigExtra.signatureDict()
|
||||
|
||||
let apiId: Int32 = buildConfig.apiId
|
||||
@ -1338,46 +1339,6 @@ final class SharedApplicationContext {
|
||||
self.isActivePromise.set(true)
|
||||
}
|
||||
|
||||
/*BITHockeyBaseManager.setPresentAlert({ [weak self] alert in
|
||||
if let strongSelf = self, let alert = alert {
|
||||
var actions: [TextAlertAction] = []
|
||||
for action in alert.actions {
|
||||
let isDefault = action.style == .default
|
||||
actions.append(TextAlertAction(type: isDefault ? .defaultAction : .genericAction, title: action.title ?? "", action: {
|
||||
if let action = action as? BITAlertAction {
|
||||
action.invokeAction()
|
||||
}
|
||||
}))
|
||||
}
|
||||
if let sharedContext = strongSelf.contextValue?.context.sharedContext {
|
||||
let presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||
strongSelf.mainWindow.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: alert.title, text: alert.message ?? "", actions: actions), on: .root)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
BITHockeyBaseManager.setPresentView({ [weak self] controller in
|
||||
if let strongSelf = self, let controller = controller {
|
||||
let parent = LegacyController(presentation: .modal(animateIn: true), theme: nil)
|
||||
let navigationController = UINavigationController(rootViewController: controller)
|
||||
controller.navigation_setDismiss({ [weak parent] in
|
||||
parent?.dismiss()
|
||||
}, rootController: nil)
|
||||
parent.bind(controller: navigationController)
|
||||
strongSelf.mainWindow.present(parent, on: .root)
|
||||
}
|
||||
})
|
||||
|
||||
if let hockeyAppId = buildConfig.hockeyAppId, !hockeyAppId.isEmpty {
|
||||
BITHockeyManager.shared().configure(withIdentifier: hockeyAppId, delegate: self)
|
||||
BITHockeyManager.shared().crashManager.crashManagerStatus = .alwaysAsk
|
||||
BITHockeyManager.shared().start()
|
||||
#if targetEnvironment(simulator)
|
||||
#else
|
||||
BITHockeyManager.shared().authenticator.authenticateInstallation()
|
||||
#endif
|
||||
}*/
|
||||
|
||||
if UIApplication.shared.isStatusBarHidden {
|
||||
UIApplication.shared.setStatusBarHidden(false, with: .none)
|
||||
}
|
||||
@ -1403,6 +1364,8 @@ final class SharedApplicationContext {
|
||||
})
|
||||
}*/
|
||||
|
||||
self.maybeCheckForUpdates()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1486,6 +1449,8 @@ final class SharedApplicationContext {
|
||||
self.isInForegroundPromise.set(true)
|
||||
self.isActiveValue = true
|
||||
self.isActivePromise.set(true)
|
||||
|
||||
self.maybeCheckForUpdates()
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
@ -2204,6 +2169,60 @@ final class SharedApplicationContext {
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
private var lastCheckForUpdatesTimestamp: Double?
|
||||
private let currentCheckForUpdatesDisposable = MetaDisposable()
|
||||
|
||||
private func maybeCheckForUpdates() {
|
||||
#if targetEnvironment(simulator)
|
||||
return;
|
||||
#endif
|
||||
|
||||
guard let buildConfig = self.buildConfig, !buildConfig.isAppStoreBuild, let appCenterId = buildConfig.appCenterId, !appCenterId.isEmpty else {
|
||||
return
|
||||
}
|
||||
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||
if self.lastCheckForUpdatesTimestamp == nil || self.lastCheckForUpdatesTimestamp! < timestamp - 10.0 * 60.0 {
|
||||
self.lastCheckForUpdatesTimestamp = timestamp
|
||||
|
||||
if let url = URL(string: "https://api.appcenter.ms/v0.1/public/sdk/apps/\(appCenterId)/releases/latest") {
|
||||
self.currentCheckForUpdatesDisposable.set((downloadHTTPData(url: url)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) else {
|
||||
return
|
||||
}
|
||||
guard let dict = json as? [String: Any] else {
|
||||
return
|
||||
}
|
||||
guard let versionString = dict["version"] as? String, let version = Int(versionString) else {
|
||||
return
|
||||
}
|
||||
guard let releaseNotesUrl = dict["release_notes_url"] as? String else {
|
||||
return
|
||||
}
|
||||
guard let currentVersionString = Bundle.main.infoDictionary?["CFBundleVersion"] as? String, let currentVersion = Int(currentVersionString) else {
|
||||
return
|
||||
}
|
||||
if currentVersion < version {
|
||||
let _ = (strongSelf.sharedContextPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { sharedContext in
|
||||
let presentationData = sharedContext.sharedContext.currentPresentationData.with { $0 }
|
||||
sharedContext.sharedContext.mainWindow?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "A new build is available", actions: [
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .defaultAction, title: "Show", action: {
|
||||
sharedContext.sharedContext.applicationBindings.openUrl(releaseNotesUrl)
|
||||
})
|
||||
]), on: .root, blockInteraction: false, completion: {})
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var next: UIResponder? {
|
||||
if let context = self.contextValue, let controller = context.context.keyShortcutsController {
|
||||
return controller
|
||||
@ -2345,3 +2364,29 @@ private func messageIdFromNotification(peerId: PeerId, notification: UNNotificat
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private enum DownloadFileError {
|
||||
case network
|
||||
}
|
||||
|
||||
private func downloadHTTPData(url: URL) -> Signal<Data, DownloadFileError> {
|
||||
return Signal { subscriber in
|
||||
let completed = Atomic<Bool>(value: false)
|
||||
let downloadTask = URLSession.shared.downloadTask(with: url, completionHandler: { location, _, error in
|
||||
let _ = completed.swap(true)
|
||||
if let location = location, let data = try? Data(contentsOf: location) {
|
||||
subscriber.putNext(data)
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putError(.network)
|
||||
}
|
||||
})
|
||||
downloadTask.resume()
|
||||
|
||||
return ActionDisposable {
|
||||
if !completed.with({ $0 }) {
|
||||
downloadTask.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -2545,6 +2547,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) {
|
||||
@ -4763,6 +4766,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 {
|
||||
@ -5388,7 +5395,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)
|
||||
}
|
||||
}
|
||||
@ -7145,7 +7152,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)
|
||||
}
|
||||
}
|
||||
@ -7562,7 +7569,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)
|
||||
}
|
||||
}
|
||||
|
@ -661,7 +661,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)
|
||||
}
|
||||
}
|
||||
@ -683,7 +683,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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -2012,7 +2110,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
|
||||
private func openUsername(value: String) {
|
||||
let shareController = ShareController(context: context, subject: .url("\(value)"))
|
||||
let shareController = ShareController(context: self.context, subject: .url("https://t.me/\(value)"))
|
||||
self.view.endEditing(true)
|
||||
self.controller?.present(shareController, in: .window(.root))
|
||||
}
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit a09896b3e72e76681c12e80572a7d570108cf885
|
||||
Subproject commit 0ee2e9c5843257ccd11672611829b9bb5d02aa98
|
Loading…
x
Reference in New Issue
Block a user