Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2020-02-11 22:45:04 +04:00
commit 606076c877
54 changed files with 1513 additions and 817 deletions

View File

@ -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,

View File

@ -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}" \

View File

@ -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

View File

@ -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"],

View File

@ -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;

View File

@ -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 {

View File

@ -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)
}
})

View File

@ -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) {
}
}*/
}

View File

@ -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
}
*/

View File

@ -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
}
*/

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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)
}
}
})
}

View File

@ -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)
}
})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()
}

View File

@ -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)
}
}))

View File

@ -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

View File

@ -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: {

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)
}
})

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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 : [] },

View File

@ -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
}()

View File

@ -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 {
}
}
}
*/

View File

@ -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
}
}
*/

View File

@ -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

View File

@ -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()
}
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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)
}
})

View File

@ -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)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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))

View File

@ -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()

View File

@ -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 {

View File

@ -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 {

View File

@ -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
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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