Revert "Revert to release-7.0.1"

This reverts commit 5a32d25fc621639ac3950274b912b361d68e3908.
This commit is contained in:
Ali 2020-09-26 12:34:47 +04:00
parent 9e892ca79a
commit a9c67e4210
337 changed files with 24280 additions and 11996 deletions

View File

@ -3,7 +3,7 @@
include Utils.makefile
APP_VERSION="7.0.1"
APP_VERSION="7.1"
CORE_COUNT=$(shell sysctl -n hw.logicalcpu)
CORE_COUNT_MINUS_ONE=$(shell expr ${CORE_COUNT} \- 1)

View File

@ -108,7 +108,8 @@ dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _
apiEnvironment = [apiEnvironment withUpdatedSocksProxySettings:[[MTSocksProxySettings alloc] initWithIp:proxyConnection.host port:(uint16_t)proxyConnection.port username:proxyConnection.username password:proxyConnection.password secret:proxyConnection.secret]];
}
MTContext *context = [[MTContext alloc] initWithSerialization:serialization encryptionProvider:[[EmptyEncryptionProvider alloc] init] apiEnvironment:apiEnvironment isTestingEnvironment:account.isTestingEnvironment useTempAuthKeys:false];
MTContext *context = [[MTContext alloc] initWithSerialization:serialization encryptionProvider:[[EmptyEncryptionProvider alloc] init] apiEnvironment:apiEnvironment isTestingEnvironment:account.isTestingEnvironment useTempAuthKeys:true];
context.tempKeyExpiration = 10 * 60 * 60;
NSDictionary *seedAddressList = @{};
@ -153,7 +154,7 @@ dispatch_block_t fetchImage(BuildConfig *buildConfig, AccountProxyConnection * _
for (NSNumber *datacenterId in account.datacenters) {
AccountDatacenterInfo *info = account.datacenters[datacenterId];
[context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:[[MTDatacenterAuthInfo alloc] initWithAuthKey:info.masterKey.data authKeyId:info.masterKey.keyId saltSet:@[] authKeyAttributes:@{} mainTempAuthKey:nil mediaTempAuthKey:nil]];
[context updateAuthInfoForDatacenterWithId:[datacenterId intValue] authInfo:[[MTDatacenterAuthInfo alloc] initWithAuthKey:info.masterKey.data authKeyId:info.masterKey.keyId saltSet:@[] authKeyAttributes:@{}] selector:MTDatacenterAuthInfoSelectorPersistent];
}
MTProto *mtProto = [[MTProto alloc] initWithContext:context datacenterId:datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0];

View File

@ -3,7 +3,7 @@
@implementation Serialization
- (NSUInteger)currentLayer {
return 118;
return 119;
}
- (id _Nullable)parseMessage:(NSData * _Nullable)data {

View File

@ -2607,6 +2607,7 @@ Unused sets are archived when you add more.";
"Channel.AdminLog.CanInviteUsers" = "Add Users";
"Channel.AdminLog.CanPinMessages" = "Pin Messages";
"Channel.AdminLog.CanAddAdmins" = "Add New Admins";
"Channel.AdminLog.CanBeAnonymous" = "Remain Anonymous";
"Channel.AdminLog.CanEditMessages" = "Edit Messages";
"Channel.AdminLog.MessageToggleInvitesOn" = "%@ enabled group invites";
@ -5741,3 +5742,31 @@ Any member of this group will be able to see messages in the channel.";
"AccessDenied.VideoCallCamera" = "Telegram needs access to your camera to make video calls.\n\nPlease go to Settings > Privacy > Camera and set Telegram to ON.";
"Call.AccountIsLoggedOnCurrentDevice" = "Sorry, you can't call %@ because that account is logged in to Telegram on the device you're using for the call.";
"ChatList.Search.FilterMedia" = "Media";
"ChatList.Search.FilterLinks" = "Links";
"ChatList.Search.FilterFiles" = "Files";
"ChatList.Search.FilterMusic" = "Music";
"ChatList.Search.FilterVoice" = "Voice";
"ChatList.Search.NoResults" = "No Results";
"ChatList.Search.NoResultsQueryDescription" = "There were no results for \"%@\".\nTry a new search.";
"ChatList.Search.NoResultsDescription" = "There were no results.\nTry a new search.";
"ChatList.Search.NoResultsFilter" = "Nothing Yet";
"ChatList.Search.NoResultsFitlerMedia" = "Photos and videos from all your chats will be shown here.";
"ChatList.Search.NoResultsFitlerLinks" = "Links from all your chats will be shown here.";
"ChatList.Search.NoResultsFitlerFiles" = "Files from all your chats will be shown here.";
"ChatList.Search.NoResultsFitlerMusic" = "Music from all your chats will be shown here.";
"ChatList.Search.NoResultsFitlerVoice" = "Voice and video messages from all your chats will be shown here.";
"ChatList.Search.Messages_0" = "%@ messages";
"ChatList.Search.Messages_1" = "%@ message";
"ChatList.Search.Messages_2" = "%@ messages";
"ChatList.Search.Messages_3_10" = "%@ messages";
"ChatList.Search.Messages_many" = "%@ messages";
"ChatList.Search.Messages_any" = "%@ messages";
"Conversation.InputTextAnonymousPlaceholder" = "Send anonymously";
"DialogList.Replies" = "Replies";

File diff suppressed because it is too large Load Diff

View File

@ -170,6 +170,7 @@ public enum ResolvedUrl {
case botStart(peerId: PeerId, payload: String)
case groupBotStart(peerId: PeerId, payload: String)
case channelMessage(peerId: PeerId, messageId: MessageId)
case replyThreadMessage(replyThreadMessageId: MessageId, isChannelPost: Bool, maxReadMessageId: MessageId?, messageId: MessageId)
case stickerPack(name: String)
case instantView(TelegramMediaWebpage, String?)
case proxy(host: String, port: Int32, username: String?, password: String?, secret: Data?)
@ -249,6 +250,12 @@ public enum ChatSearchDomain: Equatable {
}
}
}
public enum ChatLocation: Equatable {
case peer(PeerId)
case replyThread(threadMessageId: MessageId, isChannelPost: Bool, maxReadMessageId: MessageId?)
}
public final class NavigateToChatControllerParams {
public let navigationController: NavigationController
public let chatController: ChatController?
@ -522,8 +529,8 @@ public protocol SharedAccountContext: class {
func handleTextLinkAction(context: AccountContext, peerId: PeerId?, navigateDisposable: MetaDisposable, controller: ViewController, action: TextLinkItemActionType, itemLink: TextLinkItem)
func navigateToChat(accountId: AccountRecordId, peerId: PeerId, messageId: MessageId?)
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 messageFromPreloadedChatHistoryViewForLocation(id: MessageId, location: ChatHistoryLocationInput, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?) -> Signal<(MessageIndex?, Bool), NoError>
func makeOverlayAudioPlayerController(context: AccountContext, peerId: PeerId, type: MediaManagerPlayerType, initialMessageId: MessageId, initialOrder: MusicPlaybackSettingsOrder, isGlobalSearch: Bool, parentNavigationController: NavigationController?) -> ViewController & OverlayAudioPlayerController
func makePeerInfoController(context: AccountContext, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, fromChat: Bool) -> ViewController?
func makeChannelAdminController(context: AccountContext, peerId: PeerId, adminId: PeerId, initialParticipant: ChannelParticipant) -> ViewController?
func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController
@ -632,6 +639,9 @@ public final class TonContext {
#endif
public protocol ChatLocationContextHolder: class {
}
public protocol AccountContext: class {
var sharedContext: SharedAccountContext { get }
var account: Account { get }
@ -659,4 +669,7 @@ public protocol AccountContext: class {
func storeSecureIdPassword(password: String)
func getStoredSecureIdPassword() -> String?
func chatLocationInput(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>) -> ChatLocationInput
func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>, messageIndex: MessageIndex)
}

View File

@ -10,6 +10,93 @@ import SwiftSignalKit
import TelegramPresentationData
import TelegramUIPreferences
public final class ChatMessageItemAssociatedData: Equatable {
public enum ChannelDiscussionGroupStatus: Equatable {
case unknown
case known(PeerId?)
}
public let automaticDownloadPeerType: MediaAutoDownloadPeerType
public let automaticDownloadNetworkType: MediaAutoDownloadNetworkType
public let isRecentActions: Bool
public let isScheduledMessages: Bool
public let contactsPeerIds: Set<PeerId>
public let channelDiscussionGroup: ChannelDiscussionGroupStatus
public let animatedEmojiStickers: [String: [StickerPackItem]]
public let forcedResourceStatus: FileMediaResourceStatus?
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, isScheduledMessages: Bool = false, contactsPeerIds: Set<PeerId> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil) {
self.automaticDownloadPeerType = automaticDownloadPeerType
self.automaticDownloadNetworkType = automaticDownloadNetworkType
self.isRecentActions = isRecentActions
self.isScheduledMessages = isScheduledMessages
self.contactsPeerIds = contactsPeerIds
self.channelDiscussionGroup = channelDiscussionGroup
self.animatedEmojiStickers = animatedEmojiStickers
self.forcedResourceStatus = forcedResourceStatus
}
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
if lhs.automaticDownloadPeerType != rhs.automaticDownloadPeerType {
return false
}
if lhs.automaticDownloadNetworkType != rhs.automaticDownloadNetworkType {
return false
}
if lhs.isRecentActions != rhs.isRecentActions {
return false
}
if lhs.isScheduledMessages != rhs.isScheduledMessages {
return false
}
if lhs.contactsPeerIds != rhs.contactsPeerIds {
return false
}
if lhs.channelDiscussionGroup != rhs.channelDiscussionGroup {
return false
}
if lhs.animatedEmojiStickers != rhs.animatedEmojiStickers {
return false
}
if lhs.forcedResourceStatus != rhs.forcedResourceStatus {
return false
}
return true
}
}
public enum ChatControllerInteractionLongTapAction {
case url(String)
case mention(String)
case peerMention(PeerId, String)
case command(String)
case hashtag(String)
case timecode(Double, String)
case bankCard(String)
}
public enum ChatHistoryMessageSelection: Equatable {
case none
case selectable(selected: Bool)
public static func ==(lhs: ChatHistoryMessageSelection, rhs: ChatHistoryMessageSelection) -> Bool {
switch lhs {
case .none:
if case .none = rhs {
return true
} else {
return false
}
case let .selectable(selected):
if case .selectable(selected) = rhs {
return true
} else {
return false
}
}
}
}
public enum ChatControllerInitialBotStartBehavior {
case interactive
case automatic(returnToPeerId: PeerId, scheduled: Bool)

View File

@ -15,8 +15,8 @@ public enum ChatHistoryLocation: Equatable {
}
public struct ChatHistoryLocationInput: Equatable {
public let content: ChatHistoryLocation
public let id: Int32
public var content: ChatHistoryLocation
public var id: Int32
public init(content: ChatHistoryLocation, id: Int32) {
self.content = content

View File

@ -1,5 +1,13 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramCore
public enum GalleryControllerItemSource {
case peerMessagesAtId(messageId: MessageId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>)
case standaloneMessage(Message)
case custom(messages: Signal<([Message], Int32, Bool), NoError>, messageId: MessageId, loadMore: (() -> Void)?)
}
public final class GalleryControllerActionInteraction {
public let openUrl: (String, Bool) -> Void

View File

@ -8,6 +8,120 @@ import AsyncDisplayKit
import TelegramAudio
import UniversalMediaPlayer
public enum PeerMessagesMediaPlaylistId: Equatable, SharedMediaPlaylistId {
case peer(PeerId)
case recentActions(PeerId)
case custom
public func isEqual(to: SharedMediaPlaylistId) -> Bool {
if let to = to as? PeerMessagesMediaPlaylistId {
return self == to
}
return false
}
}
public enum PeerMessagesPlaylistLocation: Equatable, SharedMediaPlaylistLocation {
case messages(peerId: PeerId, tagMask: MessageTags, at: MessageId)
case singleMessage(MessageId)
case recentActions(Message)
case custom(messages: Signal<([Message], Int32, Bool), NoError>, at: MessageId, loadMore: (() -> Void)?)
public var playlistId: PeerMessagesMediaPlaylistId {
switch self {
case let .messages(peerId, _, _):
return .peer(peerId)
case let .singleMessage(id):
return .peer(id.peerId)
case let .recentActions(message):
return .recentActions(message.id.peerId)
case .custom:
return .custom
}
}
public var messageId: MessageId? {
switch self {
case let .messages(_, _, messageId), let .singleMessage(messageId), let .custom(_, messageId, _):
return messageId
default:
return nil
}
}
public func isEqual(to: SharedMediaPlaylistLocation) -> Bool {
if let to = to as? PeerMessagesPlaylistLocation {
return self == to
} else {
return false
}
}
public static func ==(lhs: PeerMessagesPlaylistLocation, rhs: PeerMessagesPlaylistLocation) -> Bool {
switch lhs {
case let .messages(peerId, tagMask, at):
if case .messages(peerId, tagMask, at) = rhs {
return true
} else {
return false
}
case let .singleMessage(messageId):
if case .singleMessage(messageId) = rhs {
return true
} else {
return false
}
case let .recentActions(lhsMessage):
if case let .recentActions(rhsMessage) = rhs, lhsMessage.id == rhsMessage.id {
return true
} else {
return false
}
case let .custom(_, lhsAt, _):
if case let .custom(_, rhsAt, _) = rhs, lhsAt == rhsAt {
return true
} else {
return false
}
}
}
}
public func peerMessageMediaPlayerType(_ message: Message) -> MediaManagerPlayerType? {
func extractFileMedia(_ message: Message) -> TelegramMediaFile? {
var file: TelegramMediaFile?
for media in message.media {
if let media = media as? TelegramMediaFile {
file = media
break
} else if let media = media as? TelegramMediaWebpage, case let .Loaded(content) = media.content, let f = content.file {
file = f
break
}
}
return file
}
if let file = extractFileMedia(message) {
if file.isVoice || file.isInstantVideo {
return .voice
} else if file.isMusic {
return .music
}
}
return nil
}
public func peerMessagesMediaPlaylistAndItemId(_ message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> (SharedMediaPlaylistId, SharedMediaPlaylistItemId)? {
if isGlobalSearch {
return (PeerMessagesMediaPlaylistId.custom, PeerMessagesMediaPlaylistItemId(messageId: message.id))
} else if isRecentActions {
return (PeerMessagesMediaPlaylistId.recentActions(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id))
} else {
return (PeerMessagesMediaPlaylistId.peer(message.id.peerId), PeerMessagesMediaPlaylistItemId(messageId: message.id))
}
}
public enum MediaManagerPlayerType {
case voice
case music

View File

@ -6,6 +6,7 @@ import SyncCore
import SwiftSignalKit
import Display
import AsyncDisplayKit
import UniversalMediaPlayer
public enum ChatControllerInteractionOpenMessageMode {
case `default`
@ -18,6 +19,8 @@ public enum ChatControllerInteractionOpenMessageMode {
public final class OpenChatMessageParams {
public let context: AccountContext
public let chatLocation: ChatLocation?
public let chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?
public let message: Message
public let standalone: Bool
public let reverseMessageGalleryOrder: Bool
@ -36,9 +39,13 @@ public final class OpenChatMessageParams {
public let setupTemporaryHiddenMedia: (Signal<Any?, NoError>, Int, Media) -> Void
public let chatAvatarHiddenMedia: (Signal<MessageId?, NoError>, Media) -> Void
public let actionInteraction: GalleryControllerActionInteraction?
public let playlistLocation: PeerMessagesPlaylistLocation?
public let gallerySource: GalleryControllerItemSource?
public init(
context: AccountContext,
chatLocation: ChatLocation?,
chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?,
message: Message,
standalone: Bool,
reverseMessageGalleryOrder: Bool,
@ -56,9 +63,13 @@ public final class OpenChatMessageParams {
sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?,
setupTemporaryHiddenMedia: @escaping (Signal<Any?, NoError>, Int, Media) -> Void,
chatAvatarHiddenMedia: @escaping (Signal<MessageId?, NoError>, Media) -> Void,
actionInteraction: GalleryControllerActionInteraction? = nil
actionInteraction: GalleryControllerActionInteraction? = nil,
playlistLocation: PeerMessagesPlaylistLocation? = nil,
gallerySource: GalleryControllerItemSource? = nil
) {
self.context = context
self.chatLocation = chatLocation
self.chatLocationContextHolder = chatLocationContextHolder
self.message = message
self.standalone = standalone
self.reverseMessageGalleryOrder = reverseMessageGalleryOrder
@ -77,5 +88,7 @@ public final class OpenChatMessageParams {
self.setupTemporaryHiddenMedia = setupTemporaryHiddenMedia
self.chatAvatarHiddenMedia = chatAvatarHiddenMedia
self.actionInteraction = actionInteraction
self.playlistLocation = playlistLocation
self.gallerySource = gallerySource
}
}

View File

@ -29,7 +29,7 @@ public func storedMessageFromSearch(account: Account, message: Message) -> Signa
}
}
let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)
let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)
let _ = transaction.addMessages([storeMessage], location: .Random)
}
@ -46,7 +46,7 @@ public func storeMessageFromSearch(transaction: Transaction, message: Message) {
}
}
let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)
let storeMessage = StoreMessage(id: .Id(message.id), globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: StoreMessageFlags(message.flags), tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo.flatMap(StoreMessageForwardInfo.init), authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)
let _ = transaction.addMessages([storeMessage], location: .Random)
}

View File

@ -15,6 +15,7 @@ import Emoji
private let deletedIcon = UIImage(bundleImageName: "Avatar/DeletedIcon")?.precomposed()
private let savedMessagesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/SavedMessagesIcon"), color: .white)
private let archivedChatsIcon = UIImage(bundleImageName: "Avatar/ArchiveAvatarIcon")?.precomposed()
private let repliesIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/RepliesMessagesIcon"), color: .white)
public func avatarPlaceholderFont(size: CGFloat) -> UIFont {
return Font.with(size: size, design: .round, traits: [.bold])
@ -100,6 +101,7 @@ private func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool {
private enum AvatarNodeIcon: Equatable {
case none
case savedMessagesIcon
case repliesIcon
case archivedChatsIcon(hiddenByDefault: Bool)
case editAvatarIcon
case deletedIcon
@ -109,6 +111,7 @@ public enum AvatarNodeImageOverride: Equatable {
case none
case image(TelegramMediaImageRepresentation)
case savedMessagesIcon
case repliesIcon
case archivedChatsIcon(hiddenByDefault: Bool)
case editAvatarIcon
case deletedIcon
@ -308,6 +311,9 @@ public final class AvatarNode: ASDisplayNode {
case .savedMessagesIcon:
representation = nil
icon = .savedMessagesIcon
case .repliesIcon:
representation = nil
icon = .repliesIcon
case let .archivedChatsIcon(hiddenByDefault):
representation = nil
icon = .archivedChatsIcon(hiddenByDefault: hiddenByDefault)
@ -452,6 +458,8 @@ public final class AvatarNode: ASDisplayNode {
colorsArray = grayscaleColors
} else if case .savedMessagesIcon = parameters.icon {
colorsArray = savedMessagesColors
} else if case .repliesIcon = parameters.icon {
colorsArray = savedMessagesColors
} else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme {
colorsArray = [theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor, theme.list.itemAccentColor.withAlphaComponent(0.1).cgColor]
} else if case let .archivedChatsIcon(hiddenByDefault) = parameters.icon, let theme = parameters.theme {
@ -506,6 +514,15 @@ public final class AvatarNode: ASDisplayNode {
if let savedMessagesIcon = savedMessagesIcon {
context.draw(savedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - savedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - savedMessagesIcon.size.height) / 2.0)), size: savedMessagesIcon.size))
}
} else if case .repliesIcon = parameters.icon {
let factor = bounds.size.width / 60.0
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
context.scaleBy(x: factor, y: -factor)
context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0)
if let repliesIcon = repliesIcon {
context.draw(repliesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - repliesIcon.size.width) / 2.0), y: floor((bounds.size.height - repliesIcon.size.height) / 2.0)), size: repliesIcon.size))
}
} else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme, !parameters.hasImage {
context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)

View File

@ -0,0 +1,22 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "ChatInterfaceState",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/TextFormat:TextFormat",
"//submodules/AccountContext:AccountContext",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
)

View File

@ -0,0 +1,22 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatInterfaceState",
module_name = "ChatInterfaceState",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/SyncCore:SyncCore",
"//submodules/TextFormat:TextFormat",
"//submodules/AccountContext:AccountContext",
],
visibility = [
"//visibility:public",
],
)

View File

@ -6,18 +6,23 @@ import SyncCore
import TextFormat
import AccountContext
struct ChatInterfaceSelectionState: PostboxCoding, Equatable {
let selectedIds: Set<MessageId>
public enum ChatTextInputMediaRecordingButtonMode: Int32 {
case audio = 0
case video = 1
}
public struct ChatInterfaceSelectionState: PostboxCoding, Equatable {
public let selectedIds: Set<MessageId>
static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool {
public static func ==(lhs: ChatInterfaceSelectionState, rhs: ChatInterfaceSelectionState) -> Bool {
return lhs.selectedIds == rhs.selectedIds
}
init(selectedIds: Set<MessageId>) {
public init(selectedIds: Set<MessageId>) {
self.selectedIds = selectedIds
}
init(decoder: PostboxDecoder) {
public init(decoder: PostboxDecoder) {
if let data = decoder.decodeBytesForKeyNoCopy("i") {
self.selectedIds = Set(MessageId.decodeArrayFromBuffer(data))
} else {
@ -25,27 +30,27 @@ struct ChatInterfaceSelectionState: PostboxCoding, Equatable {
}
}
func encode(_ encoder: PostboxEncoder) {
public func encode(_ encoder: PostboxEncoder) {
let buffer = WriteBuffer()
MessageId.encodeArrayToBuffer(Array(selectedIds), buffer: buffer)
encoder.encodeBytes(buffer, forKey: "i")
}
}
struct ChatEditMessageState: PostboxCoding, Equatable {
let messageId: MessageId
let inputState: ChatTextInputState
let disableUrlPreview: String?
let inputTextMaxLength: Int32?
public struct ChatEditMessageState: PostboxCoding, Equatable {
public let messageId: MessageId
public let inputState: ChatTextInputState
public let disableUrlPreview: String?
public let inputTextMaxLength: Int32?
init(messageId: MessageId, inputState: ChatTextInputState, disableUrlPreview: String?, inputTextMaxLength: Int32?) {
public init(messageId: MessageId, inputState: ChatTextInputState, disableUrlPreview: String?, inputTextMaxLength: Int32?) {
self.messageId = messageId
self.inputState = inputState
self.disableUrlPreview = disableUrlPreview
self.inputTextMaxLength = inputTextMaxLength
}
init(decoder: PostboxDecoder) {
public init(decoder: PostboxDecoder) {
self.messageId = MessageId(peerId: PeerId(decoder.decodeInt64ForKey("mp", orElse: 0)), namespace: decoder.decodeInt32ForKey("mn", orElse: 0), id: decoder.decodeInt32ForKey("mi", orElse: 0))
if let inputState = decoder.decodeObjectForKey("is", decoder: { return ChatTextInputState(decoder: $0) }) as? ChatTextInputState {
self.inputState = inputState
@ -56,7 +61,7 @@ struct ChatEditMessageState: PostboxCoding, Equatable {
self.inputTextMaxLength = decoder.decodeOptionalInt32ForKey("tl")
}
func encode(_ encoder: PostboxEncoder) {
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt64(self.messageId.peerId.toInt64(), forKey: "mp")
encoder.encodeInt32(self.messageId.namespace, forKey: "mn")
encoder.encodeInt32(self.messageId.id, forKey: "mi")
@ -73,31 +78,31 @@ struct ChatEditMessageState: PostboxCoding, Equatable {
}
}
static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool {
public static func ==(lhs: ChatEditMessageState, rhs: ChatEditMessageState) -> Bool {
return lhs.messageId == rhs.messageId && lhs.inputState == rhs.inputState && lhs.disableUrlPreview == rhs.disableUrlPreview && lhs.inputTextMaxLength == rhs.inputTextMaxLength
}
func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState {
public func withUpdatedInputState(_ inputState: ChatTextInputState) -> ChatEditMessageState {
return ChatEditMessageState(messageId: self.messageId, inputState: inputState, disableUrlPreview: self.disableUrlPreview, inputTextMaxLength: self.inputTextMaxLength)
}
func withUpdatedDisableUrlPreview(_ disableUrlPreview: String?) -> ChatEditMessageState {
public func withUpdatedDisableUrlPreview(_ disableUrlPreview: String?) -> ChatEditMessageState {
return ChatEditMessageState(messageId: self.messageId, inputState: self.inputState, disableUrlPreview: disableUrlPreview, inputTextMaxLength: self.inputTextMaxLength)
}
}
struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable {
var closedButtonKeyboardMessageId: MessageId?
var processedSetupReplyMessageId: MessageId?
var closedPinnedMessageId: MessageId?
var closedPeerSpecificPackSetup: Bool = false
var dismissedAddContactPhoneNumber: String?
public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable {
public var closedButtonKeyboardMessageId: MessageId?
public var processedSetupReplyMessageId: MessageId?
public var closedPinnedMessageId: MessageId?
public var closedPeerSpecificPackSetup: Bool = false
public var dismissedAddContactPhoneNumber: String?
var isEmpty: Bool {
public var isEmpty: Bool {
return self.closedButtonKeyboardMessageId == nil && self.processedSetupReplyMessageId == nil && self.closedPinnedMessageId == nil && self.closedPeerSpecificPackSetup == false && self.dismissedAddContactPhoneNumber == nil
}
init() {
public init() {
self.closedButtonKeyboardMessageId = nil
self.processedSetupReplyMessageId = nil
self.closedPinnedMessageId = nil
@ -105,7 +110,7 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable {
self.dismissedAddContactPhoneNumber = nil
}
init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) {
public init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) {
self.closedButtonKeyboardMessageId = closedButtonKeyboardMessageId
self.processedSetupReplyMessageId = processedSetupReplyMessageId
self.closedPinnedMessageId = closedPinnedMessageId
@ -113,7 +118,7 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable {
self.dismissedAddContactPhoneNumber = dismissedAddContactPhoneNumber
}
init(decoder: PostboxDecoder) {
public init(decoder: PostboxDecoder) {
if let closedMessageIdPeerId = decoder.decodeOptionalInt64ForKey("cb.p"), let closedMessageIdNamespace = decoder.decodeOptionalInt32ForKey("cb.n"), let closedMessageIdId = decoder.decodeOptionalInt32ForKey("cb.i") {
self.closedButtonKeyboardMessageId = MessageId(peerId: PeerId(closedMessageIdPeerId), namespace: closedMessageIdNamespace, id: closedMessageIdId)
} else {
@ -135,7 +140,7 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable {
self.closedPeerSpecificPackSetup = decoder.decodeInt32ForKey("cpss", orElse: 0) != 0
}
func encode(_ encoder: PostboxEncoder) {
public func encode(_ encoder: PostboxEncoder) {
if let closedButtonKeyboardMessageId = self.closedButtonKeyboardMessageId {
encoder.encodeInt64(closedButtonKeyboardMessageId.peerId.toInt64(), forKey: "cb.p")
encoder.encodeInt32(closedButtonKeyboardMessageId.namespace, forKey: "cb.n")
@ -176,21 +181,21 @@ struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable {
}
}
struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable {
let messageIndex: MessageIndex
let relativeOffset: Double
public struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable {
public let messageIndex: MessageIndex
public let relativeOffset: Double
init(messageIndex: MessageIndex, relativeOffset: Double) {
public init(messageIndex: MessageIndex, relativeOffset: Double) {
self.messageIndex = messageIndex
self.relativeOffset = relativeOffset
}
init(decoder: PostboxDecoder) {
public init(decoder: PostboxDecoder) {
self.messageIndex = MessageIndex(id: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("m.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("m.n", orElse: 0), id: decoder.decodeInt32ForKey("m.i", orElse: 0)), timestamp: decoder.decodeInt32ForKey("m.t", orElse: 0))
self.relativeOffset = decoder.decodeDoubleForKey("ro", orElse: 0.0)
}
func encode(_ encoder: PostboxEncoder) {
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt32(self.messageIndex.timestamp, forKey: "m.t")
encoder.encodeInt64(self.messageIndex.id.peerId.toInt64(), forKey: "m.p")
encoder.encodeInt32(self.messageIndex.id.namespace, forKey: "m.n")
@ -198,7 +203,7 @@ struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable {
encoder.encodeDouble(self.relativeOffset, forKey: "ro")
}
static func ==(lhs: ChatInterfaceHistoryScrollState, rhs: ChatInterfaceHistoryScrollState) -> Bool {
public static func ==(lhs: ChatInterfaceHistoryScrollState, rhs: ChatInterfaceHistoryScrollState) -> Bool {
if lhs.messageIndex != rhs.messageIndex {
return false
}
@ -210,18 +215,18 @@ struct ChatInterfaceHistoryScrollState: PostboxCoding, Equatable {
}
public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equatable {
let timestamp: Int32
let composeInputState: ChatTextInputState
let composeDisableUrlPreview: String?
let replyMessageId: MessageId?
let forwardMessageIds: [MessageId]?
let editMessage: ChatEditMessageState?
let selectionState: ChatInterfaceSelectionState?
let messageActionsState: ChatInterfaceMessageActionsState
let historyScrollState: ChatInterfaceHistoryScrollState?
let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode
let silentPosting: Bool
let inputLanguage: String?
public let timestamp: Int32
public let composeInputState: ChatTextInputState
public let composeDisableUrlPreview: String?
public let replyMessageId: MessageId?
public let forwardMessageIds: [MessageId]?
public let editMessage: ChatEditMessageState?
public let selectionState: ChatInterfaceSelectionState?
public let messageActionsState: ChatInterfaceMessageActionsState
public let historyScrollState: ChatInterfaceHistoryScrollState?
public let mediaRecordingMode: ChatTextInputMediaRecordingButtonMode
public let silentPosting: Bool
public let inputLanguage: String?
public var associatedMessageIds: [MessageId] {
var ids: [MessageId] = []
@ -259,7 +264,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata
return result
}
var effectiveInputState: ChatTextInputState {
public var effectiveInputState: ChatTextInputState {
if let editMessage = self.editMessage {
return editMessage.inputState
} else {
@ -282,7 +287,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata
self.inputLanguage = nil
}
init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageId: MessageId?, forwardMessageIds: [MessageId]?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) {
public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreview: String?, replyMessageId: MessageId?, forwardMessageIds: [MessageId]?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, silentPosting: Bool, inputLanguage: String?) {
self.timestamp = timestamp
self.composeInputState = composeInputState
self.composeDisableUrlPreview = composeDisableUrlPreview
@ -437,17 +442,17 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata
return lhs.composeInputState == rhs.composeInputState && lhs.replyMessageId == rhs.replyMessageId && lhs.selectionState == rhs.selectionState && lhs.editMessage == rhs.editMessage
}
func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState {
public func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState {
let updatedComposeInputState = inputState
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState {
public func withUpdatedComposeDisableUrlPreview(_ disableUrlPreview: String?) -> ChatInterfaceState {
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: disableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState {
public func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState {
var updatedEditMessage = self.editMessage
var updatedComposeInputState = self.composeInputState
if let editMessage = self.editMessage {
@ -459,15 +464,15 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState {
public func withUpdatedReplyMessageId(_ replyMessageId: MessageId?) -> ChatInterfaceState {
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withUpdatedForwardMessageIds(_ forwardMessageIds: [MessageId]?) -> ChatInterfaceState {
public func withUpdatedForwardMessageIds(_ forwardMessageIds: [MessageId]?) -> ChatInterfaceState {
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withUpdatedSelectedMessages(_ messageIds: [MessageId]) -> ChatInterfaceState {
public func withUpdatedSelectedMessages(_ messageIds: [MessageId]) -> ChatInterfaceState {
var selectedIds = Set<MessageId>()
if let selectionState = self.selectionState {
selectedIds.formUnion(selectionState.selectedIds)
@ -478,7 +483,7 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withToggledSelectedMessages(_ messageIds: [MessageId], value: Bool) -> ChatInterfaceState {
public func withToggledSelectedMessages(_ messageIds: [MessageId], value: Bool) -> ChatInterfaceState {
var selectedIds = Set<MessageId>()
if let selectionState = self.selectionState {
selectedIds.formUnion(selectionState.selectedIds)
@ -493,27 +498,27 @@ public final class ChatInterfaceState: SynchronizeableChatInterfaceState, Equata
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withoutSelectionState() -> ChatInterfaceState {
public func withoutSelectionState() -> ChatInterfaceState {
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState {
public func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState {
return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState {
public func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState {
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState {
public func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState {
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: f(self.messageActionsState), historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState {
public func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState {
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}
func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState {
public func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState {
return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreview: self.composeDisableUrlPreview, replyMessageId: self.replyMessageId, forwardMessageIds: self.forwardMessageIds, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage)
}

View File

@ -4,7 +4,7 @@ import Display
import TelegramPresentationData
import ListSectionHeaderNode
public enum ChatListSearchItemHeaderType: Int32 {
public enum ChatListSearchItemHeaderType {
case localPeers
case members
case contacts
@ -13,7 +13,6 @@ public enum ChatListSearchItemHeaderType: Int32 {
case globalPeers
case deviceContacts
case recentPeers
case messages
case phoneNumber
case exceptions
case addToExceptions
@ -22,6 +21,109 @@ public enum ChatListSearchItemHeaderType: Int32 {
case chats
case chatTypes
case faq
case messages(Int32)
fileprivate func title(strings: PresentationStrings) -> String {
switch self {
case .localPeers:
return strings.DialogList_SearchSectionDialogs
case .members:
return strings.Channel_Info_Members
case .contacts:
return strings.Contacts_TopSection
case .bots:
return strings.MemberSearch_BotSection
case .admins:
return strings.Channel_Management_Title
case .globalPeers:
return strings.DialogList_SearchSectionGlobal
case .deviceContacts:
return strings.Contacts_NotRegisteredSection
case .recentPeers:
return strings.DialogList_SearchSectionRecent
case .phoneNumber:
return strings.Contacts_PhoneNumber
case .exceptions:
return strings.GroupInfo_Permissions_Exceptions
case .addToExceptions:
return strings.Exceptions_AddToExceptions
case .mapAddress:
return strings.Map_AddressOnMap
case .nearbyVenues:
return strings.Map_PlacesNearby
case .chats:
return strings.Cache_ByPeerHeader
case .chatTypes:
return strings.ChatList_ChatTypesSection
case .faq:
return strings.Settings_FrequentlyAskedQuestions
case let .messages(count):
return strings.ChatList_Search_Messages(count)
}
}
fileprivate var id: ChatListSearchItemHeaderId {
switch self {
case .localPeers:
return .localPeers
case .members:
return .members
case .contacts:
return .contacts
case .bots:
return .bots
case .admins:
return .admins
case .globalPeers:
return .globalPeers
case .deviceContacts:
return .deviceContacts
case .recentPeers:
return .recentPeers
case .phoneNumber:
return .phoneNumber
case .exceptions:
return .exceptions
case .addToExceptions:
return .addToExceptions
case .mapAddress:
return .mapAddress
case .nearbyVenues:
return .nearbyVenues
case .chats:
return .chats
case .chatTypes:
return .chatTypes
case .faq:
return .faq
case .messages:
return .messages
}
}
}
private enum ChatListSearchItemHeaderId: Int32 {
case localPeers
case members
case contacts
case bots
case admins
case globalPeers
case deviceContacts
case recentPeers
case phoneNumber
case exceptions
case addToExceptions
case mapAddress
case nearbyVenues
case chats
case chatTypes
case faq
case messages
case photos
case links
case files
case music
}
public final class ChatListSearchItemHeader: ListViewItemHeader {
@ -37,7 +139,7 @@ public final class ChatListSearchItemHeader: ListViewItemHeader {
public init(type: ChatListSearchItemHeaderType, theme: PresentationTheme, strings: PresentationStrings, actionTitle: String? = nil, action: (() -> Void)? = nil) {
self.type = type
self.id = Int64(self.type.rawValue)
self.id = Int64(self.type.id.rawValue)
self.theme = theme
self.strings = strings
self.actionTitle = actionTitle
@ -75,43 +177,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
super.init()
switch type {
case .localPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
case .members:
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
case .contacts:
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
case .bots:
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
case .admins:
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
case .globalPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
case .deviceContacts:
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
case .messages:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
case .recentPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
case .phoneNumber:
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
case .exceptions:
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
case .addToExceptions:
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
case .mapAddress:
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
case .nearbyVenues:
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
case .chats:
self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased()
case .chatTypes:
self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased()
case .faq:
self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased()
}
self.sectionHeaderNode.title = type.title(strings: strings).uppercased()
self.sectionHeaderNode.action = actionTitle
self.sectionHeaderNode.activateAction = action
@ -127,43 +193,7 @@ public final class ChatListSearchItemHeaderNode: ListViewItemHeaderNode {
self.actionTitle = actionTitle
self.action = action
switch type {
case .localPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionDialogs.uppercased()
case .members:
self.sectionHeaderNode.title = strings.Channel_Info_Members.uppercased()
case .contacts:
self.sectionHeaderNode.title = strings.Contacts_TopSection.uppercased()
case .bots:
self.sectionHeaderNode.title = strings.MemberSearch_BotSection.uppercased()
case .admins:
self.sectionHeaderNode.title = strings.Channel_Management_Title.uppercased()
case .globalPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionGlobal.uppercased()
case .deviceContacts:
self.sectionHeaderNode.title = strings.Contacts_NotRegisteredSection.uppercased()
case .messages:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionMessages.uppercased()
case .recentPeers:
self.sectionHeaderNode.title = strings.DialogList_SearchSectionRecent.uppercased()
case .phoneNumber:
self.sectionHeaderNode.title = strings.Contacts_PhoneNumber.uppercased()
case .exceptions:
self.sectionHeaderNode.title = strings.GroupInfo_Permissions_Exceptions.uppercased()
case .addToExceptions:
self.sectionHeaderNode.title = strings.Exceptions_AddToExceptions.uppercased()
case .mapAddress:
self.sectionHeaderNode.title = strings.Map_AddressOnMap.uppercased()
case .nearbyVenues:
self.sectionHeaderNode.title = strings.Map_PlacesNearby.uppercased()
case .chats:
self.sectionHeaderNode.title = strings.Cache_ByPeerHeader.uppercased()
case .chatTypes:
self.sectionHeaderNode.title = strings.ChatList_ChatTypesSection.uppercased()
case .faq:
self.sectionHeaderNode.title = strings.Settings_FrequentlyAskedQuestions.uppercased()
}
self.sectionHeaderNode.title = type.title(strings: strings).uppercased()
self.sectionHeaderNode.action = actionTitle
self.sectionHeaderNode.activateAction = action

View File

@ -46,6 +46,15 @@ static_library(
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/TooltipUI:TooltipUI",
"//submodules/ListMessageItem:ListMessageItem",
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
"//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/GalleryData:GalleryData",
"//submodules/InstantPageUI:InstantPageUI",
"//submodules/ListSectionHeaderNode:ListSectionHeaderNode",
"//submodules/ChatInterfaceState:ChatInterfaceState",
"//submodules/GridMessageSelectionNode:GridMessageSelectionNode",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -46,6 +46,15 @@ swift_library(
"//submodules/ItemListPeerActionItem:ItemListPeerActionItem",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/TooltipUI:TooltipUI",
"//submodules/ListMessageItem:ListMessageItem",
"//submodules/ChatMessageInteractiveMediaBadge:ChatMessageInteractiveMediaBadge",
"//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/GalleryData:GalleryData",
"//submodules/InstantPageUI:InstantPageUI",
"//submodules/ListSectionHeaderNode:ListSectionHeaderNode",
"//submodules/ChatInterfaceState:ChatInterfaceState",
"//submodules/ShareController:ShareController",
"//submodules/GridMessageSelectionNode:GridMessageSelectionNode",
],
visibility = [
"//visibility:public",

View File

@ -1548,7 +1548,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
let isEmpty = resolvedItems.count <= 1 || displayTabsAtBottom
if wasEmpty != isEmpty {
if wasEmpty != isEmpty, strongSelf.displayNavigationBar {
strongSelf.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode)
if let parentController = strongSelf.parent as? TabBarController {
parentController.navigationBar?.setSecondaryContentNode(isEmpty ? nil : strongSelf.tabContainerNode)
@ -1681,20 +1681,79 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController,
if let scrollToTop = strongSelf.scrollToTop {
scrollToTop()
}
if let searchContentNode = strongSelf.searchContentNode {
strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode)
var updatedSearchOptionsImpl: ((ChatListSearchOptions?, Bool) -> Void)?
if let filterContainerNodeAndActivate = strongSelf.chatListDisplayNode.activateSearch(placeholderNode: searchContentNode.placeholderNode, navigationController: strongSelf.navigationController as? NavigationController, updatedSearchOptions: { options, hasDate in
updatedSearchOptionsImpl?(options, hasDate)
}) {
let (filterContainerNode, activate) = filterContainerNodeAndActivate
strongSelf.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: false)
if let parentController = strongSelf.parent as? TabBarController {
parentController.navigationBar?.setSecondaryContentNode(filterContainerNode, animated: true)
}
activate()
var currentHasSuggestions = true
updatedSearchOptionsImpl = { [weak self, weak filterContainerNode] options, hasSuggestions in
guard let strongSelf = self, let strongFilterContainerNode = filterContainerNode else {
return
}
if currentHasSuggestions != hasSuggestions {
currentHasSuggestions = hasSuggestions
var node: ASDisplayNode?
if let options = options, options.messageTags != nil && !hasSuggestions {
} else {
node = strongFilterContainerNode
}
strongSelf.navigationBar?.setSecondaryContentNode(node, animated: false)
if let parentController = strongSelf.parent as? TabBarController {
parentController.navigationBar?.setSecondaryContentNode(node, animated: true)
}
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
if let layout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout, transition: transition)
(strongSelf.parent as? TabBarController)?.updateLayout(transition: transition)
}
}
}
}
}
strongSelf.setDisplayNavigationBar(false, transition: .animated(duration: 0.5, curve: .spring))
let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
strongSelf.setDisplayNavigationBar(false, transition: transition)
(strongSelf.parent as? TabBarController)?.updateIsTabBarHidden(true, transition: .animated(duration: 0.4, curve: .spring))
})
}
}
public func deactivateSearch(animated: Bool) {
if !self.displayNavigationBar {
self.setDisplayNavigationBar(true, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate)
if let searchContentNode = self.searchContentNode {
self.chatListDisplayNode.deactivateSearch(placeholderNode: searchContentNode.placeholderNode, animated: animated)
}
let filtersIsEmpty: Bool
if let (resolvedItems, displayTabsAtBottom) = tabContainerData {
filtersIsEmpty = resolvedItems.count <= 1 || displayTabsAtBottom
} else {
filtersIsEmpty = true
}
self.navigationBar?.setSecondaryContentNode(filtersIsEmpty ? nil : self.tabContainerNode, animated: false)
if let parentController = self.parent as? TabBarController {
parentController.navigationBar?.setSecondaryContentNode(filtersIsEmpty ? nil : self.tabContainerNode, animated: animated)
}
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate
self.setDisplayNavigationBar(true, transition: transition)
(self.parent as? TabBarController)?.updateIsTabBarHidden(false, transition: .animated(duration: 0.4, curve: .spring))
}
}

View File

@ -200,7 +200,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
}, present: { _ in })
let items = (0 ..< 2).map { _ -> ChatListItem in
return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
return ChatListItem(presentationData: chatListPresentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: 0, messageIndex: MessageIndex(id: MessageId(peerId: peer1.id, namespace: 0, id: 0), timestamp: timestamp1)), content: .peer(messages: [Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: peer1.id, namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: timestamp1, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peer1, text: "Text", attributes: [], media: [], peers: peers, associatedMessages: SimpleDictionary(), associatedMessageIds: [])], peer: RenderedPeer(peer: peer1), combinedReadState: CombinedPeerReadState(states: [(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false))]), isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(tagSummaryCount: nil, actionsSummaryCount: nil), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: false, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
}
var itemNodes: [ChatListItemNode] = []
@ -1147,12 +1147,12 @@ final class ChatListControllerNode: ASDisplayNode {
}
}
func activateSearch(placeholderNode: SearchBarPlaceholderNode) {
func activateSearch(placeholderNode: SearchBarPlaceholderNode, navigationController: NavigationController?, updatedSearchOptions: ((ChatListSearchOptions?, Bool) -> Void)?) -> (ASDisplayNode, () -> Void)? {
guard let (containerLayout, _, _, cleanNavigationBarHeight) = self.containerLayout, let navigationBar = self.navigationBar, self.searchDisplayController == nil else {
return
return nil
}
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: ChatListSearchContainerNode(context: self.context, filter: [], groupId: self.groupId, openPeer: { [weak self] peer, dismissSearch in
let contentNode = ChatListSearchContainerNode(context: self.context, filter: [], groupId: self.groupId, openPeer: { [weak self] peer, dismissSearch in
self?.requestOpenPeerFromSearch?(peer, dismissSearch)
}, openDisabledPeer: { _ in
}, openRecentPeerOptions: { [weak self] peer in
@ -1165,25 +1165,36 @@ final class ChatListControllerNode: ASDisplayNode {
if let requestAddContact = self?.requestAddContact {
requestAddContact(phoneNumber)
}
}, peerContextAction: self.peerContextAction, present: { [weak self] c in
self?.controller?.present(c, in: .window(.root))
}), cancel: { [weak self] in
}, peerContextAction: self.peerContextAction, present: { [weak self] c, a in
self?.controller?.present(c, in: .window(.root), with: a)
}, presentInGlobalOverlay: { [weak self] c, a in
self?.controller?.presentInGlobalOverlay(c, with: a)
}, navigationController: navigationController, updatedSearchOptions: { options, hasDate in
updatedSearchOptions?(options, hasDate)
})
self.searchDisplayController = SearchDisplayController(presentationData: self.presentationData, contentNode: contentNode, cancel: { [weak self] in
if let requestDeactivateSearch = self?.requestDeactivateSearch {
requestDeactivateSearch()
}
})
self.containerNode.accessibilityElementsHidden = true
self.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
self.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
if let strongSelf = self, let strongPlaceholderNode = placeholderNode {
if isSearchBar {
strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode)
} else {
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
}
return (contentNode.filterContainerNode, { [weak self] in
guard let strongSelf = self else {
return
}
}, placeholder: placeholderNode)
strongSelf.searchDisplayController?.containerLayoutUpdated(containerLayout, navigationBarHeight: cleanNavigationBarHeight, transition: .immediate)
strongSelf.searchDisplayController?.activate(insertSubnode: { [weak self, weak placeholderNode] subnode, isSearchBar in
if let strongSelf = self, let strongPlaceholderNode = placeholderNode {
if isSearchBar {
strongPlaceholderNode.supernode?.insertSubnode(subnode, aboveSubnode: strongPlaceholderNode)
} else {
strongSelf.insertSubnode(subnode, belowSubnode: navigationBar)
}
}
}, placeholder: placeholderNode)
})
}
func deactivateSearch(placeholderNode: SearchBarPlaceholderNode, animated: Bool) {

View File

@ -625,7 +625,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
let previousContentWidth = self.scrollNode.view.contentSize.width
if self.currentParams?.presentationData.theme !== presentationData.theme {
self.selectedLineNode.image = generateImage(CGSize(width: 7.0, height: 4.0), rotatedContext: { size, context in
self.selectedLineNode.image = generateImage(CGSize(width: 8.0, height: 4.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(presentationData.theme.list.itemAccentColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,306 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import SyncCore
import Postbox
import TelegramCore
import TelegramPresentationData
enum ChatListSearchFilter: Equatable {
case media
case links
case files
case music
case voice
case peer(PeerId, String)
case date(Int32, String)
var id: Int32 {
switch self {
case .media:
return 0
case .links:
return 1
case .files:
return 2
case .music:
return 3
case .voice:
return 4
case let .peer(peerId, _):
return peerId.id
case let .date(date, _):
return date
}
}
}
private final class ItemNode: ASDisplayNode {
private let pressed: () -> Void
private let iconNode: ASImageNode
private let titleNode: ImmediateTextNode
private let buttonNode: HighlightTrackingButtonNode
private var selectionFraction: CGFloat = 0.0
private(set) var unreadCount: Int = 0
private var isReordering: Bool = false
private var theme: PresentationTheme?
init(pressed: @escaping () -> Void) {
self.pressed = pressed
let titleInset: CGFloat = 4.0
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.titleNode.insets = UIEdgeInsets(top: titleInset, left: 0.0, bottom: titleInset, right: 0.0)
self.buttonNode = HighlightTrackingButtonNode()
super.init()
self.addSubnode(self.titleNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.buttonNode)
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.iconNode.layer.removeAnimation(forKey: "opacity")
strongSelf.iconNode.alpha = 0.4
strongSelf.titleNode.layer.removeAnimation(forKey: "opacity")
strongSelf.titleNode.alpha = 0.4
} else {
strongSelf.iconNode.alpha = 1.0
strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
strongSelf.titleNode.alpha = 1.0
strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
}
@objc private func buttonPressed() {
self.pressed()
}
func update(type: ChatListSearchFilter, presentationData: PresentationData, transition: ContainedViewLayoutTransition) {
let title: String
let icon: UIImage?
let color = presentationData.theme.list.itemSecondaryTextColor
switch type {
case .media:
title = presentationData.strings.ChatList_Search_FilterMedia
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Media"), color: color)
case .links:
title = presentationData.strings.ChatList_Search_FilterLinks
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Links"), color: color)
case .files:
title = presentationData.strings.ChatList_Search_FilterFiles
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Files"), color: color)
case .music:
title = presentationData.strings.ChatList_Search_FilterMusic
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Music"), color: color)
case .voice:
title = presentationData.strings.ChatList_Search_FilterVoice
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Voice"), color: color)
case let .peer(_, displayTitle):
title = displayTitle
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/User"), color: color)
case let .date(_, displayTitle):
title = displayTitle
icon = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Calendar"), color: color)
}
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(14.0), textColor: color)
if self.theme !== presentationData.theme {
self.theme = presentationData.theme
self.iconNode.image = icon
}
}
func updateLayout(height: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
let iconInset: CGFloat = 22.0
if let image = self.iconNode.image {
self.iconNode.frame = CGRect(x: 0.0, y: floorToScreenPixels((height - image.size.height) / 2.0), width: image.size.width, height: image.size.height)
}
let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: .greatestFiniteMagnitude))
let titleFrame = CGRect(origin: CGPoint(x: -self.titleNode.insets.left + iconInset, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
self.titleNode.frame = titleFrame
return titleSize.width - self.titleNode.insets.left - self.titleNode.insets.right + iconInset
}
func updateArea(size: CGSize, sideInset: CGFloat, transition: ContainedViewLayoutTransition) {
self.buttonNode.frame = CGRect(origin: CGPoint(x: -sideInset, y: 0.0), size: CGSize(width: size.width + sideInset * 2.0, height: size.height))
self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -sideInset, bottom: 0.0, right: -sideInset)
}
}
enum ChatListSearchFilterEntryId: Hashable {
case filter(Int32)
}
enum ChatListSearchFilterEntry: Equatable {
case filter(ChatListSearchFilter)
var id: ChatListSearchFilterEntryId {
switch self {
case let .filter(filter):
return .filter(filter.id)
}
}
}
final class ChatListSearchFiltersContainerNode: ASDisplayNode {
private let scrollNode: ASScrollNode
private var itemNodes: [ChatListSearchFilterEntryId: ItemNode] = [:]
var filterPressed: ((ChatListSearchFilter) -> Void)?
private var currentParams: (size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], presentationData: PresentationData)?
override init() {
self.scrollNode = ASScrollNode()
super.init()
self.scrollNode.view.showsHorizontalScrollIndicator = false
self.scrollNode.view.scrollsToTop = false
self.scrollNode.view.delaysContentTouches = false
self.scrollNode.view.canCancelContentTouches = true
if #available(iOS 11.0, *) {
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
}
self.addSubnode(self.scrollNode)
}
func cancelAnimations() {
self.scrollNode.layer.removeAllAnimations()
}
func update(size: CGSize, sideInset: CGFloat, filters: [ChatListSearchFilterEntry], presentationData: PresentationData, transition proposedTransition: ContainedViewLayoutTransition) {
let isFirstTime = self.currentParams == nil
let transition: ContainedViewLayoutTransition = isFirstTime ? .immediate : proposedTransition
if self.currentParams?.presentationData.theme !== presentationData.theme {
self.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
}
self.currentParams = (size: size, sideInset: sideInset, filters: filters, presentationData: presentationData)
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
for i in 0 ..< filters.count {
let filter = filters[i]
if case let .filter(type) = filter {
let itemNode: ItemNode
var itemNodeTransition = transition
if let current = self.itemNodes[filter.id] {
itemNode = current
} else {
itemNodeTransition = .immediate
itemNode = ItemNode(pressed: { [weak self] in
self?.filterPressed?(type)
})
self.itemNodes[filter.id] = itemNode
}
itemNode.update(type: type, presentationData: presentationData, transition: itemNodeTransition)
}
}
var removeKeys: [ChatListSearchFilterEntryId] = []
for (id, _) in self.itemNodes {
if !filters.contains(where: { $0.id == id }) {
removeKeys.append(id)
}
}
for id in removeKeys {
if let itemNode = self.itemNodes.removeValue(forKey: id) {
transition.updateAlpha(node: itemNode, alpha: 0.0, completion: { [weak itemNode] _ in
itemNode?.removeFromSupernode()
})
transition.updateTransformScale(node: itemNode, scale: 0.1)
}
}
var tabSizes: [(ChatListSearchFilterEntryId, CGSize, ItemNode, Bool)] = []
var totalRawTabSize: CGFloat = 0.0
for filter in filters {
guard let itemNode = self.itemNodes[filter.id] else {
continue
}
let wasAdded = itemNode.supernode == nil
var itemNodeTransition = transition
if wasAdded {
itemNodeTransition = .immediate
self.scrollNode.addSubnode(itemNode)
}
let paneNodeWidth = itemNode.updateLayout(height: size.height, transition: itemNodeTransition)
let paneNodeSize = CGSize(width: paneNodeWidth, height: size.height)
tabSizes.append((filter.id, paneNodeSize, itemNode, wasAdded))
totalRawTabSize += paneNodeSize.width
}
let minSpacing: CGFloat = 24.0
var spacing = minSpacing
let resolvedSideInset: CGFloat = 16.0 + sideInset
var leftOffset: CGFloat = resolvedSideInset
var longTitlesWidth: CGFloat = resolvedSideInset
var titlesWidth: CGFloat = 0.0
for i in 0 ..< tabSizes.count {
let (_, paneNodeSize, _, _) = tabSizes[i]
longTitlesWidth += paneNodeSize.width
titlesWidth += paneNodeSize.width
if i != tabSizes.count - 1 {
longTitlesWidth += minSpacing
}
}
longTitlesWidth += resolvedSideInset
let verticalOffset: CGFloat = -3.0
for i in 0 ..< tabSizes.count {
let (_, paneNodeSize, paneNode, wasAdded) = tabSizes[i]
let itemNodeTransition = transition
let paneFrame = CGRect(origin: CGPoint(x: leftOffset, y: floor((size.height - paneNodeSize.height) / 2.0) + verticalOffset), size: paneNodeSize)
if wasAdded {
paneNode.frame = paneFrame
paneNode.alpha = 0.0
paneNode.subnodeTransform = CATransform3DMakeScale(0.1, 0.1, 1.0)
itemNodeTransition.updateSublayerTransformScale(node: paneNode, scale: 1.0)
itemNodeTransition.updateAlpha(node: paneNode, alpha: 1.0)
} else {
itemNodeTransition.updateFrameAdditive(node: paneNode, frame: paneFrame)
}
paneNode.updateArea(size: paneFrame.size, sideInset: spacing / 2.0, transition: itemNodeTransition)
paneNode.hitTestSlop = UIEdgeInsets(top: 0.0, left: -spacing / 2.0, bottom: 0.0, right: -spacing / 2.0)
leftOffset += paneNodeSize.width + spacing
}
leftOffset -= spacing
leftOffset += resolvedSideInset
self.scrollNode.view.contentSize = CGSize(width: leftOffset, height: size.height)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,182 @@
import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import SyncCore
import SwiftSignalKit
import TelegramPresentationData
import AccountContext
import AppBundle
final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
private let context: AccountContext
private var theme: PresentationTheme
private let deleteMessages: () -> Void
private let shareMessages: () -> Void
private let forwardMessages: () -> Void
private let separatorNode: ASDisplayNode
private let backgroundNode: ASDisplayNode
private let deleteButton: HighlightableButtonNode
private let forwardButton: HighlightableButtonNode
private let shareButton: HighlightableButtonNode
private var actions: ChatAvailableMessageActions?
private let canDeleteMessagesDisposable = MetaDisposable()
private var validLayout: ContainerViewLayout?
var selectedMessages = Set<MessageId>() {
didSet {
if oldValue != self.selectedMessages {
self.forwardButton.isEnabled = self.selectedMessages.count != 0
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
if self.selectedMessages.isEmpty {
self.actions = nil
if let layout = self.validLayout {
let _ = self.update(layout: layout, presentationData: presentationData, transition: .immediate)
}
self.canDeleteMessagesDisposable.set(nil)
} else {
self.canDeleteMessagesDisposable.set((self.context.sharedContext.chatAvailableMessageActions(postbox: context.account.postbox, accountPeerId: context.account.peerId, messageIds: self.selectedMessages)
|> deliverOnMainQueue).start(next: { [weak self] actions in
if let strongSelf = self {
strongSelf.actions = actions
if let layout = strongSelf.validLayout {
let _ = strongSelf.update(layout: layout, presentationData: presentationData, transition: .immediate)
}
}
}))
}
}
}
}
init(context: AccountContext, deleteMessages: @escaping () -> Void, shareMessages: @escaping () -> Void, forwardMessages: @escaping () -> Void) {
self.context = context
self.deleteMessages = deleteMessages
self.shareMessages = shareMessages
self.forwardMessages = forwardMessages
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.theme = presentationData.theme
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = presentationData.theme.chat.inputPanel.panelSeparatorColor
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = presentationData.theme.chat.inputPanel.panelBackgroundColor
self.deleteButton = HighlightableButtonNode(pointerStyle: .default)
self.deleteButton.isEnabled = false
self.deleteButton.isAccessibilityElement = true
self.deleteButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextDelete
self.forwardButton = HighlightableButtonNode(pointerStyle: .default)
self.forwardButton.isAccessibilityElement = true
self.forwardButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextForward
self.shareButton = HighlightableButtonNode(pointerStyle: .default)
self.shareButton.isEnabled = false
self.shareButton.isAccessibilityElement = true
self.shareButton.accessibilityLabel = presentationData.strings.VoiceOver_MessageContextShare
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.deleteButton)
self.addSubnode(self.forwardButton)
self.addSubnode(self.shareButton)
self.addSubnode(self.separatorNode)
self.forwardButton.isEnabled = false
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), forControlEvents: .touchUpInside)
self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside)
self.shareButton.addTarget(self, action: #selector(self.shareButtonPressed), forControlEvents: .touchUpInside)
}
deinit {
self.canDeleteMessagesDisposable.dispose()
}
func update(layout: ContainerViewLayout, presentationData: PresentationData, transition: ContainedViewLayoutTransition) -> CGFloat {
if presentationData.theme !== self.theme {
self.theme = presentationData.theme
self.backgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.forwardButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
self.shareButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionAction"), color: presentationData.theme.chat.inputPanel.panelControlDisabledColor), for: [.disabled])
}
let width = layout.size.width
let insets = layout.insets(options: [])
let leftInset = insets.left
let rightInset = insets.left
let panelHeight: CGFloat
if case .regular = layout.metrics.widthClass, case .regular = layout.metrics.heightClass {
panelHeight = 49.0
} else {
panelHeight = 45.0
}
if let actions = self.actions {
self.deleteButton.isEnabled = false
self.forwardButton.isEnabled = actions.options.contains(.forward)
self.shareButton.isEnabled = false
self.deleteButton.isEnabled = !actions.options.intersection([.deleteLocally, .deleteGlobally]).isEmpty
self.shareButton.isEnabled = !actions.options.intersection([.forward]).isEmpty
self.deleteButton.isHidden = !self.deleteButton.isEnabled
} else {
self.deleteButton.isEnabled = false
self.deleteButton.isHidden = true
self.forwardButton.isEnabled = false
self.shareButton.isEnabled = false
}
self.deleteButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: 57.0, height: panelHeight))
self.forwardButton.frame = CGRect(origin: CGPoint(x: width - rightInset - 57.0, y: 0.0), size: CGSize(width: 57.0, height: panelHeight))
self.shareButton.frame = CGRect(origin: CGPoint(x: floor((width - rightInset - 57.0) / 2.0), y: 0.0), size: CGSize(width: 57.0, height: panelHeight))
let panelHeightWithInset = panelHeight + layout.intrinsicInsets.bottom - 49.0
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeightWithInset)))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
return panelHeightWithInset
}
@objc func deleteButtonPressed() {
self.deleteMessages()
}
@objc func forwardButtonPressed() {
self.forwardMessages()
}
@objc func shareButtonPressed() {
self.shareMessages()
}
}

View File

@ -0,0 +1,144 @@
import Foundation
import TelegramPresentationData
private let telegramReleaseDate = Date(timeIntervalSince1970: 1376438400.0)
func suggestDates(for string: String, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat) -> [(Date, String?)] {
let string = string.folding(options: .diacriticInsensitive, locale: .current).trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
if string.count < 3 {
return []
}
let months: [Int: (String, String)] = [
1: (strings.Month_GenJanuary, strings.Month_ShortJanuary),
2: (strings.Month_GenFebruary, strings.Month_ShortFebruary),
3: (strings.Month_GenMarch, strings.Month_ShortMarch),
4: (strings.Month_GenApril, strings.Month_ShortApril),
5: (strings.Month_GenMay, strings.Month_ShortMay),
6: (strings.Month_GenJune, strings.Month_ShortJune),
7: (strings.Month_GenJuly, strings.Month_ShortJuly),
8: (strings.Month_GenAugust, strings.Month_ShortAugust),
9: (strings.Month_GenSeptember, strings.Month_ShortSeptember),
10: (strings.Month_GenOctober, strings.Month_ShortOctober),
11: (strings.Month_GenNovember, strings.Month_ShortNovember),
12: (strings.Month_GenDecember, strings.Month_ShortDecember)
]
let weekDays: [Int: (String, String)] = [
1: (strings.Weekday_Monday, strings.Weekday_ShortMonday),
2: (strings.Weekday_Tuesday, strings.Weekday_ShortTuesday),
3: (strings.Weekday_Wednesday, strings.Weekday_ShortWednesday),
4: (strings.Weekday_Thursday, strings.Weekday_ShortThursday),
5: (strings.Weekday_Friday, strings.Weekday_ShortFriday),
6: (strings.Weekday_Saturday, strings.Weekday_ShortSaturday),
7: (strings.Weekday_Sunday, strings.Weekday_ShortSunday.lowercased()),
]
let today = strings.Weekday_Today
let yesterday = strings.Weekday_Yesterday
let dateSeparator = dateTimeFormat.dateSeparator
var result: [(Date, String?)] = []
let calendar = Calendar.current
func getUpperDate(for date: Date) -> Date {
let components = calendar.dateComponents(in: .current, from: date)
let upperComponents = DateComponents(year: components.year, month: components.month, day: components.day, hour: 23, minute: 59, second: 59)
return calendar.date(from: upperComponents)!
}
let now = Date()
let nowComponents = calendar.dateComponents(in: .current, from: now)
guard let year = nowComponents.year else {
return []
}
let midnight = calendar.startOfDay(for: now)
if today.lowercased().hasPrefix(string) {
let todayDate = getUpperDate(for: midnight)
result.append((todayDate, today))
}
if yesterday.lowercased().hasPrefix(string) {
let yesterdayMidnight = calendar.date(byAdding: .day, value: -1, to: midnight)!
let yesterdayDate = getUpperDate(for: yesterdayMidnight)
result.append((yesterdayDate, yesterday))
}
func getUpperMonthDate(month: Int, year: Int) -> Date {
let monthComponents = DateComponents(year: year, month: month)
let date = calendar.date(from: monthComponents)!
let range = calendar.range(of: .day, in: .month, for: date)!
let numDays = range.count
let upperComponents = DateComponents(year: year, month: month, day: numDays, hour: 23, minute: 59, second: 59)
return calendar.date(from: upperComponents)!
}
let decimalRange = string.rangeOfCharacter(from: .decimalDigits)
if decimalRange != nil {
if string.count == 4, let value = Int(string), value <= year {
let date = getUpperMonthDate(month: 12, year: value)
if date > telegramReleaseDate {
result.append((date, "\(value)"))
}
} else if !dateSeparator.isEmpty && string.contains(dateSeparator) {
let stringComponents = string.components(separatedBy: dateSeparator)
if stringComponents.count > 1 {
let locale = Locale(identifier: strings.baseLanguageCode)
do {
let dd = try NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue)
if let match = dd.firstMatch(in: string, options: [], range: NSMakeRange(0, string.utf16.count)), let date = match.date, date > telegramReleaseDate {
var resultDate = date
if resultDate > now {
if let date = calendar.date(byAdding: .year, value: -1, to: resultDate) {
resultDate = date
}
}
for i in 0..<5 {
if let date = calendar.date(byAdding: .year, value: -i, to: resultDate) {
result.append((date, nil))
}
}
}
} catch {
}
}
}
} else {
for (day, value) in weekDays {
let dayName = value.0.lowercased()
let shortDayName = value.1.lowercased()
if string == shortDayName || (string.count >= shortDayName.count && dayName.hasPrefix(string)) {
var nextDateComponent = calendar.dateComponents([.hour, .minute, .second], from: now)
nextDateComponent.weekday = day + calendar.firstWeekday
if let date = calendar.nextDate(after: now, matching: nextDateComponent, matchingPolicy: .nextTime, direction: .backward) {
let upperDate = getUpperDate(for: date)
for i in 0..<5 {
if let date = calendar.date(byAdding: .hour, value: -24 * 7 * i, to: upperDate) {
if calendar.isDate(date, equalTo: now, toGranularity: .weekOfYear) {
result.append((date, value.0))
} else {
result.append((date, nil))
}
}
}
}
}
}
for (month, value) in months {
let monthName = value.0.lowercased()
let shortMonthName = value.1.lowercased()
if string == shortMonthName || (string.count >= shortMonthName.count && monthName.hasPrefix(string)) {
for i in (year - 7 ... year).reversed() {
let date = getUpperMonthDate(month: month, year: i)
if date <= now && date > telegramReleaseDate {
result.append((date, nil))
}
}
}
}
}
return result
}

View File

@ -637,7 +637,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let peer = peer {
var overrideImage: AvatarNodeImageOverride?
if peer.id == item.context.account.peerId && !displayAsMessage {
if peer.id.isReplies {
overrideImage = .repliesIcon
} else if peer.id == item.context.account.peerId && !displayAsMessage {
overrideImage = .savedMessagesIcon
} else if peer.isDeleted {
overrideImage = .deletedIcon
@ -1099,7 +1101,9 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
titleAttributedString = NSAttributedString(string: item.presentationData.strings.ChatList_ArchivedChatsTitle, font: titleFont, textColor: theme.titleColor)
} else if itemPeer.chatMainPeer?.id == item.context.account.peerId {
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleFont, textColor: theme.titleColor)
} else if let displayTitle = itemPeer.chatMainPeer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) {
} else if let id = itemPeer.chatMainPeer?.id, id.isReplies {
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: titleFont, textColor: theme.titleColor)
} else if let displayTitle = itemPeer.chatMainPeer?.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder) {
titleAttributedString = NSAttributedString(string: displayTitle, font: titleFont, textColor: item.index.messageIndex.id.peerId.namespace == Namespaces.Peer.SecretChat ? theme.secretTitleColor : theme.titleColor)
}
case .group:

View File

@ -0,0 +1,20 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "ChatMessageInteractiveMediaBadge",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TextFormat:TextFormat",
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/AppBundle:AppBundle",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
)

View File

@ -0,0 +1,20 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatMessageInteractiveMediaBadge",
module_name = "ChatMessageInteractiveMediaBadge",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/TextFormat:TextFormat",
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/AppBundle:AppBundle",
],
visibility = [
"//visibility:public",
],
)

View File

@ -7,18 +7,21 @@ import TextFormat
import RadialStatusNode
import AppBundle
enum ChatMessageInteractiveMediaDownloadState: Equatable {
private let font = Font.regular(11.0)
private let boldFont = Font.semibold(11.0)
public enum ChatMessageInteractiveMediaDownloadState: Equatable {
case remote
case fetching(progress: Float?)
case compactRemote
case compactFetching(progress: Float)
}
enum ChatMessageInteractiveMediaBadgeContent: Equatable {
public enum ChatMessageInteractiveMediaBadgeContent: Equatable {
case text(inset: CGFloat, backgroundColor: UIColor, foregroundColor: UIColor, text: NSAttributedString)
case mediaDownload(backgroundColor: UIColor, foregroundColor: UIColor, duration: String, size: String?, muted: Bool, active: Bool)
static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool {
public static func ==(lhs: ChatMessageInteractiveMediaBadgeContent, rhs: ChatMessageInteractiveMediaBadgeContent) -> Bool {
switch lhs {
case let .text(lhsInset, lhsBackgroundColor, lhsForegroundColor, lhsText):
if case let .text(rhsInset, rhsBackgroundColor, rhsForegroundColor, rhsText) = rhs, lhsInset.isEqual(to: rhsInset), lhsBackgroundColor.isEqual(rhsBackgroundColor), lhsForegroundColor.isEqual(rhsForegroundColor), lhsText.isEqual(to: rhsText) {
@ -36,12 +39,9 @@ enum ChatMessageInteractiveMediaBadgeContent: Equatable {
}
}
private let font = Font.regular(11.0)
private let boldFont = Font.semibold(11.0)
final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
public final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
private var content: ChatMessageInteractiveMediaBadgeContent?
var pressed: (() -> Void)?
public var pressed: (() -> Void)?
private var mediaDownloadState: ChatMessageInteractiveMediaDownloadState?
@ -56,7 +56,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
private var iconNode: ASImageNode?
private var mediaDownloadStatusNode: RadialStatusNode?
override init() {
override public init() {
self.backgroundNode = ASImageNode()
self.backgroundNode.clipsToBounds = true
self.durationNode = ASTextNode()
@ -68,7 +68,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
self.backgroundNode.addSubnode(self.durationNode)
}
override func didLoad() {
override public func didLoad() {
super.didLoad()
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
@ -87,7 +87,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
return self.measureNode.measure(CGSize(width: 240.0, height: 160.0)).width
}
func update(theme: PresentationTheme?, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, alignment: NSTextAlignment = .left, animated: Bool, badgeAnimated: Bool = true) {
public func update(theme: PresentationTheme?, content: ChatMessageInteractiveMediaBadgeContent?, mediaDownloadState: ChatMessageInteractiveMediaDownloadState?, alignment: NSTextAlignment = .left, animated: Bool, badgeAnimated: Bool = true) {
var transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
let previousContentSize = self.previousContentSize
@ -297,7 +297,7 @@ final class ChatMessageInteractiveMediaBadge: ASDisplayNode {
}
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return self.backgroundNode.frame.contains(point)
}
}

View File

@ -140,6 +140,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
let deletePeer: ((PeerId) -> Void)?
let itemHighlighting: ContactItemHighlighting?
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
let arrowAction: (() -> Void)?
public let selectable: Bool
@ -147,7 +148,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
public let header: ListViewItemHeader?
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil) {
public init(presentationData: ItemListPresentationData, style: ItemListStyle = .plain, sectionId: ItemListSectionId = 0, sortOrder: PresentationPersonNameOrder, displayOrder: PresentationPersonNameOrder, context: AccountContext, peerMode: ContactsPeerItemPeerMode, peer: ContactsPeerItemPeer, status: ContactsPeerItemStatus, badge: ContactsPeerItemBadge? = nil, enabled: Bool, selection: ContactsPeerItemSelection, editing: ContactsPeerItemEditing, options: [ItemListPeerItemRevealOption] = [], additionalActions: [ContactsPeerItemAction] = [], actionIcon: ContactsPeerItemActionIcon = .none, index: PeerNameIndex?, header: ListViewItemHeader?, action: @escaping (ContactsPeerItemPeer) -> Void, disabledAction: ((ContactsPeerItemPeer) -> Void)? = nil, setPeerIdWithRevealedOptions: ((PeerId?, PeerId?) -> Void)? = nil, deletePeer: ((PeerId) -> Void)? = nil, itemHighlighting: ContactItemHighlighting? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, arrowAction: (() -> Void)? = nil) {
self.presentationData = presentationData
self.style = style
self.sectionId = sectionId
@ -172,6 +173,7 @@ public class ContactsPeerItem: ItemListItem, ListViewItemWithHeader {
self.itemHighlighting = itemHighlighting
self.selectable = enabled || disabledAction != nil
self.contextAction = contextAction
self.arrowAction = arrowAction
if let index = index {
var letter: String = "#"
@ -318,6 +320,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
private var badgeTextNode: TextNode?
private var selectionNode: CheckNode?
private var actionButtonNodes: [HighlightableButtonNode]?
private var arrowButtonNode: HighlightableButtonNode?
private var isHighlighted: Bool = false
@ -503,6 +506,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
break
}
var arrowButtonImage: UIImage?
if let _ = item.arrowAction {
arrowButtonImage = generateTintedImage(image: UIImage(bundleImageName: "Chat List/Search/Arrow"), color: item.presentationData.theme.list.disclosureArrowColor)
}
var actionButtons: [ActionButton]?
struct ActionButton {
let image: UIImage?
@ -548,6 +556,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
if let user = peer as? TelegramUser {
if peer.id == item.context.account.peerId, case .generalSearch = item.peerMode {
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: titleBoldFont, textColor: textColor)
} else if peer.id.isReplies {
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: titleBoldFont, textColor: textColor)
} else if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
let string = NSMutableAttributedString()
switch item.displayOrder {
@ -665,6 +675,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
additionalTitleInset += badgeSize
if let arrowButtonImage = arrowButtonImage {
additionalTitleInset += arrowButtonImage.size.width + 4.0
}
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - additionalTitleInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: multilineStatus ? 3 : 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - badgeSize), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -735,6 +749,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
var overrideImage: AvatarNodeImageOverride?
if peer.id == item.context.account.peerId, case .generalSearch = item.peerMode {
overrideImage = .savedMessagesIcon
} else if peer.id.isReplies, case .generalSearch = item.peerMode {
overrideImage = .repliesIcon
} else if peer.isDeleted {
overrideImage = .deletedIcon
}
@ -856,6 +872,23 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
actionButtonNodes.forEach { $0.removeFromSupernode() }
}
if let arrowButtonImage = arrowButtonImage {
if strongSelf.arrowButtonNode == nil {
let arrowButtonNode = HighlightableButtonNode()
arrowButtonNode.addTarget(self, action: #selector(strongSelf.arrowButtonPressed), forControlEvents: .touchUpInside)
strongSelf.arrowButtonNode = arrowButtonNode
strongSelf.containerNode.addSubnode(arrowButtonNode)
}
if let arrowButtonNode = strongSelf.arrowButtonNode {
arrowButtonNode.setImage(arrowButtonImage, for: .normal)
transition.updateFrame(node: arrowButtonNode, frame: CGRect(origin: CGPoint(x: params.width - params.rightInset - 12.0 - arrowButtonImage.size.width, y: floor((nodeLayout.contentSize.height - arrowButtonImage.size.height) / 2.0)), size: arrowButtonImage.size))
}
} else if let arrowButtonNode = strongSelf.arrowButtonNode {
strongSelf.arrowButtonNode = nil
arrowButtonNode.removeFromSupernode()
}
let badgeBackgroundWidth: CGFloat
if let currentBadgeBackgroundImage = currentBadgeBackgroundImage, let (badgeTextLayout, badgeTextApply) = badgeTextLayoutAndApply {
let badgeBackgroundNode: ASImageNode
@ -876,7 +909,12 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
badgeBackgroundNode.image = currentBadgeBackgroundImage
badgeBackgroundWidth = max(badgeTextLayout.size.width + 10.0, currentBadgeBackgroundImage.size.width)
let badgeBackgroundFrame = CGRect(x: revealOffset + params.width - params.rightInset - badgeBackgroundWidth - 6.0, y: floor((nodeLayout.contentSize.height - currentBadgeBackgroundImage.size.height) / 2.0), width: badgeBackgroundWidth, height: currentBadgeBackgroundImage.size.height)
var badgeBackgroundFrame = CGRect(x: revealOffset + params.width - params.rightInset - badgeBackgroundWidth - 6.0, y: floor((nodeLayout.contentSize.height - currentBadgeBackgroundImage.size.height) / 2.0), width: badgeBackgroundWidth, height: currentBadgeBackgroundImage.size.height)
if let arrowButtonImage = arrowButtonImage {
badgeBackgroundFrame.origin.x -= arrowButtonImage.size.width + 6.0
}
let badgeTextFrame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.midX - badgeTextLayout.size.width / 2.0, y: badgeBackgroundFrame.minY + 2.0), size: badgeTextLayout.size)
let badgeTextNode = badgeTextApply()
@ -1064,4 +1102,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
return nil
}
}
@objc func arrowButtonPressed() {
if let (item, _, _, _, _, _) = self.layoutParams {
item.arrowAction?()
}
}
}

View File

@ -77,6 +77,8 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
if chatPeer.id == context.account.peerId {
self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .savedMessagesIcon)
} else if chatPeer.id.isReplies {
self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .repliesIcon)
} else {
var overrideImage: AvatarNodeImageOverride?
if chatPeer.isDeleted {

View File

@ -3,6 +3,17 @@ import AsyncDisplayKit
private var backArrowImageCache: [Int32: UIImage] = [:]
class SparseNode: ASDisplayNode {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event)
if result != self.view {
return result
} else {
return nil
}
}
}
public final class NavigationBarTheme {
public static func generateBackArrowImage(color: UIColor) -> UIImage? {
return generateImage(CGSize(width: 13.0, height: 22.0), rotatedContext: { size, context in
@ -126,7 +137,8 @@ open class NavigationBar: ASDisplayNode {
}
private let stripeNode: ASDisplayNode
private let clippingNode: ASDisplayNode
private let clippingNode: SparseNode
private let buttonsContainerNode: ASDisplayNode
public private(set) var contentNode: NavigationBarContentNode?
public private(set) var secondaryContentNode: ASDisplayNode?
@ -260,7 +272,7 @@ open class NavigationBar: ASDisplayNode {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.presentationData.theme.primaryTextColor)
self.titleNode.accessibilityLabel = title
if self.titleNode.supernode == nil {
self.clippingNode.addSubnode(self.titleNode)
self.buttonsContainerNode.addSubnode(self.titleNode)
}
} else {
self.titleNode.removeFromSupernode()
@ -279,7 +291,7 @@ open class NavigationBar: ASDisplayNode {
}
if let titleView = self.titleView {
self.clippingNode.view.addSubview(titleView)
self.buttonsContainerNode.view.addSubview(titleView)
}
self.invalidateCalculatedLayout()
@ -499,7 +511,7 @@ open class NavigationBar: ASDisplayNode {
}
if self.leftButtonNode.supernode == nil {
self.clippingNode.addSubnode(self.leftButtonNode)
self.buttonsContainerNode.addSubnode(self.leftButtonNode)
}
if animated {
@ -540,9 +552,9 @@ open class NavigationBar: ASDisplayNode {
if let backTitle = backTitle {
self.backButtonNode.updateManualText(backTitle)
if self.backButtonNode.supernode == nil {
self.clippingNode.addSubnode(self.backButtonNode)
self.clippingNode.addSubnode(self.backButtonArrow)
self.clippingNode.addSubnode(self.badgeNode)
self.buttonsContainerNode.addSubnode(self.backButtonNode)
self.buttonsContainerNode.addSubnode(self.backButtonArrow)
self.buttonsContainerNode.addSubnode(self.badgeNode)
}
if animated {
@ -588,7 +600,7 @@ open class NavigationBar: ASDisplayNode {
}
self.rightButtonNode.updateItems(items)
if self.rightButtonNode.supernode == nil {
self.clippingNode.addSubnode(self.rightButtonNode)
self.buttonsContainerNode.addSubnode(self.rightButtonNode)
}
if animated {
self.rightButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
@ -648,25 +660,25 @@ open class NavigationBar: ASDisplayNode {
if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.presentationData.theme.primaryTextColor) {
self.transitionTitleNode = transitionTitleNode
if self.leftButtonNode.supernode != nil {
self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode)
self.buttonsContainerNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode)
} else if self.backButtonNode.supernode != nil {
self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.backButtonNode)
self.buttonsContainerNode.insertSubnode(transitionTitleNode, belowSubnode: self.backButtonNode)
} else {
self.clippingNode.addSubnode(transitionTitleNode)
self.buttonsContainerNode.addSubnode(transitionTitleNode)
}
}
case .bottom:
if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.presentationData.theme.buttonColor) {
self.transitionBackButtonNode = transitionBackButtonNode
self.clippingNode.addSubnode(transitionBackButtonNode)
self.buttonsContainerNode.addSubnode(transitionBackButtonNode)
}
if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.presentationData.theme.buttonColor) {
self.transitionBackArrowNode = transitionBackArrowNode
self.clippingNode.addSubnode(transitionBackArrowNode)
self.buttonsContainerNode.addSubnode(transitionBackArrowNode)
}
if let transitionBadgeNode = value.navigationBar?.makeTransitionBadgeNode() {
self.transitionBadgeNode = transitionBadgeNode
self.clippingNode.addSubnode(transitionBadgeNode)
self.buttonsContainerNode.addSubnode(transitionBadgeNode)
}
}
}
@ -701,9 +713,12 @@ open class NavigationBar: ASDisplayNode {
self.rightButtonNode = NavigationButtonNode()
self.rightButtonNode.hitTestSlop = UIEdgeInsets(top: -4.0, left: -4.0, bottom: -4.0, right: -10.0)
self.clippingNode = ASDisplayNode()
self.clippingNode = SparseNode()
self.clippingNode.clipsToBounds = true
self.buttonsContainerNode = ASDisplayNode()
self.buttonsContainerNode.clipsToBounds = true
self.backButtonNode.color = self.presentationData.theme.buttonColor
self.backButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor
self.leftButtonNode.color = self.presentationData.theme.buttonColor
@ -720,6 +735,7 @@ open class NavigationBar: ASDisplayNode {
super.init()
self.addSubnode(self.buttonsContainerNode)
self.addSubnode(self.clippingNode)
self.backgroundColor = self.presentationData.theme.backgroundColor
@ -821,7 +837,7 @@ open class NavigationBar: ASDisplayNode {
self.validLayout = (size, defaultHeight, additionalHeight, leftInset, rightInset, appearsHidden)
if let secondaryContentNode = self.secondaryContentNode {
transition.updateAlpha(node: secondaryContentNode, alpha: appearsHidden ? 0.0 : 1.0)
// transition.updateAlpha(node: secondaryContentNode, alpha: appearsHidden ? 0.0 : 1.0)
}
let apparentAdditionalHeight: CGFloat = self.secondaryContentNode != nil ? NavigationBar.defaultSecondaryContentHeight : 0.0
@ -830,6 +846,7 @@ open class NavigationBar: ASDisplayNode {
let backButtonInset: CGFloat = leftInset + 27.0
transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: size))
transition.updateFrame(node: self.buttonsContainerNode, frame: CGRect(origin: CGPoint(), size: size))
var expansionHeight: CGFloat = 0.0
if let contentNode = self.contentNode {
var contentNodeFrame: CGRect
@ -839,7 +856,9 @@ open class NavigationBar: ASDisplayNode {
contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height))
case .expansion:
expansionHeight = contentNode.height
contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - expansionHeight - apparentAdditionalHeight), size: CGSize(width: size.width, height: expansionHeight))
let additionalExpansionHeight: CGFloat = self.secondaryContentNode != nil && appearsHidden ? NavigationBar.defaultSecondaryContentHeight : 0.0
contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - expansionHeight - apparentAdditionalHeight - additionalExpansionHeight), size: CGSize(width: size.width, height: expansionHeight))
if appearsHidden {
if self.secondaryContentNode != nil {
contentNodeFrame.origin.y += NavigationBar.defaultSecondaryContentHeight
@ -1174,10 +1193,10 @@ open class NavigationBar: ASDisplayNode {
contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
if case .replacement = contentNode.mode, !self.clippingNode.alpha.isZero {
self.clippingNode.alpha = 0.0
if case .replacement = contentNode.mode, !self.buttonsContainerNode.alpha.isZero {
self.buttonsContainerNode.alpha = 0.0
if animated {
self.clippingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
self.buttonsContainerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2)
}
}
@ -1187,23 +1206,36 @@ open class NavigationBar: ASDisplayNode {
} else {
self.requestLayout()
}
} else if self.clippingNode.alpha.isZero {
self.clippingNode.alpha = 1.0
} else if self.buttonsContainerNode.alpha.isZero {
self.buttonsContainerNode.alpha = 1.0
if animated {
self.clippingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.buttonsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
}
}
public func setSecondaryContentNode(_ secondatryContentNode: ASDisplayNode?) {
if self.secondaryContentNode !== secondatryContentNode {
public func setSecondaryContentNode(_ secondaryContentNode: ASDisplayNode?, animated: Bool = false) {
if self.secondaryContentNode !== secondaryContentNode {
if let previous = self.secondaryContentNode {
previous.removeFromSupernode()
if animated && previous.supernode === self.clippingNode {
previous.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak previous] finished in
if finished {
previous?.removeFromSupernode()
previous?.layer.removeAllAnimations()
}
})
} else {
previous.removeFromSupernode()
}
}
self.secondaryContentNode = secondatryContentNode
if let secondaryContentNode = secondatryContentNode {
self.secondaryContentNode = secondaryContentNode
if let secondaryContentNode = secondaryContentNode {
self.clippingNode.addSubnode(secondaryContentNode)
if animated {
secondaryContentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
}
}
}
@ -1223,11 +1255,11 @@ open class NavigationBar: ASDisplayNode {
if let contentNode = self.contentNode, case .replacement = contentNode.mode {
} else {
let targetAlpha: CGFloat = hidden ? 0.0 : 1.0
let previousAlpha = self.clippingNode.alpha
let previousAlpha = self.buttonsContainerNode.alpha
if previousAlpha != targetAlpha {
self.clippingNode.alpha = targetAlpha
self.buttonsContainerNode.alpha = targetAlpha
if animated {
self.clippingNode.layer.animateAlpha(from: previousAlpha, to: targetAlpha, duration: 0.2)
self.buttonsContainerNode.layer.animateAlpha(from: previousAlpha, to: targetAlpha, duration: 0.2)
}
}
}
@ -1247,7 +1279,7 @@ open class NavigationBar: ASDisplayNode {
return nil
}
if result == self.view || result == self.clippingNode.view {
if result == self.view || result == self.buttonsContainerNode.view {
return nil
}

View File

@ -340,9 +340,9 @@ open class TabBarController: ViewController {
}
}
public func updateLayout() {
public func updateLayout(transition: ContainedViewLayoutTransition = .immediate) {
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .immediate)
self.containerLayoutUpdated(layout, transition: transition)
}
}

View File

@ -400,6 +400,9 @@ public enum TabBarItemContextActionType {
if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode, !self.displayNavigationBar {
navigationBarFrame.origin.y += contentNode.height + statusBarHeight
}
if let _ = navigationBar.contentNode, let _ = navigationBar.secondaryContentNode, !self.displayNavigationBar {
navigationBarFrame.origin.y += NavigationBar.defaultSecondaryContentHeight
}
navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: defaultNavigationBarHeight, additionalHeight: 0.0, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, transition: transition)
if !transition.isAnimated {
navigationBar.layer.cancelAnimationsRecursive(key: "bounds")

View File

@ -0,0 +1,23 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "FileMediaResourceStatus",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/AccountContext:AccountContext",
"//submodules/MediaPlayer:UniversalMediaPlayer",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
"$SDKROOT/System/Library/Frameworks/WebKit.framework",
],
)

View File

@ -0,0 +1,22 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "FileMediaResourceStatus",
module_name = "FileMediaResourceStatus",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/SyncCore:SyncCore",
"//submodules/AccountContext:AccountContext",
"//submodules/MediaPlayer:UniversalMediaPlayer",
],
visibility = [
"//visibility:public",
],
)

View File

@ -7,12 +7,12 @@ import SwiftSignalKit
import UniversalMediaPlayer
import AccountContext
private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal<MediaPlayerStatus?, NoError> {
private func internalMessageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal<MediaPlayerStatus?, NoError> {
guard let playerType = peerMessageMediaPlayerType(message) else {
return .single(nil)
}
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions) {
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) {
return context.sharedContext.mediaManager.filteredPlaylistState(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType)
|> mapToSignal { state -> Signal<MediaPlayerStatus?, NoError> in
return .single(state?.status)
@ -22,31 +22,31 @@ private func internalMessageFileMediaPlaybackStatus(context: AccountContext, fil
}
}
func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal<MediaPlayerStatus, NoError> {
public func messageFileMediaPlaybackStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal<MediaPlayerStatus, NoError> {
var duration = 0.0
if let value = file.duration {
duration = Double(value)
}
let defaultStatus = MediaPlayerStatus(generationTimestamp: 0.0, duration: duration, dimensions: CGSize(), timestamp: 0.0, baseRate: 1.0, seekId: 0, status: .paused, soundEnabled: true)
return internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) |> map { status in
return internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) |> map { status in
return status ?? defaultStatus
}
}
func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool) -> Signal<Float, NoError> {
public func messageFileMediaPlaybackAudioLevelEvents(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isGlobalSearch: Bool) -> Signal<Float, NoError> {
guard let playerType = peerMessageMediaPlayerType(message) else {
return .never()
}
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions) {
if let (playlistId, itemId) = peerMessagesMediaPlaylistAndItemId(message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) {
return context.sharedContext.mediaManager.filteredPlayerAudioLevelEvents(accountId: context.account.id, playlistId: playlistId, itemId: itemId, type: playerType)
} else {
return .never()
}
}
func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false) -> Signal<FileMediaResourceStatus, NoError> {
let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions) |> map { status -> MediaPlayerPlaybackStatus? in
public func messageFileMediaResourceStatus(context: AccountContext, file: TelegramMediaFile, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal<FileMediaResourceStatus, NoError> {
let playbackStatus = internalMessageFileMediaPlaybackStatus(context: context, file: file, message: message, isRecentActions: isRecentActions, isGlobalSearch: isGlobalSearch) |> map { status -> MediaPlayerPlaybackStatus? in
return status?.status
}

View File

@ -0,0 +1,31 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "GalleryData",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext",
"//submodules/AppBundle:AppBundle",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/InstantPageUI:InstantPageUI",
"//submodules/GalleryUI:GalleryUI",
"//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI",
"//submodules/MediaResources:MediaResources",
"//submodules/WebsiteType:WebsiteType",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
"$SDKROOT/System/Library/Frameworks/QuickLook.framework",
"$SDKROOT/System/Library/Frameworks/SafariServices.framework",
],
)

View File

@ -0,0 +1,29 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "GalleryData",
module_name = "GalleryData",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/SyncCore:SyncCore",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext",
"//submodules/AppBundle:AppBundle",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/InstantPageUI:InstantPageUI",
"//submodules/GalleryUI:GalleryUI",
"//submodules/PeerAvatarGalleryUI:PeerAvatarGalleryUI",
"//submodules/MediaResources:MediaResources",
"//submodules/WebsiteType:WebsiteType",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,296 @@
import Foundation
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SyncCore
import SwiftSignalKit
import PassKit
import Lottie
import TelegramUIPreferences
import TelegramPresentationData
import AccountContext
import InstantPageUI
import PeerAvatarGalleryUI
import GalleryUI
import MediaResources
import WebsiteType
public enum ChatMessageGalleryControllerData {
case url(String)
case pass(TelegramMediaFile)
case instantPage(InstantPageGalleryController, Int, Media)
case map(TelegramMediaMap)
case stickerPack(StickerPackReference)
case audio(TelegramMediaFile)
case document(TelegramMediaFile, Bool)
case gallery(Signal<GalleryController, NoError>)
case secretGallery(SecretMediaPreviewController)
case chatAvatars(AvatarGalleryController, Media)
case theme(TelegramMediaFile)
case other(Media)
}
private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, media: [MediaId: Media], counter: inout Int) -> [InstantPageGalleryEntry] {
switch block {
case let .image(id, caption, _, _):
if let m = media[id] {
let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))]
counter += 1
return result
}
case let .video(id, caption, _, _):
if let m = media[id] {
let result = [InstantPageGalleryEntry(index: Int32(counter), pageId: pageId, media: InstantPageMedia(index: counter, media: m, url: nil, caption: caption.text, credit: caption.credit), caption: caption.text, credit: caption.credit, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0))]
counter += 1
return result
}
case let .collage(items, _):
var result: [InstantPageGalleryEntry] = []
for item in items {
result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter))
}
return result
case let .slideshow(items, _):
var result: [InstantPageGalleryEntry] = []
for item in items {
result.append(contentsOf: instantPageBlockMedia(pageId: pageId, block: item, media: media, counter: &counter))
}
return result
default:
break
}
return []
}
public func instantPageGalleryMedia(webpageId: MediaId, page: InstantPage, galleryMedia: Media) -> [InstantPageGalleryEntry] {
var result: [InstantPageGalleryEntry] = []
var counter: Int = 0
for block in page.blocks {
result.append(contentsOf: instantPageBlockMedia(pageId: webpageId, block: block, media: page.media, counter: &counter))
}
var found = false
for item in result {
if item.media.media.id == galleryMedia.id {
found = true
break
}
}
if !found {
result.insert(InstantPageGalleryEntry(index: Int32(counter), pageId: webpageId, media: InstantPageMedia(index: counter, media: galleryMedia, url: nil, caption: nil, credit: nil), caption: nil, credit: nil, location: InstantPageGalleryEntryLocation(position: Int32(counter), totalCount: 0)), at: 0)
}
for i in 0 ..< result.count {
let item = result[i]
result[i] = InstantPageGalleryEntry(index: Int32(i), pageId: item.pageId, media: item.media, caption: item.caption, credit: item.credit, location: InstantPageGalleryEntryLocation(position: Int32(i), totalCount: Int32(result.count)))
}
return result
}
public func chatMessageGalleryControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, navigationController: NavigationController?, standalone: Bool, reverseMessageGalleryOrder: Bool, mode: ChatControllerInteractionOpenMessageMode, source: GalleryControllerItemSource?, synchronousLoad: Bool, actionInteraction: GalleryControllerActionInteraction?) -> ChatMessageGalleryControllerData? {
var galleryMedia: Media?
var otherMedia: Media?
var instantPageMedia: (TelegramMediaWebpage, [InstantPageGalleryEntry])?
for media in message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case let .photoUpdated(image):
if let peer = messageMainPeer(message), let image = image {
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer, message.timestamp, nil, message.id, image.immediateThumbnailData, "action")])
let galleryController = AvatarGalleryController(context: context, peer: peer, sourceCorners: .roundRect(15.5), remoteEntries: promise, skipInitial: true, replaceRootController: { controller, ready in
})
return .chatAvatars(galleryController, image)
}
default:
break
}
} else if let file = media as? TelegramMediaFile {
galleryMedia = file
} else if let image = media as? TelegramMediaImage {
galleryMedia = image
} else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let file = content.file {
galleryMedia = file
} else if let image = content.image {
if case .link = mode {
} else if ["photo", "document", "video", "gif", "telegram_album"].contains(content.type) {
galleryMedia = image
}
}
if let instantPage = content.instantPage, let galleryMedia = galleryMedia {
switch instantPageType(of: content) {
case .album:
let medias = instantPageGalleryMedia(webpageId: webpage.webpageId, page: instantPage, galleryMedia: galleryMedia)
if medias.count > 1 {
instantPageMedia = (webpage, medias)
}
default:
break
}
}
} else if let mapMedia = media as? TelegramMediaMap {
galleryMedia = mapMedia
} else if let contactMedia = media as? TelegramMediaContact {
otherMedia = contactMedia
}
}
var stream = false
var autoplayingVideo = false
var landscape = false
var timecode: Double? = nil
switch mode {
case .stream:
stream = true
case .automaticPlayback:
autoplayingVideo = true
case .landscape:
autoplayingVideo = true
landscape = true
case let .timecode(time):
timecode = time
default:
break
}
if let (webPage, instantPageMedia) = instantPageMedia, let galleryMedia = galleryMedia {
var centralIndex: Int = 0
for i in 0 ..< instantPageMedia.count {
if instantPageMedia[i].media.media.id == galleryMedia.id {
centralIndex = i
break
}
}
let gallery = InstantPageGalleryController(context: context, webPage: webPage, message: message, entries: instantPageMedia, centralIndex: centralIndex, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, replaceRootController: { [weak navigationController] controller, ready in
if let navigationController = navigationController {
navigationController.replaceTopController(controller, animated: false, ready: ready)
}
}, baseNavigationController: navigationController)
return .instantPage(gallery, centralIndex, galleryMedia)
} else if let galleryMedia = galleryMedia {
if let mapMedia = galleryMedia as? TelegramMediaMap {
return .map(mapMedia)
} else if let file = galleryMedia as? TelegramMediaFile, (file.isSticker || file.isAnimatedSticker) {
for attribute in file.attributes {
if case let .Sticker(_, reference, _) = attribute {
if let reference = reference {
return .stickerPack(reference)
}
break
}
}
} else if let file = galleryMedia as? TelegramMediaFile, file.isAnimatedSticker {
return nil
} else if let file = galleryMedia as? TelegramMediaFile, file.isMusic || file.isVoice || file.isInstantVideo {
return .audio(file)
} else if let file = galleryMedia as? TelegramMediaFile, file.mimeType == "application/vnd.apple.pkpass" || (file.fileName != nil && file.fileName!.lowercased().hasSuffix(".pkpass")) {
return .pass(file)
} else {
if let file = galleryMedia as? TelegramMediaFile {
if let fileName = file.fileName {
let ext = (fileName as NSString).pathExtension.lowercased()
if ext == "tgios-theme" {
return .theme(file)
} else if ext == "wav" || ext == "opus" {
return .audio(file)
} else if ext == "json", let fileSize = file.size, fileSize < 1024 * 1024 {
if let path = context.account.postbox.mediaBox.completedResourcePath(file.resource), let composition = LOTComposition(filePath: path), composition.timeDuration > 0.0 {
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? ChatLocation.peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
return .gallery(.single(gallery))
}
}
if ext == "mkv" {
return .document(file, true)
}
}
if internalDocumentItemSupportsMimeType(file.mimeType, fileName: file.fileName ?? "file") {
let gallery = GalleryController(context: context, source: source ?? .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil)), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
return .gallery(.single(gallery))
}
if !file.isVideo {
return .document(file, false)
}
}
if message.containsSecretMedia {
let gallery = SecretMediaPreviewController(context: context, messageId: message.id)
return .secretGallery(gallery)
} else {
let startTimecode: Signal<Double?, NoError>
if let timecode = timecode {
startTimecode = .single(timecode)
} else {
startTimecode = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id)
|> map { state in
return state?.timestamp
}
}
return .gallery(startTimecode
|> deliverOnMainQueue
|> map { timecode in
let gallery = GalleryController(context: context, source: source ?? (standalone ? .standaloneMessage(message) : .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil))), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
navigationController?.replaceTopController(controller, animated: false, ready: ready)
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
gallery.temporaryDoNotWaitForReady = autoplayingVideo
return gallery
})
}
}
}
if let otherMedia = otherMedia {
return .other(otherMedia)
} else {
return nil
}
}
public enum ChatMessagePreviewControllerData {
case instantPage(InstantPageGalleryController, Int, Media)
case gallery(GalleryController)
}
public func chatMessagePreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> ChatMessagePreviewControllerData? {
if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) {
switch mediaData {
case let .gallery(gallery):
break
case let .instantPage(gallery, centralIndex, galleryMedia):
return .instantPage(gallery, centralIndex, galleryMedia)
default:
break
}
}
return nil
}
public func chatMediaListPreviewControllerData(context: AccountContext, chatLocation: ChatLocation?, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>?, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?) -> Signal<ChatMessagePreviewControllerData?, NoError> {
if let mediaData = chatMessageGalleryControllerData(context: context, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, mode: .default, source: nil, synchronousLoad: true, actionInteraction: nil) {
switch mediaData {
case let .gallery(gallery):
return gallery
|> map { gallery in
return .gallery(gallery)
}
case let .instantPage(gallery, centralIndex, galleryMedia):
return .single(.instantPage(gallery, centralIndex, galleryMedia))
default:
break
}
}
return .single(nil)
}

View File

@ -246,20 +246,20 @@ public final class GalleryControllerPresentationArguments {
private enum GalleryMessageHistoryView {
case view(MessageHistoryView)
case single(MessageHistoryEntry)
case entries([MessageHistoryEntry], Bool, Bool)
var entries: [MessageHistoryEntry] {
switch self {
case let .view(view):
return view.entries
case let .single(entry):
return [entry]
case let .entries(entries, _, _):
return entries
}
}
var tagMask: MessageTags? {
switch self {
case .single:
case .entries:
return nil
case let .view(view):
return view.tagMask
@ -268,8 +268,8 @@ private enum GalleryMessageHistoryView {
var hasEarlier: Bool {
switch self {
case .single:
return false
case let .entries(_, hasEarlier, _):
return hasEarlier
case let .view(view):
return view.earlierId != nil
}
@ -277,19 +277,14 @@ private enum GalleryMessageHistoryView {
var hasLater: Bool {
switch self {
case .single:
return false
case let .entries(_ , _, hasLater):
return hasLater
case let .view(view):
return view.laterId != nil
}
}
}
public enum GalleryControllerItemSource {
case peerMessagesAtId(MessageId)
case standaloneMessage(Message)
}
public enum GalleryControllerInteractionTapAction {
case url(url: String, concealed: Bool)
case textMention(String)
@ -357,6 +352,7 @@ public class GalleryController: ViewController, StandalonePresentableController
private var entries: [MessageHistoryEntry] = []
private var hasLeftEntries: Bool = false
private var hasRightEntries: Bool = false
private var loadingMore: Bool = false
private var tagMask: MessageTags?
private var centralEntryStableId: UInt32?
private var configuration: GalleryConfiguration?
@ -417,17 +413,23 @@ public class GalleryController: ViewController, StandalonePresentableController
let message: Signal<Message?, NoError>
switch source {
case let .peerMessagesAtId(messageId):
case let .peerMessagesAtId(messageId, _, _):
message = context.account.postbox.messageAtId(messageId)
case let .standaloneMessage(m):
message = .single(m)
case let .custom(messages, messageId, _):
message = messages
|> take(1)
|> map { messages, _, _ in
return messages.first(where: { $0.id == messageId })
}
}
let messageView = message
|> filter({ $0 != nil })
|> mapToSignal { message -> Signal<GalleryMessageHistoryView?, NoError> in
switch source {
case .peerMessagesAtId:
case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder):
if let tags = tagsForMessage(message!) {
let namespaces: MessageIdNamespaces
if Namespaces.Message.allScheduled.contains(message!.id.namespace) {
@ -435,16 +437,27 @@ public class GalleryController: ViewController, StandalonePresentableController
} else {
namespaces = .not(Namespaces.Message.allScheduled)
}
return context.account.postbox.aroundMessageHistoryViewForLocation(.peer(message!.id.peerId), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)
return context.account.postbox.aroundMessageHistoryViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(message!.index), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tags, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)
}
} else {
return .single(GalleryMessageHistoryView.single(MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))))
}
return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false, false))
}
case .standaloneMessage:
return .single(GalleryMessageHistoryView.single(MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))))
return .single(GalleryMessageHistoryView.entries([MessageHistoryEntry(message: message!, isRead: false, location: nil, monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false))], false ,false))
case let .custom(messages, _, _):
return messages
|> map { messages, totalCount, hasMore in
var entries: [MessageHistoryEntry] = []
var index = messages.count
for message in messages.reversed() {
entries.append(MessageHistoryEntry(message: message, isRead: false, location: MessageHistoryEntryLocation(index: Int(totalCount) - index, count: Int(totalCount)), monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))
index -= 1
}
return GalleryMessageHistoryView.entries(entries, hasMore, false)
}
}
}
|> take(1)
@ -470,7 +483,7 @@ public class GalleryController: ViewController, StandalonePresentableController
loop: for i in 0 ..< entries.count {
let message = entries[i].message
switch source {
case let .peerMessagesAtId(messageId):
case let .peerMessagesAtId(messageId, _, _):
if message.id == messageId {
centralEntryStableId = message.stableId
break loop
@ -480,6 +493,11 @@ public class GalleryController: ViewController, StandalonePresentableController
centralEntryStableId = message.stableId
break loop
}
case let .custom(_, messageId, _):
if message.id == messageId {
centralEntryStableId = message.stableId
break loop
}
}
}
@ -813,7 +831,7 @@ public class GalleryController: ViewController, StandalonePresentableController
self.isOpaqueWhenInOverlay = true
switch source {
case let .peerMessagesAtId(id):
case let .peerMessagesAtId(id, _, _):
if id.peerId.namespace == Namespaces.Peer.SecretChat {
self.screenCaptureEventsDisposable = (screenCaptureEvents()
|> deliverOnMainQueue).start(next: { [weak self] _ in
@ -984,70 +1002,125 @@ public class GalleryController: ViewController, StandalonePresentableController
}
switch strongSelf.source {
case let .peerMessagesAtId(initialMessageId):
var reloadAroundIndex: MessageIndex?
if index <= 2 && strongSelf.hasLeftEntries {
reloadAroundIndex = strongSelf.entries.first?.index
} else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries {
reloadAroundIndex = strongSelf.entries.last?.index
}
if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask {
let namespaces: MessageIdNamespaces
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
namespaces = .just(Namespaces.Message.allScheduled)
} else {
namespaces = .not(Namespaces.Message.allScheduled)
case let .peerMessagesAtId(_, chatLocation, chatLocationContextHolder):
var reloadAroundIndex: MessageIndex?
if index <= 2 && strongSelf.hasLeftEntries {
reloadAroundIndex = strongSelf.entries.first?.index
} else if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries {
reloadAroundIndex = strongSelf.entries.last?.index
}
let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(.peer(initialMessageId.peerId), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)
}
|> take(1)
strongSelf.updateVisibleDisposable.set((signal
|> deliverOnMainQueue).start(next: { view in
guard let strongSelf = self, let view = view else {
return
}
let entries = view.entries
if strongSelf.invertItemOrder {
strongSelf.entries = entries.reversed()
strongSelf.hasLeftEntries = view.hasLater
strongSelf.hasRightEntries = view.hasEarlier
if let reloadAroundIndex = reloadAroundIndex, let tagMask = strongSelf.tagMask {
let namespaces: MessageIdNamespaces
if Namespaces.Message.allScheduled.contains(message.id.namespace) {
namespaces = .just(Namespaces.Message.allScheduled)
} else {
strongSelf.entries = entries
strongSelf.hasLeftEntries = view.hasEarlier
strongSelf.hasRightEntries = view.hasLater
namespaces = .not(Namespaces.Message.allScheduled)
}
if strongSelf.isViewLoaded {
var items: [GalleryItem] = []
var centralItemIndex: Int?
for entry in strongSelf.entries {
var isCentral = false
if entry.message.stableId == strongSelf.centralEntryStableId {
isCentral = true
let signal = strongSelf.context.account.postbox.aroundMessageHistoryViewForLocation(strongSelf.context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder), anchor: .index(reloadAroundIndex), count: 50, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tagMask: tagMask, namespaces: namespaces, orderStatistics: [.combinedLocation])
|> mapToSignal { (view, _, _) -> Signal<GalleryMessageHistoryView?, NoError> in
let mapped = GalleryMessageHistoryView.view(view)
return .single(mapped)
}
|> take(1)
strongSelf.updateVisibleDisposable.set((signal
|> deliverOnMainQueue).start(next: { view in
guard let strongSelf = self, let view = view else {
return
}
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.presentInGlobalOverlay(c, with: a)
let entries = view.entries
if strongSelf.invertItemOrder {
strongSelf.entries = entries.reversed()
strongSelf.hasLeftEntries = view.hasLater
strongSelf.hasRightEntries = view.hasEarlier
} else {
strongSelf.entries = entries
strongSelf.hasLeftEntries = view.hasEarlier
strongSelf.hasRightEntries = view.hasLater
}
if strongSelf.isViewLoaded {
var items: [GalleryItem] = []
var centralItemIndex: Int?
for entry in strongSelf.entries {
var isCentral = false
if entry.message.stableId == strongSelf.centralEntryStableId {
isCentral = true
}
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.presentInGlobalOverlay(c, with: a)
}
}) {
if isCentral {
centralItemIndex = items.count
}
items.append(item)
}
}
}) {
if isCentral {
centralItemIndex = items.count
strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex)
}
}))
}
case let .custom(messages, _, loadMore):
if index >= strongSelf.entries.count - 3 && strongSelf.hasRightEntries && !strongSelf.loadingMore {
strongSelf.loadingMore = true
loadMore?()
strongSelf.updateVisibleDisposable.set((messages
|> deliverOnMainQueue).start(next: { messages, totalCount, hasMore in
guard let strongSelf = self else {
return
}
var entries: [MessageHistoryEntry] = []
var index = messages.count
for message in messages.reversed() {
entries.append(MessageHistoryEntry(message: message, isRead: false, location: MessageHistoryEntryLocation(index: Int(totalCount) - index, count: Int(totalCount)), monthLocation: nil, attributes: MutableMessageHistoryEntryAttributes(authorIsContact: false)))
index -= 1
}
if entries.count > strongSelf.entries.count {
if strongSelf.invertItemOrder {
strongSelf.entries = entries.reversed()
strongSelf.hasLeftEntries = false
strongSelf.hasRightEntries = hasMore
} else {
strongSelf.entries = entries
strongSelf.hasLeftEntries = hasMore
strongSelf.hasRightEntries = false
}
if strongSelf.isViewLoaded {
var items: [GalleryItem] = []
var centralItemIndex: Int?
for entry in strongSelf.entries {
var isCentral = false
if entry.message.stableId == strongSelf.centralEntryStableId {
isCentral = true
}
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.presentInGlobalOverlay(c, with: a)
}
}) {
if isCentral {
centralItemIndex = items.count
}
items.append(item)
}
}
items.append(item)
strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex)
}
}
strongSelf.galleryNode.pager.replaceItems(items, centralItemIndex: centralItemIndex)
}
}))
}
default:
break
strongSelf.loadingMore = false
}))
}
default:
break
}
}
if strongSelf.didSetReady {
@ -1152,6 +1225,7 @@ public class GalleryController: ViewController, StandalonePresentableController
self.adjustedForInitialPreviewingLayout = true
self.galleryNode.setControlsHidden(true, animated: false)
if let centralItemNode = self.galleryNode.pager.centralItemNode(), let itemSize = centralItemNode.contentSize() {
centralItemNode.adjustForPreviewing()
self.preferredContentSize = itemSize.aspectFitted(layout.size)
self.containerLayoutUpdated(ContainerViewLayout(size: self.preferredContentSize, metrics: LayoutMetrics(), deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate)
}

View File

@ -85,6 +85,9 @@ open class GalleryItemNode: ASDisplayNode {
open func controlsVisibilityUpdated(isVisible: Bool) {
}
open func adjustForPreviewing() {
}
open func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
}

View File

@ -1352,7 +1352,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
switch contentInfo {
case let .message(message):
let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), replaceRootController: { controller, ready in
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil)), replaceRootController: { controller, ready in
if let baseNavigationController = baseNavigationController {
baseNavigationController.replaceTopController(controller, animated: false, ready: ready)
}
@ -1434,7 +1434,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
switch contentInfo {
case let .message(message):
let gallery = GalleryController(context: context, source: .peerMessagesAtId(message.id), replaceRootController: { controller, ready in
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: .peer(message.id.peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil)), replaceRootController: { controller, ready in
if let baseNavigationController = baseNavigationController {
baseNavigationController.replaceTopController(controller, animated: false, ready: ready)
}
@ -1537,6 +1537,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
override func adjustForPreviewing() {
super.adjustForPreviewing()
self.scrubberView.isHidden = true
}
override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> {
return .single((self.footerContentNode, nil))
}

View File

@ -17,6 +17,7 @@ static_library(
"//submodules/TelegramBaseController:TelegramBaseController",
"//submodules/ChatListUI:ChatListUI",
"//submodules/SegmentedControlNode:SegmentedControlNode",
"//submodules/ListMessageItem:ListMessageItem",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -18,6 +18,7 @@ swift_library(
"//submodules/TelegramBaseController:TelegramBaseController",
"//submodules/ChatListUI:ChatListUI",
"//submodules/SegmentedControlNode:SegmentedControlNode",
"//submodules/ListMessageItem:ListMessageItem",
],
visibility = [
"//visibility:public",

View File

@ -9,6 +9,7 @@ import TelegramPresentationData
import TelegramBaseController
import AccountContext
import ChatListUI
import ListMessageItem
public final class HashtagSearchController: TelegramBaseController {
private let queue = Queue()
@ -41,11 +42,11 @@ public final class HashtagSearchController: TelegramBaseController {
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)
let location: SearchMessagesLocation = .general(tags: nil)
let location: SearchMessagesLocation = .general(tags: nil, minDate: nil, maxDate: nil)
let search = searchMessages(account: context.account, location: location, query: query, state: nil)
let foundMessages: Signal<[ChatListSearchEntry], NoError> = search
|> map { result, _ in
return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData) })
return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData, result.totalCount, nil, false) })
}
let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { _, _ in
@ -82,10 +83,27 @@ public final class HashtagSearchController: TelegramBaseController {
if let strongSelf = self {
let previousEntries = previousSearchItems.swap(entries)
let firstTime = previousEntries == nil
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], interaction: interaction, peerContextAction: nil, toggleExpandLocalResults: {
}, toggleExpandGlobalResults: {
let listInteraction = ListMessageItemInteraction(openMessage: { message, mode -> Bool in
return true
}, openMessageContextMenu: { message, bool, node, rect, gesture in
}, toggleMessagesSelection: { messageId, selected in
}, openUrl: { url, _, _, message in
}, openInstantPage: { message, data in
}, longTap: { action, message in
}, getHiddenMedia: {
return [:]
})
let firstTime = previousEntries == nil
let transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: entries, displayingResults: true, isEmpty: entries.isEmpty, isLoading: false, animated: false, searchQuery: "", context: strongSelf.context, presentationData: strongSelf.presentationData, enableHeaders: false, filter: [], interaction: interaction, listInteraction: listInteraction, peerContextAction: nil, toggleExpandLocalResults: {
}, toggleExpandGlobalResults: {
}, searchPeer: { _ in
}, searchResults: [], searchOptions: nil, messageContextAction: nil)
strongSelf.controllerNode.enqueueTransition(transition, firstTime: firstTime)
}
})

View File

@ -42,6 +42,8 @@ final class HashtagSearchControllerNode: ASDisplayNode {
var items: [String] = []
if peer?.id == context.account.peerId {
items.append(presentationData.strings.Conversation_SavedMessages)
} else if let id = peer?.id, id.isReplies {
items.append(presentationData.strings.DialogList_Replies)
} else {
items.append(peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) ?? "")
}

View File

@ -9,6 +9,56 @@ import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
public func instantPageAndAnchor(message: Message) -> (TelegramMediaWebpage, String?)? {
for media in message.media {
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if let _ = content.instantPage {
var textUrl: String?
if let pageUrl = URL(string: content.url) {
inner: for attribute in message.attributes {
if let attribute = attribute as? TextEntitiesMessageAttribute {
for entity in attribute.entities {
switch entity.type {
case let .TextUrl(url):
if let parsedUrl = URL(string: url) {
if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path {
textUrl = url
}
}
case .Url:
let nsText = message.text as NSString
var entityRange = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
if entityRange.location + entityRange.length > nsText.length {
entityRange.location = max(0, nsText.length - entityRange.length)
entityRange.length = nsText.length - entityRange.location
}
let url = nsText.substring(with: entityRange)
if let parsedUrl = URL(string: url) {
if pageUrl.scheme == parsedUrl.scheme && pageUrl.host == parsedUrl.host && pageUrl.path == parsedUrl.path {
textUrl = url
}
}
default:
break
}
}
break inner
}
}
}
var anchor: String?
if let textUrl = textUrl, let anchorRange = textUrl.range(of: "#") {
anchor = String(textUrl[anchorRange.upperBound...])
}
return (webpage, anchor)
}
break
}
}
return nil
}
public final class InstantPageController: ViewController {
private let context: AccountContext
private var webPage: TelegramMediaWebpage

View File

@ -855,6 +855,9 @@ func instantPageLayoutForWebPage(_ webPage: TelegramMediaWebpage, boundingWidth:
if let image = loadedContent.image, let id = image.id {
media[id] = image
}
if let video = loadedContent.file, let id = video.id {
media[id] = video
}
var mediaIndexCounter: Int = 0
var embedIndexCounter: Int = 0

View File

@ -683,6 +683,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
if item.peer.id == item.context.account.peerId, case .threatSelfAsSaved = item.aliasHandling {
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_SavedMessages, font: currentBoldFont, textColor: titleColor)
} else if item.peer.id.isReplies {
titleAttributedString = NSAttributedString(string: item.presentationData.strings.DialogList_Replies, font: currentBoldFont, textColor: titleColor)
} else if let user = item.peer as? TelegramUser {
if let firstName = user.firstName, let lastName = user.lastName, !firstName.isEmpty, !lastName.isEmpty {
let string = NSMutableAttributedString()
@ -1059,6 +1061,8 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
if item.peer.id == item.context.account.peerId, case .threatSelfAsSaved = item.aliasHandling {
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: .savedMessagesIcon, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad)
} else if item.peer.id.isReplies {
strongSelf.avatarNode.setPeer(context: item.context, theme: item.presentationData.theme, peer: item.peer, overrideImage: .repliesIcon, emptyColor: item.presentationData.theme.list.mediaPlaceholderColor, synchronousLoad: synchronousLoad)
} else {
var overrideImage: AvatarNodeImageOverride?
if item.peer.isDeleted {

View File

@ -570,7 +570,7 @@ private func loadLegacyMessages(account: TemporaryAccount, basePath: String, acc
}
let (parsedTags, parsedGlobalTags) = tagsForStoreMessage(incoming: parsedFlags.contains(.Incoming), attributes: parsedAttributes, media: parsedMedia, textEntities: nil)
messages.append(StoreMessage(id: parsedId, globallyUniqueId: globallyUniqueId, groupingKey: parsedGroupingKey, timestamp: timestamp, flags: parsedFlags, tags: parsedTags, globalTags: parsedGlobalTags, localTags: [], forwardInfo: nil, authorId: parsedAuthorId, text: text, attributes: parsedAttributes, media: parsedMedia))
messages.append(StoreMessage(id: parsedId, globallyUniqueId: globallyUniqueId, groupingKey: parsedGroupingKey, threadId: nil, timestamp: timestamp, flags: parsedFlags, tags: parsedTags, globalTags: parsedGlobalTags, localTags: [], forwardInfo: nil, authorId: parsedAuthorId, text: text, attributes: parsedAttributes, media: parsedMedia))
//Logger.shared.log("loadLegacyMessages", "message \(messageId) completed")

View File

@ -0,0 +1,36 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "ListMessageItem",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/ItemListUI:ItemListUI",
"//submodules/AccountContext:AccountContext",
"//submodules/TextFormat:TextFormat",
"//submodules/AppBundle:AppBundle",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/ListSectionHeaderNode:ListSectionHeaderNode",
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/UrlHandling:UrlHandling",
"//submodules/UrlWhitelist:UrlWhitelist",
"//submodules/WebsiteType:WebsiteType",
"//submodules/PhotoResources:PhotoResources",
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/SemanticStatusNode:SemanticStatusNode",
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
"//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/ContextUI:ContextUI",
"//submodules/FileMediaResourceStatus:FileMediaResourceStatus",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
"$SDKROOT/System/Library/Frameworks/UIKit.framework",
],
)

View File

@ -0,0 +1,38 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ListMessageItem",
module_name = "ListMessageItem",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/TelegramCore:TelegramCore",
"//submodules/SyncCore:SyncCore",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/ItemListUI:ItemListUI",
"//submodules/AccountContext:AccountContext",
"//submodules/TextFormat:TextFormat",
"//submodules/AppBundle:AppBundle",
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
"//submodules/ListSectionHeaderNode:ListSectionHeaderNode",
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/UrlHandling:UrlHandling",
"//submodules/UrlWhitelist:UrlWhitelist",
"//submodules/WebsiteType:WebsiteType",
"//submodules/PhotoResources:PhotoResources",
"//submodules/RadialStatusNode:RadialStatusNode",
"//submodules/SemanticStatusNode:SemanticStatusNode",
"//submodules/MusicAlbumArtResources:MusicAlbumArtResources",
"//submodules/MediaPlayer:UniversalMediaPlayer",
"//submodules/ContextUI:ContextUI",
"//submodules/FileMediaResourceStatus:FileMediaResourceStatus",
],
visibility = [
"//visibility:public",
],
)

View File

@ -16,7 +16,7 @@ private let timezoneOffset: Int32 = {
return Int32(timeinfoNow.tm_gmtoff)
}()
func listMessageDateHeaderId(timestamp: Int32) -> Int64 {
public func listMessageDateHeaderId(timestamp: Int32) -> Int64 {
let unclippedValue: Int64 = min(Int64(Int32.max), Int64(timestamp) + Int64(timezoneOffset))
var time: time_t = time_t(Int32(clamping: unclippedValue))
@ -28,7 +28,7 @@ func listMessageDateHeaderId(timestamp: Int32) -> Int64 {
return Int64(roundedTimestamp)
}
func listMessageDateHeaderInfo(timestamp: Int32) -> (year: Int32, month: Int32) {
public func listMessageDateHeaderInfo(timestamp: Int32) -> (year: Int32, month: Int32) {
var time: time_t = time_t(timestamp + timezoneOffset)
var timeinfo: tm = tm()
localtime_r(&time, &timeinfo)
@ -76,7 +76,7 @@ final class ListMessageDateHeader: ListViewItemHeader {
}
}
final class ListMessageDateHeaderNode: ListViewItemHeaderNode {
public final class ListMessageDateHeaderNode: ListViewItemHeaderNode {
var theme: PresentationTheme
var strings: PresentationStrings
let headerNode: ListSectionHeaderNode
@ -99,7 +99,7 @@ final class ListMessageDateHeaderNode: ListViewItemHeaderNode {
self.headerNode.title = stringForMonth(strings: strings, month: month, ofYear: year).uppercased()
}
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
self.headerNode.updateTheme(theme: theme)
@ -109,7 +109,7 @@ final class ListMessageDateHeaderNode: ListViewItemHeaderNode {
self.setNeedsLayout()
}
override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel))
self.headerNode.frame = headerFrame
self.headerNode.updateLayout(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset)

View File

@ -18,6 +18,7 @@ import PhotoResources
import MusicAlbumArtResources
import UniversalMediaPlayer
import ContextUI
import FileMediaResourceStatus
private let extensionImageCache = Atomic<[UInt32: UIImage]>(value: [:])
@ -88,6 +89,36 @@ private func extensionImage(fileExtension: String?) -> UIImage? {
}
private let extensionFont = Font.with(size: 15.0, design: .round, traits: [.bold])
func fullAuthorString(for item: ListMessageItem) -> String {
var authorString = ""
if let author = item.message.author, [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(item.message.id.peerId.namespace) {
var authorName = ""
if author.id == item.context.account.peerId {
authorName = item.presentationData.strings.DialogList_You
} else {
authorName = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
}
if let peer = item.message.peers[item.message.id.peerId], author.id != peer.id {
authorString = "\(authorName)\(peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))"
} else {
authorString = authorName
}
} else if let peer = item.message.peers[item.message.id.peerId] {
if item.message.id.peerId.namespace == Namespaces.Peer.CloudChannel {
authorString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
} else {
if item.message.id.peerId == item.context.account.peerId {
authorString = item.presentationData.strings.DialogList_SavedMessages
} else if item.message.flags.contains(.Incoming) {
authorString = peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
} else {
authorString = "\(item.presentationData.strings.DialogList_You)\(peer.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder))"
}
}
}
return authorString
}
private struct FetchControls {
let fetch: () -> Void
let cancel: () -> Void
@ -122,7 +153,7 @@ private enum FileIconImage: Equatable {
}
}
final class ListMessageFileItemNode: ListMessageNode {
public final class ListMessageFileItemNode: ListMessageNode {
private let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
private let extractedBackgroundImageNode: ASImageNode
@ -140,6 +171,7 @@ final class ListMessageFileItemNode: ListMessageNode {
private let titleNode: TextNode
private let descriptionNode: TextNode
private let descriptionProgressNode: ImmediateTextNode
private let dateNode: TextNode
private let extensionIconNode: ASImageNode
private let extensionIconText: TextNode
@ -195,6 +227,9 @@ final class ListMessageFileItemNode: ListMessageNode {
self.descriptionProgressNode.isUserInteractionEnabled = false
self.descriptionProgressNode.maximumNumberOfLines = 1
self.dateNode = TextNode()
self.dateNode.isUserInteractionEnabled = false
self.extensionIconNode = ASImageNode()
self.extensionIconNode.isLayerBacked = true
self.extensionIconNode.displaysAsynchronously = false
@ -228,6 +263,7 @@ final class ListMessageFileItemNode: ListMessageNode {
self.offsetContainerNode.addSubnode(self.titleNode)
self.offsetContainerNode.addSubnode(self.descriptionNode)
self.offsetContainerNode.addSubnode(self.descriptionProgressNode)
self.offsetContainerNode.addSubnode(self.dateNode)
self.offsetContainerNode.addSubnode(self.extensionIconNode)
self.offsetContainerNode.addSubnode(self.extensionIconText)
self.offsetContainerNode.addSubnode(self.iconStatusNode)
@ -237,7 +273,7 @@ final class ListMessageFileItemNode: ListMessageNode {
return
}
item.controllerInteraction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture)
item.interaction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture)
}
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
@ -246,7 +282,7 @@ final class ListMessageFileItemNode: ListMessageNode {
}
if isExtracted {
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.theme.list.plainBackgroundColor)
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.theme.list.plainBackgroundColor)
}
if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect {
@ -260,6 +296,7 @@ final class ListMessageFileItemNode: ListMessageNode {
self?.extractedBackgroundImageNode.image = nil
}
})
transition.updateAlpha(node: strongSelf.dateNode, alpha: isExtracted ? 0.0 : 1.0)
}
}
@ -294,14 +331,15 @@ final class ListMessageFileItemNode: ListMessageNode {
self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
override func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
override public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode)
let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode)
let extensionIconTextMakeLayout = TextNode.asyncLayout(self.extensionIconText)
let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode)
let iconImageLayout = self.iconImageNode.asyncLayout()
let currentMedia = self.currentMedia
@ -315,13 +353,14 @@ final class ListMessageFileItemNode: ListMessageNode {
return { [weak self] item, params, _, _, dateHeaderAtBottom in
var updatedTheme: PresentationTheme?
if currentItem?.theme !== item.theme {
updatedTheme = item.theme
if currentItem?.presentationData.theme.theme !== item.presentationData.theme.theme {
updatedTheme = item.presentationData.theme.theme
}
let titleFont = Font.semibold(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0))
let audioTitleFont = Font.semibold(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0))
let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0))
let titleFont = Font.semibold(floor(item.presentationData.fontSize.baseDisplaySize * 16.0 / 17.0))
let audioTitleFont = Font.semibold(floor(item.presentationData.fontSize.baseDisplaySize * 16.0 / 17.0))
let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0))
let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
var leftInset: CGFloat = 65.0 + params.leftInset
let rightInset: CGFloat = 8.0 + params.rightInset
@ -329,7 +368,7 @@ final class ListMessageFileItemNode: ListMessageNode {
var leftOffset: CGFloat = 0.0
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
if case let .selectable(selected) = item.selection {
let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, false)
let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, false)
selectionNodeWidthAndApply = (selectionWidth, selectionApply)
leftOffset += selectionWidth
}
@ -363,61 +402,85 @@ final class ListMessageFileItemNode: ListMessageNode {
isAudio = true
isVoice = voice
titleText = NSAttributedString(string: title ?? (file.fileName ?? "Unknown Track"), font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor)
titleText = NSAttributedString(string: title ?? (file.fileName ?? "Unknown Track"), font: audioTitleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
let descriptionString: String
var descriptionString: String
if let performer = performer {
descriptionString = "\(stringForDuration(Int32(duration)))\(performer)"
if item.isGlobalSearchResult {
descriptionString = performer
} else {
descriptionString = "\(stringForDuration(Int32(duration)))\(performer)"
}
} else if let size = file.size {
descriptionString = dataSizeString(size, decimalSeparator: item.dateTimeFormat.decimalSeparator)
descriptionString = dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
} else {
descriptionString = ""
}
descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor)
if item.isGlobalSearchResult {
let authorString = fullAuthorString(for: item)
if descriptionString.isEmpty {
descriptionString = authorString
} else {
descriptionString = "\(descriptionString)\(authorString)"
}
}
descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
if !voice {
iconImage = .albumArt(file, SharedMediaPlaybackAlbumArt(thumbnailResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: true), fullSizeResource: ExternalMusicAlbumArtResource(title: title ?? "", performer: performer ?? "", isThumbnail: false)))
} else {
titleText = NSAttributedString(string: " ", font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor)
descriptionText = NSAttributedString(string: item.message.author?.displayTitle(strings: item.strings, displayOrder: .firstLast) ?? " ", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor)
titleText = NSAttributedString(string: " ", font: audioTitleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
descriptionText = NSAttributedString(string: item.message.author?.displayTitle(strings: item.presentationData.strings, displayOrder: .firstLast) ?? " ", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
}
}
}
if isInstantVideo || isVoice {
let authorName: String
var authorName: String
if let author = message.forwardInfo?.author {
if author.id == item.context.account.peerId {
authorName = item.strings.DialogList_You
authorName = item.presentationData.strings.DialogList_You
} else {
authorName = author.displayTitle(strings: item.strings, displayOrder: .firstLast)
authorName = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
}
} else if let signature = message.forwardInfo?.authorSignature {
authorName = signature
} else if let author = message.author {
if author.id == item.context.account.peerId {
authorName = item.strings.DialogList_You
authorName = item.presentationData.strings.DialogList_You
} else {
authorName = author.displayTitle(strings: item.strings, displayOrder: .firstLast)
authorName = author.displayTitle(strings: item.presentationData.strings, displayOrder: item.presentationData.nameDisplayOrder)
}
} else {
authorName = " "
}
titleText = NSAttributedString(string: authorName, font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor)
let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.strings, dateTimeFormat: item.dateTimeFormat)
let descriptionString: String
if let duration = file.duration {
descriptionString = "\(stringForDuration(Int32(duration)))\(dateString)"
} else {
descriptionString = dateString
if item.isGlobalSearchResult {
authorName = fullAuthorString(for: item)
}
descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor)
titleText = NSAttributedString(string: authorName, font: audioTitleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat)
var descriptionString: String = ""
if let duration = file.duration {
if item.isGlobalSearchResult {
descriptionString = stringForDuration(Int32(duration))
} else {
descriptionString = "\(stringForDuration(Int32(duration)))\(dateString)"
}
} else {
if !item.isGlobalSearchResult {
descriptionString = dateString
}
}
descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
iconImage = .roundVideo(file)
} else if !isAudio {
let fileName: String = file.fileName ?? ""
titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
var fileExtension: String?
if let range = fileName.range(of: ".", options: [.backwards]) {
@ -432,16 +495,31 @@ final class ListMessageFileItemNode: ListMessageNode {
iconImage = .imageRepresentation(file, representation)
}
let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.strings, dateTimeFormat: item.dateTimeFormat)
let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat)
let descriptionString: String
var descriptionString: String = ""
if let size = file.size {
descriptionString = "\(dataSizeString(size, decimalSeparator: item.dateTimeFormat.decimalSeparator))\(dateString)"
if item.isGlobalSearchResult {
descriptionString = (dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator))
} else {
descriptionString = "\(dataSizeString(size, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator))\(dateString)"
}
} else {
descriptionString = "\(dateString)"
if !item.isGlobalSearchResult {
descriptionString = "\(dateString)"
}
}
if item.isGlobalSearchResult {
let authorString = fullAuthorString(for: item)
if descriptionString.isEmpty {
descriptionString = authorString
} else {
descriptionString = "\(descriptionString)\(authorString)"
}
}
descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor)
descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
}
break
@ -478,7 +556,7 @@ final class ListMessageFileItemNode: ListMessageNode {
}
if statusUpdated {
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isSharedMedia: true)
updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult)
if isAudio || isInstantVideo {
if let currentUpdatedStatusSignal = updatedStatusSignal {
@ -494,14 +572,20 @@ final class ListMessageFileItemNode: ListMessageNode {
}
}
if isVoice {
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false)
updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult)
}
}
}
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 40.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.message.timestamp, relativeTo: timestamp, dateTimeFormat: item.presentationData.dateTimeFormat)
let dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0 - 40.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (dateNodeLayout, dateNodeApply) = dateNodeMakeLayout(TextNodeLayoutArguments(attributedString: dateAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 12.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: titleText, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - leftOffset - rightInset - dateNodeLayout.size.width - 4.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -511,17 +595,17 @@ final class ListMessageFileItemNode: ListMessageNode {
case let .imageRepresentation(_, representation):
let iconSize = CGSize(width: 40.0, height: 40.0)
let imageCorners = ImageCorners(radius: 6.0)
let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor)
let arguments = TransformImageArguments(corners: imageCorners, imageSize: representation.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor)
iconImageApply = iconImageLayout(arguments)
case .albumArt:
let iconSize = CGSize(width: 40.0, height: 40.0)
let imageCorners = ImageCorners(radius: iconSize.width / 2.0)
let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor)
let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor)
iconImageApply = iconImageLayout(arguments)
case let .roundVideo(file):
let iconSize = CGSize(width: 40.0, height: 40.0)
let imageCorners = ImageCorners(radius: iconSize.width / 2.0)
let arguments = TransformImageArguments(corners: imageCorners, imageSize: (file.dimensions ?? PixelDimensions(width: 320, height: 320)).cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor)
let arguments = TransformImageArguments(corners: imageCorners, imageSize: (file.dimensions ?? PixelDimensions(width: 320, height: 320)).cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor)
iconImageApply = iconImageLayout(arguments)
}
}
@ -532,7 +616,7 @@ final class ListMessageFileItemNode: ListMessageNode {
case let .imageRepresentation(file, representation):
updateIconImageSignal = chatWebpageSnippetFile(account: item.context.account, fileReference: .message(message: MessageReference(message), media: file), representation: representation)
case let .albumArt(file, albumArt):
updateIconImageSignal = playerAlbumArt(postbox: item.context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), emptyColor: item.theme.list.itemAccentColor)
updateIconImageSignal = playerAlbumArt(postbox: item.context.account.postbox, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), emptyColor: item.presentationData.theme.theme.list.itemAccentColor)
case let .roundVideo(file):
updateIconImageSignal = mediaGridMessageVideo(postbox: item.context.account.postbox, videoReference: FileMediaReference.message(message: MessageReference(message), media: file), autoFetchFullSizeThumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3))
}
@ -583,9 +667,9 @@ final class ListMessageFileItemNode: ListMessageNode {
strongSelf.currentLeftOffset = leftOffset
if let _ = updatedTheme {
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
strongSelf.linearProgressNode?.updateTheme(theme: item.theme)
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.theme.list.itemPlainSeparatorColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.theme.list.itemHighlightedBackgroundColor
strongSelf.linearProgressNode?.updateTheme(theme: item.presentationData.theme.theme)
}
if let (selectionWidth, selectionApply) = selectionNodeWidthAndApply {
@ -632,6 +716,10 @@ final class ListMessageFileItemNode: ListMessageNode {
transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 1.0), size: descriptionNodeLayout.size))
let _ = descriptionNodeApply()
let _ = dateNodeApply()
transition.updateFrame(node: strongSelf.dateNode, frame: CGRect(origin: CGPoint(x: params.width - rightInset - dateNodeLayout.size.width, y: 11.0), size: dateNodeLayout.size))
strongSelf.dateNode.isHidden = !item.isGlobalSearchResult
let iconFrame: CGRect
if isAudio {
let iconSize = CGSize(width: 40.0, height: 40.0)
@ -732,8 +820,8 @@ final class ListMessageFileItemNode: ListMessageNode {
var iconStatusForegroundColor: UIColor = .white
if isVoice {
iconStatusBackgroundColor = item.theme.list.itemAccentColor
iconStatusForegroundColor = item.theme.list.itemCheckColors.foregroundColor
iconStatusBackgroundColor = item.presentationData.theme.theme.list.itemAccentColor
iconStatusForegroundColor = item.presentationData.theme.theme.list.itemCheckColors.foregroundColor
}
if !isAudio && !isInstantVideo {
@ -767,7 +855,7 @@ final class ListMessageFileItemNode: ListMessageNode {
self.iconStatusNode.transitionToState(iconStatusState)
}
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
if highlighted, let item = self.item, case .none = item.selection {
@ -793,7 +881,7 @@ final class ListMessageFileItemNode: ListMessageNode {
}
}
override func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
override public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
if let item = self.item, item.message.id == id, self.iconImageNode.supernode != nil {
let iconImageNode = self.iconImageNode
return (self.iconImageNode, self.iconImageNode.bounds, { [weak iconImageNode] in
@ -803,15 +891,15 @@ final class ListMessageFileItemNode: ListMessageNode {
return nil
}
override func updateHiddenMedia() {
if let controllerInteraction = self.controllerInteraction, let item = self.item, controllerInteraction.hiddenMedia[item.message.id] != nil {
override public func updateHiddenMedia() {
if let interaction = self.interaction, let item = self.item, interaction.getHiddenMedia()[item.message.id] != nil {
self.iconImageNode.isHidden = true
} else {
self.iconImageNode.isHidden = false
}
}
override func updateSelectionState(animated: Bool) {
override public func updateSelectionState(animated: Bool) {
}
private func updateProgressFrame(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
@ -831,7 +919,7 @@ final class ListMessageFileItemNode: ListMessageNode {
switch fetchStatus {
case let .Fetching(_, progress):
if let file = self.currentMedia as? TelegramMediaFile, let size = file.size {
downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: item.dateTimeFormat.decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: item.dateTimeFormat.decimalSeparator))"
downloadingString = "\(dataSizeString(Int(Float(size) * progress), forceDecimal: true, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)) / \(dataSizeString(size, forceDecimal: true, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator))"
}
descriptionOffset = 14.0
case .Remote:
@ -849,7 +937,7 @@ final class ListMessageFileItemNode: ListMessageNode {
linearProgressNode = current
} else {
linearProgressNode = LinearProgressNode()
linearProgressNode.updateTheme(theme: item.theme)
linearProgressNode.updateTheme(theme: item.presentationData.theme.theme)
self.linearProgressNode = linearProgressNode
self.addSubnode(linearProgressNode)
}
@ -859,7 +947,7 @@ final class ListMessageFileItemNode: ListMessageNode {
if self.downloadStatusIconNode.supernode == nil {
self.offsetContainerNode.addSubnode(self.downloadStatusIconNode)
}
self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadPauseIcon(item.theme)
self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadPauseIcon(item.presentationData.theme.theme)
case .Local:
if let linearProgressNode = self.linearProgressNode {
self.linearProgressNode = nil
@ -883,7 +971,7 @@ final class ListMessageFileItemNode: ListMessageNode {
if self.downloadStatusIconNode.supernode == nil {
self.offsetContainerNode.addSubnode(self.downloadStatusIconNode)
}
self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(item.theme)
self.downloadStatusIconNode.image = PresentationResourcesChat.sharedMediaFileDownloadStartIcon(item.presentationData.theme.theme)
}
} else {
if let linearProgressNode = self.linearProgressNode {
@ -911,8 +999,8 @@ final class ListMessageFileItemNode: ListMessageNode {
self.descriptionProgressNode.isHidden = true
self.descriptionNode.isHidden = false
}
let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 13.0 / 17.0))
self.descriptionProgressNode.attributedText = NSAttributedString(string: downloadingString ?? "", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor)
let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 13.0 / 17.0))
self.descriptionProgressNode.attributedText = NSAttributedString(string: downloadingString ?? "", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
let descriptionSize = self.descriptionProgressNode.updateLayout(CGSize(width: size.width - 14.0, height: size.height))
transition.updateFrame(node: self.descriptionProgressNode, frame: CGRect(origin: self.descriptionNode.frame.origin, size: descriptionSize))
@ -936,8 +1024,8 @@ final class ListMessageFileItemNode: ListMessageNode {
fetch()
}
case .Local:
if let item = self.item, let controllerInteraction = self.controllerInteraction {
let _ = controllerInteraction.openMessage(item.message, .default)
if let item = self.item, let interaction = self.interaction {
let _ = interaction.openMessage(item.message, .default)
}
}
case .playbackStatus:
@ -948,11 +1036,11 @@ final class ListMessageFileItemNode: ListMessageNode {
}
}
override func header() -> ListViewItemHeader? {
override public func header() -> ListViewItemHeader? {
return self.item?.header
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let item = self.item, case .selectable = item.selection {
if self.bounds.contains(point) {
return self.view

View File

@ -10,51 +10,73 @@ import TelegramPresentationData
import AccountContext
import TelegramUIPreferences
final class ListMessageItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let fontSize: PresentationFontSize
let dateTimeFormat: PresentationDateTimeFormat
public final class ListMessageItemInteraction {
let openMessage: (Message, ChatControllerInteractionOpenMessageMode) -> Bool
let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void
let toggleMessagesSelection: ([MessageId], Bool) -> Void
let openUrl: (String, Bool, Bool?, Message?) -> Void
let openInstantPage: (Message, ChatMessageItemAssociatedData?) -> Void
let longTap: (ChatControllerInteractionLongTapAction, Message?) -> Void
let getHiddenMedia: () -> [MessageId: [Media]]
public init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, getHiddenMedia: @escaping () -> [MessageId: [Media]]) {
self.openMessage = openMessage
self.openMessageContextMenu = openMessageContextMenu
self.toggleMessagesSelection = toggleMessagesSelection
self.openUrl = openUrl
self.openInstantPage = openInstantPage
self.longTap = longTap
self.getHiddenMedia = getHiddenMedia
}
}
public final class ListMessageItem: ListViewItem {
let presentationData: ChatPresentationData
let context: AccountContext
let chatLocation: ChatLocation
let controllerInteraction: ChatControllerInteraction
let interaction: ListMessageItemInteraction
let message: Message
let selection: ChatHistoryMessageSelection
let hintIsLink: Bool
let isGlobalSearchResult: Bool
let header: ListMessageDateHeader?
let header: ListViewItemHeader?
let selectable: Bool = true
public let selectable: Bool = true
public init(theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, context: AccountContext, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool) {
self.theme = theme
self.strings = strings
self.fontSize = fontSize
self.dateTimeFormat = dateTimeFormat
public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false) {
self.presentationData = presentationData
self.context = context
self.chatLocation = chatLocation
self.controllerInteraction = controllerInteraction
self.interaction = interaction
self.message = message
if displayHeader {
self.header = ListMessageDateHeader(timestamp: message.timestamp, theme: theme, strings: strings, fontSize: fontSize)
if let header = customHeader {
self.header = header
} else if displayHeader {
self.header = ListMessageDateHeader(timestamp: message.timestamp, theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize)
} else {
self.header = nil
}
self.selection = selection
self.hintIsLink = hintIsLink
self.isGlobalSearchResult = isGlobalSearchResult
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
var viewClassName: AnyClass = ListMessageSnippetItemNode.self
for media in message.media {
if let _ = media as? TelegramMediaFile {
viewClassName = ListMessageFileItemNode.self
break
if !self.hintIsLink {
for media in self.message.media {
if let _ = media as? TelegramMediaFile {
viewClassName = ListMessageFileItemNode.self
break
}
}
}
let configure = { () -> Void in
let node = (viewClassName as! ListMessageNode.Type).init()
node.controllerInteraction = self.controllerInteraction
node.interaction = self.interaction
node.setupItem(self)
let nodeLayout = node.asyncLayout()
@ -106,11 +128,11 @@ final class ListMessageItem: ListViewItem {
}
}
func selected(listView: ListView) {
public func selected(listView: ListView) {
listView.clearHighlightAnimated(true)
if case let .selectable(selected) = self.selection {
self.controllerInteraction.toggleMessagesSelection([self.message.id], !selected)
self.interaction.toggleMessagesSelection([self.message.id], !selected)
} else {
listView.forEachItemNode { itemNode in
if let itemNode = itemNode as? ListMessageFileItemNode {

View File

@ -3,10 +3,11 @@ import UIKit
import Display
import AsyncDisplayKit
import Postbox
import AccountContext
class ListMessageNode: ListViewItemNode {
public class ListMessageNode: ListViewItemNode {
var item: ListMessageItem?
var controllerInteraction: ChatControllerInteraction?
var interaction: ListMessageItemInteraction?
required init() {
super.init(layerBacked: false, dynamicBounce: false)
@ -19,7 +20,7 @@ class ListMessageNode: ListViewItemNode {
override public func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
}
func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
return { _, params, _, _, _ in
return (ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 1.0), insets: UIEdgeInsets()), { _ in
@ -27,13 +28,13 @@ class ListMessageNode: ListViewItemNode {
}
}
func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
return nil
}
func updateHiddenMedia() {
public func updateHiddenMedia() {
}
func updateSelectionState(animated: Bool) {
public func updateSelectionState(animated: Bool) {
}
}

View File

@ -13,12 +13,15 @@ import TextFormat
import PhotoResources
import WebsiteType
import UrlHandling
import UrlWhitelist
import AccountContext
import TelegramStringFormatting
private let iconFont = Font.with(size: 30.0, design: .round, traits: [.bold])
private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radius: 6.0, color: UIColor(rgb: 0xFF9500))
final class ListMessageSnippetItemNode: ListMessageNode {
public final class ListMessageSnippetItemNode: ListMessageNode {
private let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
private let extractedBackgroundImageNode: ASImageNode
@ -34,9 +37,11 @@ final class ListMessageSnippetItemNode: ListMessageNode {
private let titleNode: TextNode
private let descriptionNode: TextNode
private let dateNode: TextNode
private let instantViewIconNode: ASImageNode
private let linkNode: TextNode
private var linkHighlightingNode: LinkHighlightingNode?
private let authorNode: TextNode
private let iconTextBackgroundNode: ASImageNode
private let iconTextNode: TextNode
@ -44,7 +49,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
private var currentIconImageRepresentation: TelegramMediaImageRepresentation?
private var currentMedia: Media?
var currentPrimaryUrl: String?
public var currentPrimaryUrl: String?
private var currentIsInstantView: Bool?
private var appliedItem: ListMessageItem?
@ -72,6 +77,9 @@ final class ListMessageSnippetItemNode: ListMessageNode {
self.descriptionNode = TextNode()
self.descriptionNode.isUserInteractionEnabled = false
self.dateNode = TextNode()
self.dateNode.isUserInteractionEnabled = false
self.instantViewIconNode = ASImageNode()
self.instantViewIconNode.isLayerBacked = true
self.instantViewIconNode.displaysAsynchronously = false
@ -90,6 +98,9 @@ final class ListMessageSnippetItemNode: ListMessageNode {
self.iconImageNode = TransformImageNode()
self.iconImageNode.displaysAsynchronously = false
self.authorNode = TextNode()
self.authorNode.isUserInteractionEnabled = false
super.init()
self.addSubnode(self.separatorNode)
@ -102,16 +113,18 @@ final class ListMessageSnippetItemNode: ListMessageNode {
self.contextSourceNode.contentNode.addSubnode(self.offsetContainerNode)
self.offsetContainerNode.addSubnode(self.titleNode)
self.offsetContainerNode.addSubnode(self.descriptionNode)
self.offsetContainerNode.addSubnode(self.dateNode)
self.offsetContainerNode.addSubnode(self.linkNode)
self.offsetContainerNode.addSubnode(self.instantViewIconNode)
self.offsetContainerNode.addSubnode(self.iconImageNode)
self.offsetContainerNode.addSubnode(self.authorNode)
self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self, let item = strongSelf.item else {
return
}
item.controllerInteraction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture)
item.interaction.openMessageContextMenu(item.message, false, strongSelf.contextSourceNode, strongSelf.contextSourceNode.bounds, gesture)
}
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtracted, transition in
@ -120,7 +133,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
}
if isExtracted {
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.theme.list.plainBackgroundColor)
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.theme.list.plainBackgroundColor)
}
if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect {
@ -135,6 +148,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
self?.extractedBackgroundImageNode.image = nil
}
})
transition.updateAlpha(node: strongSelf.dateNode, alpha: isExtracted ? 0.0 : 1.0)
}
}
@ -142,7 +156,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
fatalError("init(coder:) has not been implemented")
}
override func didLoad() {
override public func didLoad() {
super.didLoad()
let recognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapLongTapOrDoubleTapGesture(_:)))
@ -182,17 +196,19 @@ final class ListMessageSnippetItemNode: ListMessageNode {
self.addTransitionOffsetAnimation(0.0, duration: duration, beginAt: currentTimestamp)
}
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
override public func animateRemoved(_ currentTimestamp: Double, duration: Double) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
}
override func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
override public func asyncLayout() -> (_ item: ListMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: Bool, _ mergedBottom: Bool, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation) -> Void) {
let titleNodeMakeLayout = TextNode.asyncLayout(self.titleNode)
let descriptionNodeMakeLayout = TextNode.asyncLayout(self.descriptionNode)
let linkNodeMakeLayout = TextNode.asyncLayout(self.linkNode)
let dateNodeMakeLayout = TextNode.asyncLayout(self.dateNode)
let iconTextMakeLayout = TextNode.asyncLayout(self.iconTextNode)
let iconImageLayout = self.iconImageNode.asyncLayout()
let authorNodeMakeLayout = TextNode.asyncLayout(self.authorNode)
let currentIconImageRepresentation = self.currentIconImageRepresentation
let currentItem = self.appliedItem
@ -202,19 +218,21 @@ final class ListMessageSnippetItemNode: ListMessageNode {
return { [weak self] item, params, _, _, dateHeaderAtBottom in
var updatedTheme: PresentationTheme?
if currentItem?.theme !== item.theme {
updatedTheme = item.theme
if currentItem?.presentationData.theme.theme !== item.presentationData.theme.theme {
updatedTheme = item.presentationData.theme.theme
}
let titleFont = Font.semibold(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0))
let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0))
let titleFont = Font.semibold(floor(item.presentationData.fontSize.baseDisplaySize * 16.0 / 17.0))
let descriptionFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0))
let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
let authorFont = Font.regular(floor(item.presentationData.fontSize.baseDisplaySize * 14.0 / 17.0))
let leftInset: CGFloat = 65.0 + params.leftInset
var leftOffset: CGFloat = 0.0
var selectionNodeWidthAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
if case let .selectable(selected) = item.selection {
let (selectionWidth, selectionApply) = selectionNodeLayout(item.theme.list.itemCheckColors.strokeColor, item.theme.list.itemCheckColors.fillColor, item.theme.list.itemCheckColors.foregroundColor, selected, false)
let (selectionWidth, selectionApply) = selectionNodeLayout(item.presentationData.theme.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.theme.list.itemCheckColors.fillColor, item.presentationData.theme.theme.list.itemCheckColors.foregroundColor, selected, false)
selectionNodeWidthAndApply = (selectionWidth, selectionApply)
leftOffset += selectionWidth
}
@ -255,7 +273,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
iconText = NSAttributedString(string: host[..<host.index(after: host.startIndex)].uppercased(), font: iconFont, textColor: UIColor.white)
}
title = NSAttributedString(string: content.title ?? content.websiteName ?? hostName, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
title = NSAttributedString(string: content.title ?? content.websiteName ?? hostName, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
if let image = content.image {
if let representation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 80, height: 80)) {
@ -268,11 +286,11 @@ final class ListMessageSnippetItemNode: ListMessageNode {
}
let mutableDescriptionText = NSMutableAttributedString()
if let text = content.text {
mutableDescriptionText.append(NSAttributedString(string: text + "\n", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor))
if let text = content.text, !item.isGlobalSearchResult {
mutableDescriptionText.append(NSAttributedString(string: text + "\n", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor))
}
let plainUrlString = NSAttributedString(string: content.displayUrl, font: descriptionFont, textColor: item.theme.list.itemAccentColor)
let plainUrlString = NSAttributedString(string: content.displayUrl, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemAccentColor)
let urlString = NSMutableAttributedString()
urlString.append(plainUrlString)
urlString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.URL), value: content.displayUrl, range: NSMakeRange(0, urlString.length))
@ -294,6 +312,21 @@ final class ListMessageSnippetItemNode: ListMessageNode {
}
}
for media in item.message.media {
if let image = media as? TelegramMediaImage {
if let representation = imageRepresentationLargerThan(image.representations, size: PixelDimensions(width: 80, height: 80)) {
iconImageReferenceAndRepresentation = (.message(message: MessageReference(item.message), media: image), representation)
}
break
}
if let file = media as? TelegramMediaFile {
if let representation = smallestImageRepresentation(file.previewRepresentations) {
iconImageReferenceAndRepresentation = (.message(message: MessageReference(item.message), media: file), representation)
}
break
}
}
var entities: [MessageTextEntity]?
entities = messageEntities
@ -328,25 +361,25 @@ final class ListMessageSnippetItemNode: ListMessageNode {
if let url = parsedUrl, let host = host {
primaryUrl = urlString
if url.path.hasPrefix("/addstickers/") {
title = NSAttributedString(string: urlString, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
title = NSAttributedString(string: urlString, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
iconText = NSAttributedString(string: "S", font: iconFont, textColor: UIColor.white)
} else {
iconText = NSAttributedString(string: host[..<host.index(after: host.startIndex)].uppercased(), font: iconFont, textColor: UIColor.white)
title = NSAttributedString(string: host, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor)
title = NSAttributedString(string: host, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
}
let mutableDescriptionText = NSMutableAttributedString()
let (messageTextUrl, _) = parseUrl(url: item.message.text, wasConcealed: false)
if messageTextUrl != rawUrlString {
mutableDescriptionText.append(NSAttributedString(string: item.message.text + "\n", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor))
if messageTextUrl != rawUrlString, !item.isGlobalSearchResult {
mutableDescriptionText.append(NSAttributedString(string: item.message.text + "\n", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor))
}
let urlAttributedString = NSMutableAttributedString()
urlAttributedString.append(NSAttributedString(string: urlString, font: descriptionFont, textColor: item.theme.list.itemAccentColor))
if item.theme.list.itemAccentColor.isEqual(item.theme.list.itemPrimaryTextColor) {
urlAttributedString.append(NSAttributedString(string: urlString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemAccentColor))
if item.presentationData.theme.theme.list.itemAccentColor.isEqual(item.presentationData.theme.theme.list.itemPrimaryTextColor) {
urlAttributedString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: NSMakeRange(0, urlAttributedString.length))
}
urlAttributedString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.URL), value: urlString, range: NSMakeRange(0, urlAttributedString.length))
@ -355,6 +388,50 @@ final class ListMessageSnippetItemNode: ListMessageNode {
descriptionText = mutableDescriptionText
}
break loop
case let .TextUrl(url):
var range = NSRange(location: entity.range.lowerBound, length: entity.range.upperBound - entity.range.lowerBound)
let nsString = item.message.text as NSString
if range.location + range.length > nsString.length {
range.location = max(0, nsString.length - range.length)
range.length = nsString.length - range.location
}
let tempTitleString = (nsString.substring(with: range) as String).trimmingCharacters(in: .whitespacesAndNewlines)
var (urlString, concealed) = parseUrl(url: url, wasConcealed: false)
let rawUrlString = urlString
var parsedUrl = URL(string: urlString)
if parsedUrl == nil || parsedUrl!.host == nil || parsedUrl!.host!.isEmpty {
urlString = "http://" + urlString
parsedUrl = URL(string: urlString)
}
let host: String? = concealed ? urlString : parsedUrl?.host
if let url = parsedUrl, let host = host {
primaryUrl = urlString
title = NSAttributedString(string: tempTitleString as String, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor)
if url.path.hasPrefix("/addstickers/") {
iconText = NSAttributedString(string: "S", font: iconFont, textColor: UIColor.white)
} else {
iconText = NSAttributedString(string: host[..<host.index(after: host.startIndex)].uppercased(), font: iconFont, textColor: UIColor.white)
}
let mutableDescriptionText = NSMutableAttributedString()
let (messageTextUrl, _) = parseUrl(url: item.message.text, wasConcealed: false)
if messageTextUrl != rawUrlString, !item.isGlobalSearchResult {
mutableDescriptionText.append(NSAttributedString(string: item.message.text + "\n", font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor))
}
let urlAttributedString = NSMutableAttributedString()
urlAttributedString.append(NSAttributedString(string: urlString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemAccentColor))
if item.presentationData.theme.theme.list.itemAccentColor.isEqual(item.presentationData.theme.theme.list.itemPrimaryTextColor) {
urlAttributedString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue as NSNumber, range: NSMakeRange(0, urlAttributedString.length))
}
urlAttributedString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.URL), value: urlString, range: NSMakeRange(0, urlAttributedString.length))
linkText = urlAttributedString
descriptionText = mutableDescriptionText
}
break loop
default:
break
}
@ -362,14 +439,20 @@ final class ListMessageSnippetItemNode: ListMessageNode {
}
}
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - params.rightInset - 16.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.message.timestamp, relativeTo: timestamp, dateTimeFormat: item.presentationData.dateTimeFormat)
let dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
let (dateNodeLayout, dateNodeApply) = dateNodeMakeLayout(TextNodeLayoutArguments(attributedString: dateAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 12.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(TextNodeLayoutArguments(attributedString: title, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .middle, constrainedSize: CGSize(width: params.width - leftInset - leftOffset - 8.0 - params.rightInset - 16.0 - dateNodeLayout.size.width, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(TextNodeLayoutArguments(attributedString: descriptionText, backgroundColor: nil, maximumNumberOfLines: 3, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - params.rightInset - 16.0 - 8.0, height: CGFloat.infinity), alignment: .natural, lineSpacing: 0.3, cutout: nil, insets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)))
let (linkNodeLayout, linkNodeApply) = linkNodeMakeLayout(TextNodeLayoutArguments(attributedString: linkText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - 8.0 - params.rightInset - 16.0 - 8.0, height: CGFloat.infinity), alignment: .natural, lineSpacing: 0.3, cutout: isInstantView ? TextNodeCutout(topLeft: CGSize(width: 14.0, height: 8.0)) : nil, insets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)))
var instantViewImage: UIImage?
if isInstantView {
instantViewImage = PresentationResourcesChat.sharedMediaInstantViewIcon(item.theme)
instantViewImage = PresentationResourcesChat.sharedMediaInstantViewIcon(item.presentationData.theme.theme)
}
let (iconTextLayout, iconTextApply) = iconTextMakeLayout(TextNodeLayoutArguments(attributedString: iconText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
@ -378,7 +461,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
if let iconImageReferenceAndRepresentation = iconImageReferenceAndRepresentation {
let iconSize = CGSize(width: 40.0, height: 40.0)
let imageCorners = ImageCorners(radius: 6.0)
let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconImageReferenceAndRepresentation.1.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.theme.list.mediaPlaceholderColor)
let arguments = TransformImageArguments(corners: imageCorners, imageSize: iconImageReferenceAndRepresentation.1.dimensions.cgSize.aspectFilled(iconSize), boundingSize: iconSize, intrinsicInsets: UIEdgeInsets(), emptyColor: item.presentationData.theme.theme.list.mediaPlaceholderColor)
iconImageApply = iconImageLayout(arguments)
}
@ -394,7 +477,19 @@ final class ListMessageSnippetItemNode: ListMessageNode {
}
}
let contentHeight = 9.0 + titleNodeLayout.size.height + 10.0 + descriptionNodeLayout.size.height + linkNodeLayout.size.height
var authorString = ""
if item.isGlobalSearchResult {
authorString = fullAuthorString(for: item)
}
let authorText = NSAttributedString(string: authorString, font: authorFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
let (authorNodeLayout, authorNodeApply) = authorNodeMakeLayout(TextNodeLayoutArguments(attributedString: authorText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - params.rightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var contentHeight = 9.0 + titleNodeLayout.size.height + 10.0 + descriptionNodeLayout.size.height + linkNodeLayout.size.height
if item.isGlobalSearchResult {
contentHeight += authorNodeLayout.size.height
}
var insets = UIEdgeInsets()
if dateHeaderAtBottom, let header = item.header {
@ -434,8 +529,8 @@ final class ListMessageSnippetItemNode: ListMessageNode {
strongSelf.currentIsInstantView = isInstantView
if let _ = updatedTheme {
strongSelf.separatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.theme.list.itemHighlightedBackgroundColor
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.theme.list.itemPlainSeparatorColor
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.theme.list.itemHighlightedBackgroundColor
}
if let (selectionWidth, selectionApply) = selectionNodeWidthAndApply {
@ -468,10 +563,18 @@ final class ListMessageSnippetItemNode: ListMessageNode {
transition.updateFrame(node: strongSelf.descriptionNode, frame: descriptionFrame)
let _ = descriptionNodeApply()
let _ = dateNodeApply()
transition.updateFrame(node: strongSelf.dateNode, frame: CGRect(origin: CGPoint(x: params.width - params.rightInset - dateNodeLayout.size.width - 8.0, y: 11.0), size: dateNodeLayout.size))
strongSelf.dateNode.isHidden = !item.isGlobalSearchResult
let linkFrame = CGRect(origin: CGPoint(x: leftOffset + leftInset - 1.0, y: descriptionFrame.maxY), size: linkNodeLayout.size)
transition.updateFrame(node: strongSelf.linkNode, frame: linkFrame)
let _ = linkNodeApply()
let _ = authorNodeApply()
transition.updateFrame(node: strongSelf.authorNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset, y: linkFrame.maxY + 1.0), size: authorNodeLayout.size))
strongSelf.authorNode.isHidden = !item.isGlobalSearchResult
if let image = instantViewImage {
strongSelf.instantViewIconNode.image = image
transition.updateFrame(node: strongSelf.instantViewIconNode, frame: CGRect(origin: linkFrame.origin.offsetBy(dx: 0.0, dy: 4.0), size: image.size))
@ -528,7 +631,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
}
}
override func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
override public func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
super.setHighlighted(highlighted, at: point, animated: animated)
if highlighted, let item = self.item, case .none = item.selection, self.urlAtPoint(point) == nil {
@ -554,7 +657,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
}
}
override func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
override public func transitionNode(id: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
if let item = self.item, item.message.id == id, self.iconImageNode.supernode != nil {
let iconImageNode = self.iconImageNode
return (self.iconImageNode, self.iconImageNode.bounds, { [weak iconImageNode] in
@ -564,15 +667,15 @@ final class ListMessageSnippetItemNode: ListMessageNode {
return nil
}
override func updateHiddenMedia() {
if let controllerInteraction = self.controllerInteraction, let item = self.item, controllerInteraction.hiddenMedia[item.message.id] != nil {
override public func updateHiddenMedia() {
if let interaction = self.interaction, let item = self.item, interaction.getHiddenMedia()[item.message.id] != nil {
self.iconImageNode.isHidden = true
} else {
self.iconImageNode.isHidden = false
}
}
override func updateSelectionState(animated: Bool) {
override public func updateSelectionState(animated: Bool) {
}
func activateMedia() {
@ -580,30 +683,28 @@ final class ListMessageSnippetItemNode: ListMessageNode {
if let webpage = self.currentMedia as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
if content.instantPage != nil {
if websiteType(of: content.websiteName) == .instagram {
if !item.controllerInteraction.openMessage(item.message, .default) {
item.controllerInteraction.openInstantPage(item.message, nil)
if !item.interaction.openMessage(item.message, .default) {
item.interaction.openInstantPage(item.message, nil)
}
} else {
item.controllerInteraction.openInstantPage(item.message, nil)
item.interaction.openInstantPage(item.message, nil)
}
} else {
if isTelegramMeLink(content.url) || !item.controllerInteraction.openMessage(item.message, .link) {
item.controllerInteraction.openUrl(currentPrimaryUrl, false, false, nil)
if isTelegramMeLink(content.url) || !item.interaction.openMessage(item.message, .link) {
item.interaction.openUrl(currentPrimaryUrl, false, false, nil)
}
}
} else {
if !item.controllerInteraction.openMessage(item.message, .default) {
item.controllerInteraction.openUrl(currentPrimaryUrl, false, false, nil)
}
item.interaction.openUrl(currentPrimaryUrl, false, false, nil)
}
}
}
override func header() -> ListViewItemHeader? {
override public func header() -> ListViewItemHeader? {
return self.item?.header
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let item = self.item, case .selectable = item.selection {
if self.bounds.contains(point) {
return self.view
@ -640,13 +741,13 @@ final class ListMessageSnippetItemNode: ListMessageNode {
case .tap, .longTap:
if let item = self.item, let url = self.urlAtPoint(location) {
if case .longTap = gesture {
item.controllerInteraction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message)
item.interaction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message)
} else if url == self.currentPrimaryUrl {
if !item.controllerInteraction.openMessage(item.message, .default) {
item.controllerInteraction.openUrl(url, false, false, nil)
if !item.interaction.openMessage(item.message, .default) {
item.interaction.openUrl(url, false, false, nil)
}
} else {
item.controllerInteraction.openUrl(url, false, true, nil)
item.interaction.openUrl(url, false, true, nil)
}
}
case .hold, .doubleTap:
@ -683,7 +784,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
if let current = self.linkHighlightingNode {
linkHighlightingNode = current
} else {
linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.theme.chat.message.incoming.linkHighlightColor : item.theme.chat.message.outgoing.linkHighlightColor)
linkHighlightingNode = LinkHighlightingNode(color: item.message.effectivelyIncoming(item.context.account.peerId) ? item.presentationData.theme.theme.chat.message.incoming.linkHighlightColor : item.presentationData.theme.theme.chat.message.outgoing.linkHighlightColor)
self.linkHighlightingNode = linkHighlightingNode
self.offsetContainerNode.insertSubnode(linkHighlightingNode, belowSubnode: self.linkNode)
}

View File

@ -250,7 +250,7 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
updatedMedia[i] = TelegramMediaMap(latitude: media.latitude, longitude: media.longitude, geoPlace: media.geoPlace, venue: media.venue, liveBroadcastingTimeout: max(0, timestamp - currentMessage.timestamp - 1))
}
}
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia))
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: updatedMedia))
})
}
}).start()

View File

@ -10,6 +10,7 @@ static_library(
"//submodules/SyncCore:SyncCore#shared",
"//submodules/Postbox:Postbox#shared",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View File

@ -11,6 +11,7 @@ swift_library(
"//submodules/SyncCore:SyncCore",
"//submodules/Postbox:Postbox",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
],
visibility = [
"//visibility:public",

View File

@ -3,10 +3,10 @@ load("//Config:buck_rule_macros.bzl", "static_library", "framework", "glob_map",
framework(
name = "MtProtoKit",
srcs = glob([
"Sources/*.m",
"Sources/**/*.m",
]),
headers = glob([
"Sources/*.h",
"Sources/**/*.h",
]),
exported_headers = glob([
"PublicHeaders/**/*.h",

View File

@ -4,8 +4,8 @@ objc_library(
enable_modules = True,
module_name = "MtProtoKit",
srcs = glob([
"Sources/*.m",
"Sources/*.h",
"Sources/**/*.m",
"Sources/**/*.h",
]),
hdrs = glob([
"PublicHeaders/**/*.h",

View File

@ -0,0 +1,9 @@
#import <Foundation/Foundation.h>
#import <MtProtoKit/MTMessageService.h>
#import <MtProtoKit/MTDatacenterAuthInfo.h>
@interface MTBindKeyMessageService : NSObject <MTMessageService>
- (instancetype)initWithPersistentKey:(MTDatacenterAuthKey *)persistentKey ephemeralKey:(MTDatacenterAuthKey *)ephemeralKey completion:(void (^)(bool))completion;
@end

View File

@ -20,7 +20,7 @@
@optional
- (void)contextDatacenterAddressSetUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId addressSet:(MTDatacenterAddressSet *)addressSet;
- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo;
- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector;
- (void)contextDatacenterAuthTokenUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authToken:(id)authToken;
- (void)contextDatacenterTransportSchemesUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId shouldReset:(bool)shouldReset;
- (void)contextIsPasswordRequiredUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId;
@ -49,6 +49,7 @@
@property (nonatomic, strong, readonly) MTApiEnvironment *apiEnvironment;
@property (nonatomic, readonly) bool isTestingEnvironment;
@property (nonatomic, readonly) bool useTempAuthKeys;
@property (nonatomic) int32_t tempKeyExpiration;
+ (int32_t)fixedTimeDifference;
+ (void)setFixedTimeDifference:(int32_t)fixedTimeDifference;
@ -73,7 +74,7 @@
- (void)updateAddressSetForDatacenterWithId:(NSInteger)datacenterId addressSet:(MTDatacenterAddressSet *)addressSet forceUpdateSchemes:(bool)forceUpdateSchemes;
- (void)addAddressForDatacenterWithId:(NSInteger)datacenterId address:(MTDatacenterAddress *)address;
- (void)updateTransportSchemeForDatacenterWithId:(NSInteger)datacenterId transportScheme:(MTTransportScheme *)transportScheme media:(bool)media isProxy:(bool)isProxy;
- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo;
- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector;
- (bool)isPasswordInputRequiredForDatacenterWithId:(NSInteger)datacenterId;
- (bool)updatePasswordInputRequiredForDatacenterWithId:(NSInteger)datacenterId required:(bool)required;
@ -95,7 +96,7 @@
- (void)transportSchemeForDatacenterWithIdRequired:(NSInteger)datacenterId media:(bool)media;
- (void)invalidateTransportSchemeForDatacenterId:(NSInteger)datacenterId transportScheme:(MTTransportScheme *)transportScheme isProbablyHttp:(bool)isProbablyHttp media:(bool)media;
- (void)revalidateTransportSchemeForDatacenterId:(NSInteger)datacenterId transportScheme:(MTTransportScheme *)transportScheme media:(bool)media;
- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId;
- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId selector:(MTDatacenterAuthInfoSelector)selector;
- (NSArray<NSDictionary *> *)publicKeysForDatacenterWithId:(NSInteger)datacenterId;
- (void)updatePublicKeysForDatacenterWithId:(NSInteger)datacenterId publicKeys:(NSArray<NSDictionary *> *)publicKeys;
@ -107,8 +108,7 @@
- (void)updateAuthTokenForDatacenterWithId:(NSInteger)datacenterId authToken:(id)authToken;
- (void)addressSetForDatacenterWithIdRequired:(NSInteger)datacenterId;
- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn;
- (void)tempAuthKeyForDatacenterWithIdRequired:(NSInteger)datacenterId keyType:(MTDatacenterAuthTempKeyType)keyType;
- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn selector:(MTDatacenterAuthInfoSelector)selector;
- (void)authTokenForDatacenterWithIdRequired:(NSInteger)datacenterId authToken:(id)authToken masterDatacenterId:(NSInteger)masterDatacenterId;
- (void)reportProblemsWithDatacenterAddressForId:(NSInteger)datacenterId address:(MTDatacenterAddress *)address;

View File

@ -4,23 +4,12 @@
@class MTContext;
@class MTDatacenterAuthAction;
@protocol MTDatacenterAuthActionDelegate <NSObject>
- (void)datacenterAuthActionCompleted:(MTDatacenterAuthAction *)action;
@end
@interface MTDatacenterAuthAction : NSObject
@property (nonatomic, readonly) bool tempAuth;
@property (nonatomic, weak) id<MTDatacenterAuthActionDelegate> delegate;
@property (nonatomic, copy) void (^completedWithResult)(bool);
- (instancetype)initWithAuthKeyInfoSelector:(MTDatacenterAuthInfoSelector)authKeyInfoSelector isCdn:(bool)isCdn completion:(void (^)(MTDatacenterAuthAction *, bool))completion;
- (instancetype)initWithTempAuth:(bool)tempAuth tempAuthKeyType:(MTDatacenterAuthTempKeyType)tempAuthKeyType bindKey:(MTDatacenterAuthKey *)bindKey;
- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId isCdn:(bool)isCdn;
- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId;
- (void)cancel;
@end

View File

@ -1,10 +1,5 @@
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, MTDatacenterAuthTempKeyType) {
MTDatacenterAuthTempKeyTypeMain,
MTDatacenterAuthTempKeyTypeMedia
};
@interface MTDatacenterAuthKey: NSObject <NSCoding>
@property (nonatomic, strong, readonly) NSData *authKey;
@ -15,24 +10,24 @@ typedef NS_ENUM(NSUInteger, MTDatacenterAuthTempKeyType) {
@end
typedef NS_ENUM(int64_t, MTDatacenterAuthInfoSelector) {
MTDatacenterAuthInfoSelectorPersistent = 0,
MTDatacenterAuthInfoSelectorEphemeralMain,
MTDatacenterAuthInfoSelectorEphemeralMedia
};
@interface MTDatacenterAuthInfo : NSObject <NSCoding>
@property (nonatomic, strong, readonly) NSData *authKey;
@property (nonatomic, readonly) int64_t authKeyId;
@property (nonatomic, strong, readonly) NSArray *saltSet;
@property (nonatomic, strong, readonly) NSDictionary *authKeyAttributes;
@property (nonatomic, strong, readonly) MTDatacenterAuthKey *mainTempAuthKey;
@property (nonatomic, strong, readonly) MTDatacenterAuthKey *mediaTempAuthKey;
@property (nonatomic, strong, readonly) MTDatacenterAuthKey *persistentAuthKey;
- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes mainTempAuthKey:(MTDatacenterAuthKey *)mainTempAuthKey mediaTempAuthKey:(MTDatacenterAuthKey *)mediaTempAuthKey;
- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes;
- (int64_t)authSaltForMessageId:(int64_t)messageId;
- (MTDatacenterAuthInfo *)mergeSaltSet:(NSArray *)updatedSaltSet forTimestamp:(NSTimeInterval)timestamp;
- (MTDatacenterAuthInfo *)withUpdatedAuthKeyAttributes:(NSDictionary *)authKeyAttributes;
- (MTDatacenterAuthKey *)tempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type;
- (MTDatacenterAuthInfo *)withUpdatedTempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type key:(MTDatacenterAuthKey *)key;
@end

View File

@ -1,11 +1,13 @@
#import <Foundation/Foundation.h>
#import <MtProtoKit/MTDatacenterAuthInfo.h>
@class MTProto;
@class MTIncomingMessage;
@class MTMessageTransaction;
@class MTApiEnvironment;
@class MTSessionInfo;
@protocol MTMessageService <NSObject>
@ -15,10 +17,10 @@
- (void)mtProtoDidAddService:(MTProto *)mtProto;
- (void)mtProtoDidRemoveService:(MTProto *)mtProto;
- (void)mtProtoPublicKeysUpdated:(MTProto *)mtProto datacenterId:(NSInteger)datacenterId publicKeys:(NSArray<NSDictionary *> *)publicKeys;
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto;
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo;
- (void)mtProtoDidChangeSession:(MTProto *)mtProto;
- (void)mtProtoServerDidChangeSession:(MTProto *)mtProto firstValidMessageId:(int64_t)firstValidMessageId otherValidMessageIds:(NSArray *)otherValidMessageIds;
- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message;
- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector;
- (void)mtProto:(MTProto *)mtProto receivedQuickAck:(int32_t)quickAckId;
- (void)mtProto:(MTProto *)mtProto transactionsMayHaveFailed:(NSArray *)transactionIds;
- (void)mtProtoAllTransactionsMayHaveFailed:(MTProto *)mtProto;

View File

@ -71,4 +71,6 @@
- (void)_messageResendRequestFailed:(int64_t)messageId;
+ (NSData *)_manuallyEncryptedMessage:(NSData *)preparedData messageId:(int64_t)messageId authKey:(MTDatacenterAuthKey *)authKey;
@end

View File

@ -0,0 +1,13 @@
#import <Foundation/Foundation.h>
#import <MtProtoKit/MTProtoPersistenceInterface.h>
NS_ASSUME_NONNULL_BEGIN
@interface MTProtoEngine : NSObject
- (instancetype)initWithPersistenceInterface:(id<MTProtoPersistenceInterface>)persistenceInterface;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,12 @@
#import <Foundation/Foundation.h>
#import <MtProtoKit/MTProtoEngine.h>
NS_ASSUME_NONNULL_BEGIN
@interface MTProtoInstance : NSObject
- (instancetype)initWithEngine:(MTProtoEngine *)engine;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,14 @@
#import <Foundation/Foundation.h>
@class MTSignal;
NS_ASSUME_NONNULL_BEGIN
@protocol MTProtoPersistenceInterface <NSObject>
- (MTSignal *)get:(NSData *)key;
- (void)set:(NSData *)key value:(NSData *)value;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,156 @@
#import <MtProtoKit/MTBindKeyMessageService.h>
#import <MtProtoKit/MTTime.h>
#import <MtProtoKit/MTContext.h>
#import <MtProtoKit/MTProto.h>
#import <MtProtoKit/MTSerialization.h>
#import <MtProtoKit/MTOutgoingMessage.h>
#import <MtProtoKit/MTIncomingMessage.h>
#import <MtProtoKit/MTPreparedMessage.h>
#import <MtProtoKit/MTMessageTransaction.h>
#import <MtProtoKit/MTDatacenterSaltInfo.h>
#import <MtProtoKit/MTSessionInfo.h>
#import "MTInternalMessageParser.h"
#import "MTRpcResultMessage.h"
#import "MTBuffer.h"
@interface MTBindKeyMessageService () {
MTDatacenterAuthKey *_persistentKey;
MTDatacenterAuthKey *_ephemeralKey;
void (^_completion)(bool);
int64_t _currentMessageId;
id _currentTransactionId;
}
@end
@implementation MTBindKeyMessageService
- (instancetype)initWithPersistentKey:(MTDatacenterAuthKey *)persistentKey ephemeralKey:(MTDatacenterAuthKey *)ephemeralKey completion:(void (^)(bool))completion {
self = [super init];
if (self != nil) {
_persistentKey = persistentKey;
_ephemeralKey = ephemeralKey;
_completion = [completion copy];
}
return self;
}
- (void)mtProtoDidAddService:(MTProto *)mtProto
{
[mtProto requestTransportTransaction];
}
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo
{
if (_currentTransactionId != nil) {
return nil;
}
int64_t bindingMessageId = [sessionInfo generateClientMessageId:NULL];
int32_t bindingSeqNo = [sessionInfo takeSeqNo:true];
int32_t expiresAt = (int32_t)([mtProto.context globalTime] + mtProto.context.tempKeyExpiration);
int64_t randomId = 0;
arc4random_buf(&randomId, 8);
int64_t nonce = 0;
arc4random_buf(&nonce, 8);
MTBuffer *decryptedMessage = [[MTBuffer alloc] init];
//bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner;
[decryptedMessage appendInt32:(int32_t)0x75a3f765];
[decryptedMessage appendInt64:nonce];
[decryptedMessage appendInt64:_ephemeralKey.authKeyId];
[decryptedMessage appendInt64:_persistentKey.authKeyId];
[decryptedMessage appendInt64:sessionInfo.sessionId];
[decryptedMessage appendInt32:expiresAt];
NSData *encryptedMessage = [MTProto _manuallyEncryptedMessage:[decryptedMessage data] messageId:bindingMessageId authKey:_persistentKey];
MTBuffer *bindRequestData = [[MTBuffer alloc] init];
//auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool;
[bindRequestData appendInt32:(int32_t)0xcdd42a05];
[bindRequestData appendInt64:_persistentKey.authKeyId];
[bindRequestData appendInt64:nonce];
[bindRequestData appendInt32:expiresAt];
[bindRequestData appendTLBytes:encryptedMessage];
MTOutgoingMessage *outgoingMessage = [[MTOutgoingMessage alloc] initWithData:bindRequestData.data metadata:[NSString stringWithFormat:@"auth.bindTempAuthKey"] additionalDebugDescription:nil shortMetadata:@"auth.bindTempAuthKey" messageId:bindingMessageId messageSeqNo:bindingSeqNo];
return [[MTMessageTransaction alloc] initWithMessagePayload:@[outgoingMessage] prepared:nil failed:nil completion:^(NSDictionary *messageInternalIdToTransactionId, NSDictionary *messageInternalIdToPreparedMessage, __unused NSDictionary *messageInternalIdToQuickAckId) {
MTPreparedMessage *preparedMessage = messageInternalIdToPreparedMessage[outgoingMessage.internalId];
if (preparedMessage != nil && messageInternalIdToTransactionId[outgoingMessage.internalId] != nil) {
_currentMessageId = preparedMessage.messageId;
_currentTransactionId = messageInternalIdToTransactionId[outgoingMessage.internalId];
}
}];
return nil;
}
- (void)mtProto:(MTProto *)__unused mtProto messageDeliveryFailed:(int64_t)messageId {
if (messageId == _currentMessageId) {
_currentMessageId = 0;
_currentTransactionId = nil;
[mtProto requestTransportTransaction];
}
}
- (void)mtProto:(MTProto *)mtProto transactionsMayHaveFailed:(NSArray *)transactionIds {
if (_currentTransactionId != nil && [transactionIds containsObject:_currentTransactionId]) {
_currentTransactionId = nil;
[mtProto requestTransportTransaction];
}
}
- (void)mtProtoAllTransactionsMayHaveFailed:(MTProto *)mtProto {
if (_currentTransactionId != nil) {
_currentTransactionId = nil;
[mtProto requestTransportTransaction];
}
}
- (void)mtProtoDidChangeSession:(MTProto *)mtProto {
_currentMessageId = 0;
_currentTransactionId = nil;
[mtProto requestTransportTransaction];
}
- (void)mtProtoServerDidChangeSession:(MTProto *)mtProto firstValidMessageId:(int64_t)firstValidMessageId messageIdsInFirstValidContainer:(NSArray *)messageIdsInFirstValidContainer {
if (_currentMessageId != 0 && _currentMessageId < firstValidMessageId && ![messageIdsInFirstValidContainer containsObject:@(_currentMessageId)]) {
_currentMessageId = 0;
_currentTransactionId = nil;
[mtProto requestTransportTransaction];
}
}
- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector {
if ([message.body isKindOfClass:[MTRpcResultMessage class]]) {
MTRpcResultMessage *rpcResultMessage = message.body;
if (rpcResultMessage.requestMessageId == _currentMessageId) {
bool success = false;
if (rpcResultMessage.data.length >= 4) {
uint32_t signature = 0;
[rpcResultMessage.data getBytes:&signature range:NSMakeRange(0, 4)];
//boolTrue#997275b5 = Bool;
if (signature == 0x997275b5U) {
success = true;
}
}
_completion(success);
}
}
}
@end

View File

@ -114,7 +114,34 @@
@end
@interface MTContext () <MTDiscoverDatacenterAddressActionDelegate, MTDatacenterAuthActionDelegate, MTDatacenterTransferAuthActionDelegate>
typedef int64_t MTDatacenterAuthInfoMapKey;
typedef struct {
int32_t datacenterId;
MTDatacenterAuthInfoSelector selector;
} MTDatacenterAuthInfoMapKeyStruct;
static MTDatacenterAuthInfoMapKey authInfoMapKey(int32_t datacenterId, MTDatacenterAuthInfoSelector selector) {
int64_t result = (((int64_t)(selector)) << 32) | ((int64_t)(datacenterId));
return result;
}
static NSNumber *authInfoMapIntegerKey(int32_t datacenterId, MTDatacenterAuthInfoSelector selector) {
return [NSNumber numberWithLongLong:authInfoMapKey(datacenterId, selector)];
}
static MTDatacenterAuthInfoMapKeyStruct parseAuthInfoMapKey(int64_t key) {
MTDatacenterAuthInfoMapKeyStruct result;
result.datacenterId = (int32_t)(key & 0x7fffffff);
result.selector = (int32_t)(((key >> 32) & 0x7fffffff));
return result;
}
static MTDatacenterAuthInfoMapKeyStruct parseAuthInfoMapKeyInteger(NSNumber *key) {
return parseAuthInfoMapKey([key longLongValue]);
}
@interface MTContext () <MTDiscoverDatacenterAddressActionDelegate, MTDatacenterTransferAuthActionDelegate>
{
int64_t _uniqueId;
@ -127,7 +154,7 @@
NSMutableDictionary<NSNumber *, NSMutableDictionary<MTDatacenterAddress *, MTTransportSchemeStats *> *> *_transportSchemeStats;
MTTimer *_schemeStatsSyncTimer;
NSMutableDictionary *_datacenterAuthInfoById;
NSMutableDictionary<NSNumber *, MTDatacenterAuthInfo *> *_datacenterAuthInfoById;
NSMutableDictionary *_datacenterPublicKeysById;
@ -138,8 +165,7 @@
MTSignal *_discoverBackupAddressListSignal;
NSMutableDictionary *_discoverDatacenterAddressActions;
NSMutableDictionary *_datacenterAuthActions;
NSMutableDictionary *_datacenterTempAuthActions;
NSMutableDictionary<NSNumber *, MTDatacenterAuthAction *> *_datacenterAuthActions;
NSMutableDictionary *_datacenterTransferAuthActions;
NSMutableDictionary<NSNumber *, NSNumber *> *_datacenterCheckKeyRemovedActionTimestamps;
@ -201,6 +227,11 @@ static int32_t fixedTimeDifferenceValue = 0;
_apiEnvironment = apiEnvironment;
_isTestingEnvironment = isTestingEnvironment;
_useTempAuthKeys = useTempAuthKeys;
#if DEBUG
_tempKeyExpiration = 1 * 60 * 60;
#else
_tempKeyExpiration = 1 * 60 * 60;
#endif
_datacenterSeedAddressSetById = [[NSMutableDictionary alloc] init];
@ -218,7 +249,6 @@ static int32_t fixedTimeDifferenceValue = 0;
_discoverDatacenterAddressActions = [[NSMutableDictionary alloc] init];
_datacenterAuthActions = [[NSMutableDictionary alloc] init];
_datacenterTempAuthActions = [[NSMutableDictionary alloc] init];
_datacenterTransferAuthActions = [[NSMutableDictionary alloc] init];
_datacenterCheckKeyRemovedActionTimestamps = [[NSMutableDictionary alloc] init];
_datacenterCheckKeyRemovedActions = [[NSMutableDictionary alloc] init];
@ -258,9 +288,6 @@ static int32_t fixedTimeDifferenceValue = 0;
NSDictionary *datacenterAuthActions = _datacenterAuthActions;
_datacenterAuthActions = nil;
NSDictionary *datacenterTempAuthActions = _datacenterTempAuthActions;
_datacenterTempAuthActions = nil;
NSDictionary *discoverDatacenterAddressActions = _discoverDatacenterAddressActions;
_discoverDatacenterAddressActions = nil;
@ -284,17 +311,9 @@ static int32_t fixedTimeDifferenceValue = 0;
[action cancel];
}
for (NSNumber *nDatacenterId in datacenterAuthActions)
for (NSNumber *key in datacenterAuthActions)
{
MTDatacenterAuthAction *action = datacenterAuthActions[nDatacenterId];
action.delegate = nil;
[action cancel];
}
for (NSNumber *nDatacenterId in datacenterTempAuthActions)
{
MTDatacenterAuthAction *action = datacenterTempAuthActions[nDatacenterId];
action.delegate = nil;
MTDatacenterAuthAction *action = datacenterAuthActions[key];
[action cancel];
}
@ -360,6 +379,14 @@ static int32_t fixedTimeDifferenceValue = 0;
NSDictionary *datacenterAuthInfoById = [keychain objectForKey:@"datacenterAuthInfoById" group:@"persistent"];
if (datacenterAuthInfoById != nil) {
_datacenterAuthInfoById = [[NSMutableDictionary alloc] initWithDictionary:datacenterAuthInfoById];
#if DEBUG
/*NSArray<NSNumber *> *keys = [_datacenterAuthInfoById allKeys];
for (NSNumber *key in keys) {
if (parseAuthInfoMapKeyInteger(key).selector != MTDatacenterAuthInfoSelectorPersistent) {
[_datacenterAuthInfoById removeObjectForKey:key];
}
}*/
#endif
}
NSDictionary *datacenterPublicKeysById = [keychain objectForKey:@"datacenterPublicKeysById" group:@"ephemeral"];
@ -582,29 +609,46 @@ static int32_t fixedTimeDifferenceValue = 0;
}];
}
- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo
- (void)updateAuthInfoForDatacenterWithId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)authInfo selector:(MTDatacenterAuthInfoSelector)selector
{
[[MTContext contextQueue] dispatchOnQueue:^
{
if (datacenterId != 0)
{
if (MTLogEnabled()) {
MTLog(@"[MTContext#%x: auth info updated for %d to %@]", (int)self, datacenterId, authInfo);
}
NSNumber *infoKey = authInfoMapIntegerKey((int32_t)datacenterId, selector);
bool wasNil = _datacenterAuthInfoById[infoKey] == nil;
if (authInfo != nil) {
_datacenterAuthInfoById[@(datacenterId)] = authInfo;
_datacenterAuthInfoById[infoKey] = authInfo;
} else {
[_datacenterAuthInfoById removeObjectForKey:@(datacenterId)];
if (_datacenterAuthInfoById[infoKey] == nil) {
return;
}
[_datacenterAuthInfoById removeObjectForKey:infoKey];
}
if (MTLogEnabled()) {
MTLog(@"[MTContext#%x: auth info updated for %d selector %d to %@]", (int)self, datacenterId, selector, authInfo);
}
[_keychain setObject:_datacenterAuthInfoById forKey:@"datacenterAuthInfoById" group:@"persistent"];
NSArray *currentListeners = [[NSArray alloc] initWithArray:_changeListeners];
for (id<MTContextChangeListener> listener in currentListeners)
{
if ([listener respondsToSelector:@selector(contextDatacenterAuthInfoUpdated:datacenterId:authInfo:)])
[listener contextDatacenterAuthInfoUpdated:self datacenterId:datacenterId authInfo:authInfo];
if ([listener respondsToSelector:@selector(contextDatacenterAuthInfoUpdated:datacenterId:authInfo:selector:)])
[listener contextDatacenterAuthInfoUpdated:self datacenterId:datacenterId authInfo:authInfo selector:selector];
}
if (wasNil && authInfo != nil && selector == MTDatacenterAuthInfoSelectorPersistent) {
for (NSNumber *key in _datacenterAuthActions) {
MTDatacenterAuthInfoMapKeyStruct parsedKey = parseAuthInfoMapKeyInteger(key);
if (parsedKey.datacenterId == datacenterId && parsedKey.selector != MTDatacenterAuthInfoSelectorPersistent) {
[_datacenterAuthActions[key] execute:self datacenterId:datacenterId];
}
}
}
}
}];
@ -864,10 +908,11 @@ static int32_t fixedTimeDifferenceValue = 0;
return results;
}
- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId {
- (MTDatacenterAuthInfo *)authInfoForDatacenterWithId:(NSInteger)datacenterId selector:(MTDatacenterAuthInfoSelector)selector {
__block MTDatacenterAuthInfo *result = nil;
[[MTContext contextQueue] dispatchOnQueue:^{
result = _datacenterAuthInfoById[@(datacenterId)];
NSNumber *infoKey = authInfoMapIntegerKey((int32_t)datacenterId, selector);
result = _datacenterAuthInfoById[infoKey];
} synchronous:true];
return result;
@ -1251,48 +1296,44 @@ static int32_t fixedTimeDifferenceValue = 0;
}];
}
- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn
- (void)authInfoForDatacenterWithIdRequired:(NSInteger)datacenterId isCdn:(bool)isCdn selector:(MTDatacenterAuthInfoSelector)selector
{
[[MTContext contextQueue] dispatchOnQueue:^
{
if (_datacenterAuthActions[@(datacenterId)] == nil)
NSNumber *infoKey = authInfoMapIntegerKey((int32_t)datacenterId, selector);
if (_datacenterAuthActions[infoKey] == nil)
{
MTDatacenterAuthAction *authAction = [[MTDatacenterAuthAction alloc] initWithTempAuth:false tempAuthKeyType:MTDatacenterAuthTempKeyTypeMain bindKey:nil];
authAction.delegate = self;
_datacenterAuthActions[@(datacenterId)] = authAction;
[authAction execute:self datacenterId:datacenterId isCdn:isCdn];
}
}];
}
- (void)tempAuthKeyForDatacenterWithIdRequired:(NSInteger)datacenterId keyType:(MTDatacenterAuthTempKeyType)keyType {
[[MTContext contextQueue] dispatchOnQueue:^{
if (_datacenterTempAuthActions[@(datacenterId)] == nil) {
MTDatacenterAuthAction *authAction = [[MTDatacenterAuthAction alloc] initWithTempAuth:true tempAuthKeyType:keyType bindKey:nil];
authAction.delegate = self;
_datacenterTempAuthActions[@(datacenterId)] = authAction;
[authAction execute:self datacenterId:datacenterId isCdn:false];
}
}];
}
- (void)datacenterAuthActionCompleted:(MTDatacenterAuthAction *)action
{
[[MTContext contextQueue] dispatchOnQueue:^
{
if (action.tempAuth) {
for (NSNumber *nDatacenterId in _datacenterTempAuthActions) {
if (_datacenterTempAuthActions[nDatacenterId] == action) {
[_datacenterTempAuthActions removeObjectForKey:nDatacenterId];
__weak MTContext *weakSelf = self;
MTDatacenterAuthAction *authAction = [[MTDatacenterAuthAction alloc] initWithAuthKeyInfoSelector:selector isCdn:isCdn completion:^(MTDatacenterAuthAction *action, __unused bool success) {
[[MTContext contextQueue] dispatchOnQueue:^{
__strong MTContext *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
for (NSNumber *key in _datacenterAuthActions) {
if (_datacenterAuthActions[key] == action) {
[_datacenterAuthActions removeObjectForKey:key];
break;
}
}
}];
}];
_datacenterAuthActions[infoKey] = authAction;
switch (selector) {
case MTDatacenterAuthInfoSelectorEphemeralMain:
case MTDatacenterAuthInfoSelectorEphemeralMedia: {
if ([self authInfoForDatacenterWithId:datacenterId selector:MTDatacenterAuthInfoSelectorPersistent] == nil) {
[self authInfoForDatacenterWithIdRequired:datacenterId isCdn:false selector:MTDatacenterAuthInfoSelectorPersistent];
} else {
[authAction execute:self datacenterId:datacenterId];
}
break;
}
}
} else {
for (NSNumber *nDatacenterId in _datacenterAuthActions) {
if (_datacenterAuthActions[nDatacenterId] == action) {
[_datacenterAuthActions removeObjectForKey:nDatacenterId];
default: {
[authAction execute:self datacenterId:datacenterId];
break;
}
}
@ -1357,21 +1398,11 @@ static int32_t fixedTimeDifferenceValue = 0;
- (void)updatePeriodicTasks
{
[[MTContext contextQueue] dispatchOnQueue:^
{
int64_t saltsRequiredAtLeastUntilMessageId = (int64_t)(([self globalTime] + 24 * 60.0 * 60.0) * 4294967296);
[_datacenterAuthInfoById enumerateKeysAndObjectsUsingBlock:^(NSNumber *nDatacenterId, MTDatacenterAuthInfo *authInfo, __unused BOOL *stop)
{
if ([authInfo authSaltForMessageId:saltsRequiredAtLeastUntilMessageId == 0]) {
}
}];
}];
}
- (void)checkIfLoggedOut:(NSInteger)datacenterId {
[[MTContext contextQueue] dispatchOnQueue:^{
MTDatacenterAuthInfo *authInfo = [self authInfoForDatacenterWithId:datacenterId];
MTDatacenterAuthInfo *authInfo = [self authInfoForDatacenterWithId:datacenterId selector:MTDatacenterAuthInfoSelectorPersistent];
if (authInfo == nil || authInfo.authKey == nil) {
return;
}
@ -1382,7 +1413,7 @@ static int32_t fixedTimeDifferenceValue = 0;
_datacenterCheckKeyRemovedActionTimestamps[@(datacenterId)] = currentTimestamp;
[_datacenterCheckKeyRemovedActions[@(datacenterId)] dispose];
__weak MTContext *weakSelf = self;
_datacenterCheckKeyRemovedActions[@(datacenterId)] = [[MTDiscoverConnectionSignals checkIfAuthKeyRemovedWithContext:self datacenterId:datacenterId authKey:authInfo.authKey] startWithNext:^(NSNumber *isRemoved) {
_datacenterCheckKeyRemovedActions[@(datacenterId)] = [[MTDiscoverConnectionSignals checkIfAuthKeyRemovedWithContext:self datacenterId:datacenterId authKey:[[MTDatacenterAuthKey alloc] initWithAuthKey:authInfo.authKey authKeyId:authInfo.authKeyId notBound:false]] startWithNext:^(NSNumber* isRemoved) {
[[MTContext contextQueue] dispatchOnQueue:^{
__strong MTContext *strongSelf = weakSelf;
if (strongSelf == nil) {

View File

@ -12,13 +12,15 @@
#import <MtProtoKit/MTSignal.h>
#import <MtProtoKit/MTDatacenterAuthMessageService.h>
#import <MtProtoKit/MTRequestMessageService.h>
#import <MtProtoKit/MTBindKeyMessageService.h>
#import "MTBuffer.h"
@interface MTDatacenterAuthAction () <MTDatacenterAuthMessageServiceDelegate>
{
void (^_completion)(MTDatacenterAuthAction *, bool);
bool _isCdn;
MTDatacenterAuthTempKeyType _tempAuthKeyType;
MTDatacenterAuthKey *_bindKey;
MTDatacenterAuthInfoSelector _authKeyInfoSelector;
NSInteger _datacenterId;
__weak MTContext *_context;
@ -26,71 +28,61 @@
bool _awaitingAddresSetUpdate;
MTProto *_authMtProto;
MTProto *_bindMtProto;
MTMetaDisposable *_verifyDisposable;
}
@end
@implementation MTDatacenterAuthAction
- (instancetype)initWithTempAuth:(bool)tempAuth tempAuthKeyType:(MTDatacenterAuthTempKeyType)tempAuthKeyType bindKey:(MTDatacenterAuthKey *)bindKey {
- (instancetype)initWithAuthKeyInfoSelector:(MTDatacenterAuthInfoSelector)authKeyInfoSelector isCdn:(bool)isCdn completion:(void (^)(MTDatacenterAuthAction *, bool))completion {
self = [super init];
if (self != nil) {
_tempAuth = tempAuth;
_tempAuthKeyType = tempAuthKeyType;
_bindKey = bindKey;
_verifyDisposable = [[MTMetaDisposable alloc] init];
_authKeyInfoSelector = authKeyInfoSelector;
_isCdn = isCdn;
_completion = [completion copy];
}
return self;
}
- (void)dealloc
{
- (void)dealloc {
[self cleanup];
}
- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId isCdn:(bool)isCdn
{
- (void)execute:(MTContext *)context datacenterId:(NSInteger)datacenterId {
_datacenterId = datacenterId;
_context = context;
_isCdn = isCdn;
if (_datacenterId != 0 && context != nil)
{
bool alreadyCompleted = false;
MTDatacenterAuthInfo *currentAuthInfo = [context authInfoForDatacenterWithId:_datacenterId];
if (currentAuthInfo != nil && _bindKey == nil) {
if (_tempAuth) {
if ([currentAuthInfo tempAuthKeyWithType:_tempAuthKeyType] != nil) {
alreadyCompleted = true;
}
} else {
alreadyCompleted = true;
}
MTDatacenterAuthInfo *currentAuthInfo = [context authInfoForDatacenterWithId:_datacenterId selector:_authKeyInfoSelector];
if (currentAuthInfo != nil) {
alreadyCompleted = true;
}
if (alreadyCompleted) {
[self complete];
} else {
_authMtProto = [[MTProto alloc] initWithContext:context datacenterId:_datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0];
_authMtProto.cdn = isCdn;
_authMtProto.cdn = _isCdn;
_authMtProto.useUnauthorizedMode = true;
if (_tempAuth) {
switch (_tempAuthKeyType) {
case MTDatacenterAuthTempKeyTypeMain:
_authMtProto.media = false;
break;
case MTDatacenterAuthTempKeyTypeMedia:
_authMtProto.media = true;
_authMtProto.enforceMedia = true;
break;
default:
break;
}
bool tempAuth = false;
switch (_authKeyInfoSelector) {
case MTDatacenterAuthInfoSelectorEphemeralMain:
tempAuth = true;
_authMtProto.media = false;
break;
case MTDatacenterAuthInfoSelectorEphemeralMedia:
tempAuth = true;
_authMtProto.media = true;
_authMtProto.enforceMedia = true;
break;
default:
break;
}
MTDatacenterAuthMessageService *authService = [[MTDatacenterAuthMessageService alloc] initWithContext:context tempAuth:_tempAuth];
MTDatacenterAuthMessageService *authService = [[MTDatacenterAuthMessageService alloc] initWithContext:context tempAuth:tempAuth];
authService.delegate = self;
[_authMtProto addMessageService:authService];
}
@ -104,45 +96,71 @@
}
- (void)completeWithAuthKey:(MTDatacenterAuthKey *)authKey timestamp:(int64_t)timestamp {
if (_tempAuth) {
MTContext *mainContext = _context;
if (mainContext != nil) {
if (_bindKey != nil) {
_bindMtProto = [[MTProto alloc] initWithContext:mainContext datacenterId:_datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0];
_bindMtProto.cdn = false;
_bindMtProto.useUnauthorizedMode = false;
_bindMtProto.useTempAuthKeys = true;
__weak MTDatacenterAuthAction *weakSelf = self;
_bindMtProto.tempAuthKeyBindingResultUpdated = ^(bool success) {
__strong MTDatacenterAuthAction *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
if (MTLogEnabled()) {
MTLog(@"[MTDatacenterAuthAction#%p@%p: completeWithAuthKey %lld selector %d]", self, _context, authKey.authKeyId, _authKeyInfoSelector);
}
switch (_authKeyInfoSelector) {
case MTDatacenterAuthInfoSelectorPersistent: {
MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:authKey.authKey authKeyId:authKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:timestamp lastValidMessageId:timestamp + (29.0 * 60.0) * 4294967296]] authKeyAttributes:nil];
MTContext *context = _context;
[context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo selector:_authKeyInfoSelector];
[self complete];
}
break;
case MTDatacenterAuthInfoSelectorEphemeralMain:
case MTDatacenterAuthInfoSelectorEphemeralMedia: {
MTContext *mainContext = _context;
if (mainContext != nil) {
MTDatacenterAuthInfo *persistentAuthInfo = [mainContext authInfoForDatacenterWithId:_datacenterId selector:MTDatacenterAuthInfoSelectorPersistent];
if (persistentAuthInfo != nil) {
_bindMtProto = [[MTProto alloc] initWithContext:mainContext datacenterId:_datacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0];
_bindMtProto.cdn = false;
_bindMtProto.useUnauthorizedMode = false;
_bindMtProto.useTempAuthKeys = true;
_bindMtProto.useExplicitAuthKey = authKey;
switch (_authKeyInfoSelector) {
case MTDatacenterAuthInfoSelectorEphemeralMain:
_bindMtProto.media = false;
break;
case MTDatacenterAuthInfoSelectorEphemeralMedia:
_bindMtProto.media = true;
_bindMtProto.enforceMedia = true;
break;
default:
break;
}
[strongSelf->_bindMtProto stop];
if (strongSelf->_completedWithResult) {
strongSelf->_completedWithResult(success);
}
};
_bindMtProto.useExplicitAuthKey = authKey;
[_bindMtProto resume];
} else {
MTContext *context = _context;
[context performBatchUpdates:^{
MTDatacenterAuthInfo *authInfo = [context authInfoForDatacenterWithId:_datacenterId];
if (authInfo != nil) {
authInfo = [authInfo withUpdatedTempAuthKeyWithType:_tempAuthKeyType key:authKey];
[context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo];
}
}];
[self complete];
__weak MTDatacenterAuthAction *weakSelf = self;
[_bindMtProto addMessageService:[[MTBindKeyMessageService alloc] initWithPersistentKey:[[MTDatacenterAuthKey alloc] initWithAuthKey:persistentAuthInfo.authKey authKeyId:persistentAuthInfo.authKeyId notBound:false] ephemeralKey:authKey completion:^(bool success) {
__strong MTDatacenterAuthAction *strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
[strongSelf->_bindMtProto stop];
if (success) {
MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:authKey.authKey authKeyId:authKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:timestamp lastValidMessageId:timestamp + (29.0 * 60.0) * 4294967296]] authKeyAttributes:nil];
[strongSelf->_context updateAuthInfoForDatacenterWithId:strongSelf->_datacenterId authInfo:authInfo selector:strongSelf->_authKeyInfoSelector];
[strongSelf complete];
} else {
[strongSelf fail];
}
}]];
[_bindMtProto resume];
}
}
}
} else {
MTDatacenterAuthInfo *authInfo = [[MTDatacenterAuthInfo alloc] initWithAuthKey:authKey.authKey authKeyId:authKey.authKeyId saltSet:@[[[MTDatacenterSaltInfo alloc] initWithSalt:0 firstValidMessageId:timestamp lastValidMessageId:timestamp + (29.0 * 60.0) * 4294967296]] authKeyAttributes:nil mainTempAuthKey:nil mediaTempAuthKey:nil];
MTContext *context = _context;
[context updateAuthInfoForDatacenterWithId:_datacenterId authInfo:authInfo];
[self complete];
break;
default:
assert(false);
break;
}
}
@ -153,7 +171,10 @@
[authMtProto stop];
[_verifyDisposable dispose];
MTProto *bindMtProto = _bindMtProto;
_bindMtProto = nil;
[bindMtProto stop];
}
- (void)cancel
@ -162,18 +183,17 @@
[self fail];
}
- (void)complete
{
id<MTDatacenterAuthActionDelegate> delegate = _delegate;
if ([delegate respondsToSelector:@selector(datacenterAuthActionCompleted:)])
[delegate datacenterAuthActionCompleted:self];
- (void)complete {
if (_completion) {
_completion(self, true);
}
}
- (void)fail
{
id<MTDatacenterAuthActionDelegate> delegate = _delegate;
if ([delegate respondsToSelector:@selector(datacenterAuthActionCompleted:)])
[delegate datacenterAuthActionCompleted:self];
if (_completion) {
_completion(self, false);
}
}
@end

View File

@ -27,7 +27,7 @@
@implementation MTDatacenterAuthInfo
- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes mainTempAuthKey:(MTDatacenterAuthKey *)mainTempAuthKey mediaTempAuthKey:(MTDatacenterAuthKey *)mediaTempAuthKey
- (instancetype)initWithAuthKey:(NSData *)authKey authKeyId:(int64_t)authKeyId saltSet:(NSArray *)saltSet authKeyAttributes:(NSDictionary *)authKeyAttributes
{
self = [super init];
if (self != nil)
@ -36,8 +36,6 @@
_authKeyId = authKeyId;
_saltSet = saltSet;
_authKeyAttributes = authKeyAttributes;
_mainTempAuthKey = mainTempAuthKey;
_mediaTempAuthKey = mediaTempAuthKey;
}
return self;
}
@ -51,8 +49,6 @@
_authKeyId = [aDecoder decodeInt64ForKey:@"authKeyId"];
_saltSet = [aDecoder decodeObjectForKey:@"saltSet"];
_authKeyAttributes = [aDecoder decodeObjectForKey:@"authKeyAttributes"];
_mainTempAuthKey = [aDecoder decodeObjectForKey:@"tempAuthKey"];
_mediaTempAuthKey = [aDecoder decodeObjectForKey:@"mediaTempAuthKey"];
}
return self;
}
@ -63,8 +59,6 @@
[aCoder encodeInt64:_authKeyId forKey:@"authKeyId"];
[aCoder encodeObject:_saltSet forKey:@"saltSet"];
[aCoder encodeObject:_authKeyAttributes forKey:@"authKeyAttributes"];
[aCoder encodeObject:_mainTempAuthKey forKey:@"tempAuthKey"];
[aCoder encodeObject:_mediaTempAuthKey forKey:@"mediaTempAuthKey"];
}
- (int64_t)authSaltForMessageId:(int64_t)messageId
@ -113,35 +107,11 @@
}
}
return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:mergedSaltSet authKeyAttributes:_authKeyAttributes mainTempAuthKey:_mainTempAuthKey mediaTempAuthKey:_mediaTempAuthKey];
return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:mergedSaltSet authKeyAttributes:_authKeyAttributes];
}
- (MTDatacenterAuthInfo *)withUpdatedAuthKeyAttributes:(NSDictionary *)authKeyAttributes {
return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:authKeyAttributes mainTempAuthKey:_mainTempAuthKey mediaTempAuthKey:_mediaTempAuthKey];
}
- (MTDatacenterAuthKey *)tempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type {
switch (type) {
case MTDatacenterAuthTempKeyTypeMain:
return _mainTempAuthKey;
case MTDatacenterAuthTempKeyTypeMedia:
return _mediaTempAuthKey;
default:
NSAssert(false, @"unknown MTDatacenterAuthTempKeyType");
return nil;
}
}
- (MTDatacenterAuthInfo *)withUpdatedTempAuthKeyWithType:(MTDatacenterAuthTempKeyType)type key:(MTDatacenterAuthKey *)key {
switch (type) {
case MTDatacenterAuthTempKeyTypeMain:
return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:_authKeyAttributes mainTempAuthKey:key mediaTempAuthKey:_mediaTempAuthKey];
case MTDatacenterAuthTempKeyTypeMedia:
return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:_authKeyAttributes mainTempAuthKey:_mainTempAuthKey mediaTempAuthKey:key];
default:
NSAssert(false, @"unknown MTDatacenterAuthTempKeyType");
return self;
}
return [[MTDatacenterAuthInfo alloc] initWithAuthKey:_authKey authKeyId:_authKeyId saltSet:_saltSet authKeyAttributes:authKeyAttributes];
}
- (MTDatacenterAuthKey *)persistentAuthKey {

View File

@ -222,7 +222,7 @@ typedef enum {
}
}
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo
{
if (_currentStageTransactionId == nil)
{
@ -308,7 +308,7 @@ typedef enum {
return nil;
}
- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message
- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
{
if (_stage == MTDatacenterAuthStagePQ && [message.body isKindOfClass:[MTResPqMessage class]])
{
@ -389,7 +389,7 @@ typedef enum {
[innerDataBuffer appendBytes:_nonce.bytes length:_nonce.length];
[innerDataBuffer appendBytes:_serverNonce.bytes length:_serverNonce.length];
[innerDataBuffer appendBytes:_newNonce.bytes length:_newNonce.length];
[innerDataBuffer appendInt32:60 * 60 * 32];
[innerDataBuffer appendInt32:mtProto.context.tempKeyExpiration];
NSData *innerDataBytes = innerDataBuffer.data;

View File

@ -249,12 +249,11 @@
MTMetaDisposable *disposable = [[MTMetaDisposable alloc] init];
[[MTContext contextQueue] dispatchOnQueue:^{
MTDatacenterAuthAction *action = [[MTDatacenterAuthAction alloc] initWithTempAuth:true tempAuthKeyType:MTDatacenterAuthTempKeyTypeMain bindKey:authKey];
action.completedWithResult = ^(bool success) {
MTDatacenterAuthAction *action = [[MTDatacenterAuthAction alloc] initWithAuthKeyInfoSelector:MTDatacenterAuthInfoSelectorEphemeralMain isCdn:false completion:^(__unused MTDatacenterAuthAction *action, bool success) {
[subscriber putNext:@(!success)];
[subscriber putCompletion];
};
[action execute:context datacenterId:datacenterId isCdn:false];
}];
[action execute:context datacenterId:datacenterId];
[disposable setDisposable:[[MTBlockDisposable alloc] initWithBlock:^{
[action cancel];

View File

@ -88,7 +88,7 @@
[self fail];
else
{
if ([context authInfoForDatacenterWithId:_targetDatacenterId] != nil)
if ([context authInfoForDatacenterWithId:_targetDatacenterId selector:MTDatacenterAuthInfoSelectorPersistent] != nil)
{
_mtProto = [[MTProto alloc] initWithContext:context datacenterId:_targetDatacenterId usageCalculationInfo:nil requiredAuthToken:nil authTokenMasterDatacenterId:0];
_mtProto.useTempAuthKeys = useTempAuthKeys;
@ -118,11 +118,11 @@
[_requestService addRequest:request];
}
else
[context authInfoForDatacenterWithIdRequired:_targetDatacenterId isCdn:false];
[context authInfoForDatacenterWithIdRequired:_targetDatacenterId isCdn:false selector:MTDatacenterAuthInfoSelectorPersistent];
}
}
- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)__unused authInfo
- (void)contextDatacenterAuthInfoUpdated:(MTContext *)context datacenterId:(NSInteger)datacenterId authInfo:(MTDatacenterAuthInfo *)__unused authInfo selector:(MTDatacenterAuthInfoSelector)selector
{
if (_context != context || !_awaitingAddresSetUpdate)
return;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
#import <MtProtoKit/MTProtoEngine.h>
#import "Utils/MTQueueLocalObject.h"
#import <MtProtoKit/MTQueue.h>
NS_ASSUME_NONNULL_BEGIN
@interface MTProtoEngineImpl : NSObject {
MTQueue *_queue;
id<MTProtoPersistenceInterface> _persistenceInterface;
}
@end
@implementation MTProtoEngineImpl
- (instancetype)initWithQueue:(MTQueue *)queue persistenceInterface:(id<MTProtoPersistenceInterface>)persistenceInterface {
self = [super init];
if (self != nil) {
_queue = queue;
_persistenceInterface = persistenceInterface;
}
return self;
}
@end
@interface MTProtoEngine () {
MTQueue *_queue;
MTQueueLocalObject<MTProtoEngineImpl *> *_impl;
}
@end
@implementation MTProtoEngine
- (instancetype)initWithPersistenceInterface:(id<MTProtoPersistenceInterface>)persistenceInterface {
self = [super init];
if (self != nil) {
_queue = [[MTQueue alloc] init];
__auto_type queue = _queue;
_impl = [[MTQueueLocalObject<MTProtoEngineImpl
*> alloc] initWithQueue:queue generator:^MTProtoEngineImpl *{
return [[MTProtoEngineImpl alloc] initWithQueue:queue persistenceInterface:persistenceInterface];
}];
}
return self;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,48 @@
#import <MtProtoKit/MTProtoInstance.h>
#import <MtProtoKit/MTQueue.h>
#import "Utils/MTQueueLocalObject.h"
@interface MTProtoInstanceImpl : NSObject {
MTQueue *_queue;
MTProtoEngine *_engine;
}
@end
@implementation MTProtoInstanceImpl
- (instancetype)initWithQueue:(MTQueue *)queue engine:(MTProtoEngine *)engine {
self = [super init];
if (self != nil) {
_queue = queue;
_engine = engine;
}
return self;
}
@end
@interface MTProtoInstance () {
MTQueue *_queue;
MTQueueLocalObject<MTProtoInstanceImpl *> *_impl;
}
@end
@implementation MTProtoInstance
- (instancetype)initWithEngine:(MTProtoEngine *)engine {
self = [super init];
if (self != nil) {
_queue = [[MTQueue alloc] init];
__auto_type queue = _queue;
_impl = [[MTQueueLocalObject<MTProtoInstanceImpl
*> alloc] initWithQueue:queue generator:^MTProtoInstanceImpl *{
return [[MTProtoInstanceImpl alloc] initWithQueue:queue engine:engine];
}];
}
return self;
}
@end

View File

@ -391,12 +391,12 @@
return currentData;
}
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo
{
NSMutableArray *messages = nil;
NSMutableDictionary *requestInternalIdToMessageInternalId = nil;
bool requestsWillInitializeApi = _apiEnvironment != nil && ![_apiEnvironment.apiInitializationHash isEqualToString:[_context authInfoForDatacenterWithId:mtProto.datacenterId].authKeyAttributes[@"apiInitializationHash"]];
bool requestsWillInitializeApi = _apiEnvironment != nil && ![_apiEnvironment.apiInitializationHash isEqualToString:[_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector].authKeyAttributes[@"apiInitializationHash"]];
CFAbsoluteTime currentTime = MTAbsoluteSystemTime();
@ -561,7 +561,7 @@
return nil;
}
- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)message
- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
{
if ([message.body isKindOfClass:[MTRpcResultMessage class]])
{
@ -610,13 +610,13 @@
{
rpcError = [[MTRpcError alloc] initWithErrorCode:500 errorDescription:@"TL_PARSING_ERROR"];
[_context performBatchUpdates:^{
MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId];
MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector];
NSMutableDictionary *authKeyAttributes = [[NSMutableDictionary alloc] initWithDictionary:authInfo.authKeyAttributes];
authKeyAttributes[@"apiInitializationHash"] = @"";
authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes];
[_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo];
[_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector];
}];
}
}
@ -636,7 +636,7 @@
if (rpcResult != nil && request.requestContext.willInitializeApi)
{
MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId];
MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector];
if (![_apiEnvironment.apiInitializationHash isEqualToString:authInfo.authKeyAttributes[@"apiInitializationHash"]])
{
@ -644,7 +644,7 @@
authKeyAttributes[@"apiInitializationHash"] = _apiEnvironment.apiInitializationHash;
authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes];
[_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo];
[_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector];
}
}
@ -726,13 +726,13 @@
{
[_context performBatchUpdates:^
{
MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId];
MTDatacenterAuthInfo *authInfo = [_context authInfoForDatacenterWithId:mtProto.datacenterId selector:authInfoSelector];
NSMutableDictionary *authKeyAttributes = [[NSMutableDictionary alloc] initWithDictionary:authInfo.authKeyAttributes];
[authKeyAttributes removeObjectForKey:@"apiInitializationHash"];
authInfo = [authInfo withUpdatedAuthKeyAttributes:authKeyAttributes];
[_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo];
[_context updateAuthInfoForDatacenterWithId:mtProto.datacenterId authInfo:authInfo selector:authInfoSelector];
}];
} else if (rpcError.errorCode == 406) {
if (_didReceiveSoftAuthResetError) {

View File

@ -41,7 +41,7 @@
[mtProto requestTransportTransaction];
}
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo
{
if (_currentRequestMessageId == 0 || _currentRequestTransactionId == nil)
{
@ -121,7 +121,7 @@
}
}
- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message
- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
{
if (message.messageId == _messageId)
{

View File

@ -747,7 +747,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0;
}];
}
- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)incomingMessage
- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)incomingMessage authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
{
if ([incomingMessage.body isKindOfClass:[MTPongMessage class]])
{

View File

@ -45,7 +45,7 @@
[mtProto requestTransportTransaction];
}
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo
{
if (_currentTransactionId == nil)
{
@ -127,7 +127,7 @@
}
}
- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message
- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
{
if ([message.body isKindOfClass:[MTFutureSaltsMessage class]] && ((MTFutureSaltsMessage *)message.body).requestMessageId == _currentMessageId)
{

View File

@ -0,0 +1,14 @@
#import <Foundation/Foundation.h>
@class MTQueue;
NS_ASSUME_NONNULL_BEGIN
@interface MTQueueLocalObject<__covariant T> : NSObject
- (instancetype)initWithQueue:(MTQueue *)queue generator:(T(^)())generator;
- (void)with:(void (^)(T))f;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,56 @@
#import "MTQueueLocalObject.h"
#import <MtProtoKit/MTQueue.h>
NS_ASSUME_NONNULL_BEGIN
@interface MTQueueLocalObjectHolder : NSObject
@property (nonatomic, assign) CFTypeRef impl;
@end
@implementation MTQueueLocalObjectHolder
@end
@interface MTQueueLocalObject () {
MTQueue *_queue;
MTQueueLocalObjectHolder *_holder;
}
@end
@implementation MTQueueLocalObject
- (instancetype)initWithQueue:(MTQueue *)queue generator:(id(^)())generator {
self = [super init];
if (self != nil) {
_queue = queue;
_holder = [[MTQueueLocalObjectHolder alloc] init];
__auto_type holder = _holder;
[queue dispatchOnQueue:^{
id value = generator();
holder.impl = CFBridgingRetain(value);
} synchronous:false];
}
return self;
}
- (void)dealloc {
__auto_type holder = _holder;
[_queue dispatchOnQueue:^{
CFBridgingRelease(holder.impl);
} synchronous:false];
}
- (void)with:(void (^)(id))f {
__auto_type holder = _holder;
[_queue dispatchOnQueue:^{
id value = (__bridge id)holder.impl;
f(value);
} synchronous:false];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -487,6 +487,8 @@ private func stringForRight(strings: PresentationStrings, right: TelegramChatAdm
return strings.Channel_EditAdmin_PermissionPinMessages
} else if right.contains(.canAddAdmins) {
return strings.Channel_EditAdmin_PermissionAddAdmins
} else if right.contains(.canBeAnonymous) {
return strings.Channel_AdminLog_CanBeAnonymous
} else {
return ""
}
@ -509,6 +511,8 @@ private func rightDependencies(_ right: TelegramChatAdminRightsFlags) -> [Telegr
return []
} else if right.contains(.canAddAdmins) {
return []
} else if right.contains(.canBeAnonymous) {
return []
} else {
return []
}
@ -607,11 +611,43 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
.canBanUsers,
.canInviteUsers,
.canPinMessages,
.canBeAnonymous,
.canAddAdmins
]
}
if isCreator {
if isGroup {
entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader))
let accountUserRightsFlags: TelegramChatAdminRightsFlags
if channel.flags.contains(.isCreator) {
accountUserRightsFlags = maskRightsFlags
} else if let adminRights = channel.adminRights {
accountUserRightsFlags = maskRightsFlags.intersection(adminRights.flags)
} else {
accountUserRightsFlags = []
}
let currentRightsFlags: TelegramChatAdminRightsFlags
if let updatedFlags = state.updatedFlags {
currentRightsFlags = updatedFlags
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights {
currentRightsFlags = adminRights.rights.flags
} else if let initialParticipant = initialParticipant, case let .creator(_, maybeAdminRights, _) = initialParticipant, let adminRights = maybeAdminRights {
currentRightsFlags = adminRights.rights.flags
} else {
currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous)
}
var index = 0
for right in rightsOrder {
if accountUserRightsFlags.contains(right) {
entries.append(.rightItem(presentationData.theme, index, stringForRight(strings: presentationData.strings, right: right, isGroup: isGroup, defaultBannedRights: channel.defaultBannedRights), right, currentRightsFlags, currentRightsFlags.contains(right), right == .canBeAnonymous))
index += 1
}
}
}
} else {
entries.append(.rightsTitle(presentationData.theme, presentationData.strings.Channel_EditAdmin_PermissionsHeader))
@ -631,7 +667,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
} else if let initialParticipant = initialParticipant, case let .member(_, _, maybeAdminRights, _, _) = initialParticipant, let adminRights = maybeAdminRights {
currentRightsFlags = adminRights.rights.flags
} else {
currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins)
currentRightsFlags = accountUserRightsFlags.subtracting(.canAddAdmins).subtracting(.canBeAnonymous)
}
var index = 0
@ -731,6 +767,7 @@ private func channelAdminControllerEntries(presentationData: PresentationData, s
.canBanUsers,
.canInviteUsers,
.canPinMessages,
.canBeAnonymous,
.canAddAdmins
]

View File

@ -696,7 +696,7 @@ public func channelAdminsController(context: AccountContext, peerId initialPeerI
if let peer = peerView.peers[participant.peerId] {
switch participant {
case .creator:
result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer))
result.append(RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer))
case .admin:
var peers: [PeerId: Peer] = [:]
peers[creator.id] = creator

View File

@ -166,7 +166,7 @@ private func channelDiscussionGroupSetupControllerEntries(presentationData: Pres
var entries: [ChannelDiscussionGroupSetupControllerEntry] = []
if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId {
if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId {
if let group = view.peers[linkedDiscussionPeerId] {
if case .group = peer.info {
entries.append(.header(presentationData.theme, presentationData.strings, group.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), true, presentationData.strings.Channel_DiscussionGroup_HeaderLabel))
@ -299,7 +299,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
return
}
if groupId == cachedData.linkedDiscussionPeerId {
if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, maybeLinkedDiscussionPeerId == groupId {
navigateToGroupImpl?(groupId)
return
}
@ -483,7 +483,7 @@ public func channelDiscussionGroupSetupController(context: AccountContext, peerI
let applyPeerId: PeerId
if case .broadcast = peer.info {
applyPeerId = peerId
} else if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId {
} else if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId {
applyPeerId = linkedDiscussionPeerId
} else {
return

View File

@ -497,7 +497,7 @@ private func channelInfoEntries(account: Account, presentationData: Presentation
let discussionGroupTitle: String
if let cachedData = view.cachedData as? CachedChannelData {
if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
if let addressName = peer.addressName, !addressName.isEmpty {
discussionGroupTitle = "@\(addressName)"
} else {
@ -532,7 +532,7 @@ private func channelInfoEntries(account: Account, presentationData: Presentation
if let _ = state.editingState, let adminRights = peer.adminRights, !adminRights.isEmpty {
let discussionGroupTitle: String?
if let cachedData = view.cachedData as? CachedChannelData {
if let linkedDiscussionPeerId = cachedData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
if case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
if let addressName = peer.addressName, !addressName.isEmpty {
discussionGroupTitle = "@\(addressName)"
} else {
@ -951,7 +951,7 @@ public func channelInfoController(context: AccountContext, peerId: PeerId) -> Vi
if canEditChannel {
hasSomethingToEdit = true
} else if let adminRights = peer.adminRights, !adminRights.isEmpty {
if let cachedData = view.cachedData as? CachedChannelData, let _ = cachedData.linkedDiscussionPeerId {
if let cachedData = view.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let _ = maybeLinkedDiscussionPeerId {
hasSomethingToEdit = true
}
}

View File

@ -867,7 +867,7 @@ public final class ChannelMembersSearchContainerNode: SearchDisplayControllerCon
let renderedParticipant: RenderedChannelParticipant
switch participant {
case .creator:
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer)
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer)
case .admin:
var peers: [PeerId: Peer] = [:]
if let creator = creatorPeer {

View File

@ -216,7 +216,7 @@ class ChannelMembersSearchControllerNode: ASDisplayNode {
let renderedParticipant: RenderedChannelParticipant
switch participant {
case .creator:
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, rank: nil), peer: peer)
renderedParticipant = RenderedChannelParticipant(participant: .creator(id: peer.id, adminInfo: nil, rank: nil), peer: peer)
case .admin:
var peers: [PeerId: Peer] = [:]
peers[creator.id] = creator

View File

@ -896,7 +896,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
} else {
if cachedChannelData.flags.contains(.canChangeUsername) {
entries.append(GroupInfoEntry.groupTypeSetup(presentationData.theme, presentationData.strings.GroupInfo_GroupType, isPublic ? presentationData.strings.Channel_Setup_TypePublic : presentationData.strings.Channel_Setup_TypePrivate))
if let linkedDiscussionPeerId = cachedChannelData.linkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
if case let .known(maybeLinkedDiscussionPeerId) = cachedChannelData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = view.peers[linkedDiscussionPeerId] {
let peerTitle: String
if let addressName = peer.addressName, !addressName.isEmpty {
peerTitle = "@\(addressName)"
@ -1069,7 +1069,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
let participant: ChannelParticipant
switch sortedParticipants[i] {
case .creator:
participant = .creator(id: sortedParticipants[i].peerId, rank: nil)
participant = .creator(id: sortedParticipants[i].peerId, adminInfo: nil, rank: nil)
memberStatus = .owner(rank: nil)
case .admin:
participant = .member(id: sortedParticipants[i].peerId, invitedAt: 0, adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(flags: .groupSpecific), promotedBy: account.peerId, canBeEditedByAccountPeer: true), banInfo: nil, rank: nil)
@ -1201,7 +1201,7 @@ private func groupInfoEntries(account: Account, presentationData: PresentationDa
let participant = participants[i]
let memberStatus: GroupInfoMemberStatus
switch participant.participant {
case let .creator(_, rank):
case let .creator(_, _, rank):
memberStatus = .owner(rank: rank)
case let .member(_, _, adminInfo, _, rank):
if adminInfo != nil {

View File

@ -10,6 +10,7 @@ public enum AdditionalMessageHistoryViewData {
case preferencesEntry(ValueBoxKey)
case peer(PeerId)
case peerIsContact(PeerId)
case message(MessageId)
}
public enum AdditionalMessageHistoryViewDataEntry {
@ -22,4 +23,5 @@ public enum AdditionalMessageHistoryViewDataEntry {
case preferencesEntry(ValueBoxKey, PreferencesEntry?)
case peerIsContact(PeerId, Bool)
case peer(PeerId, Peer?)
case message(MessageId, Message?)
}

Some files were not shown because too many files have changed in this diff Show More