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

This commit is contained in:
Ilya Laktyushin 2020-10-23 19:11:42 +04:00
commit 0457354220
20 changed files with 3291 additions and 3136 deletions

View File

@ -65,22 +65,9 @@
"PUSH_CHANNEL_MESSAGE_GEOLIVE" = "%1$@|posted a live location";
"PUSH_CHANNEL_MESSAGE_GIF" = "%1$@|posted a GIF";
"PUSH_PINNED_TEXT" = "%1$@ pinned \"%2$@\" ";
"PUSH_PINNED_NOTEXT" = "%1$@ pinned a message";
"PUSH_PINNED_PHOTO" = "%1$@ pinned a photo";
"PUSH_PINNED_VIDEO" = "%1$@ pinned a video";
"PUSH_PINNED_ROUND" = "%1$@ pinned a video message";
"PUSH_PINNED_DOC" = "%1$@ pinned a file";
"PUSH_PINNED_STICKER" = "%1$@ pinned a %2$@sticker";
"PUSH_PINNED_AUDIO" = "%1$@ pinned a voice message";
"PUSH_PINNED_GEO" = "%1$@ pinned a map";
"PUSH_PINNED_GEOLIVE" = "%1$@ pinned a live location";
"PUSH_PINNED_GIF" = "%1$@ pinned a GIF";
"PUSH_MESSAGE_GAME" = "%1$@|invited you to play %2$@";
"PUSH_CHANNEL_MESSAGE_GAME" = "%1$@|invited you to play %2$@";
"PUSH_CHAT_MESSAGE_GAME" = "%2$@|%1$@ invited the group to play %3$@";
"PUSH_PINNED_GAME" = "%1$@ pinned a game";
"PUSH_MESSAGE_TEXT" = "%1$@|%2$@";
"PUSH_MESSAGE_NOTEXT" = "%1$@|sent you a message";
@ -1964,6 +1951,7 @@
"Message.PinnedStickerMessage" = "pinned sticker";
"Message.PinnedLocationMessage" = "pinned location";
"Message.PinnedContactMessage" = "pinned contact";
"Message.PinnedGenericMessage" = "%@ pinned a message";
"Notification.PinnedMessage" = "pinned message";
@ -5882,3 +5870,6 @@ Any member of this group will be able to see messages in the channel.";
"Chat.PinnedListPreview.ShowAllMessages" = "Show All Messages";
"Chat.PinnedListPreview.UnpinAllMessages" = "Unpin All Messages";
"Chat.PinnedListPreview.HidePinnedMessages" = "Hide Pinned Messages";
"Conversation.PinMessagesForMe" = "Pin for me";
"Conversation.PinMessagesFor" = "Pin for me and %@";

View File

@ -149,6 +149,11 @@ public final class AnimatedNavigationStripeNode: ASDisplayNode {
}
if self.currentConfiguration != configuration {
var isCycledJump = false
if let currentConfiguration = self.currentConfiguration, currentConfiguration.count == configuration.count, currentConfiguration.index == 0, currentConfiguration.count > 4, configuration.index == configuration.count - 1 {
isCycledJump = true
}
self.currentConfiguration = configuration
let defaultVerticalInset: CGFloat = 7.0
@ -276,6 +281,19 @@ public final class AnimatedNavigationStripeNode: ASDisplayNode {
}
transition.updateFrame(node: self.foregroundLineNode, frame: CGRect(origin: CGPoint(x: 0.0, y: itemScreenOffset), size: CGSize(width: 2.0, height: segmentHeight)), beginWithCurrentState: true)
if transition.isAnimated && isCycledJump {
let duration: Double = 0.18
let maxOffset: CGFloat = -8.0
let offsetAnimation0 = self.layer.makeAnimation(from: 0.0 as NSNumber, to: maxOffset as NSNumber, keyPath: "bounds.origin.y", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: duration / 2.0, removeOnCompletion: false, additive: true, completion: { [weak self] _ in
guard let strongSelf = self else {
return
}
let offsetAnimation1 = strongSelf.layer.makeAnimation(from: maxOffset as NSNumber, to: 0.0 as NSNumber, keyPath: "bounds.origin.y", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: duration / 2.0, additive: true)
strongSelf.layer.add(offsetAnimation1, forKey: "cycleShake")
})
self.layer.add(offsetAnimation0, forKey: "cycleShake")
}
}
}
}

View File

@ -632,7 +632,7 @@ final class ChatListFilterTabContainerNode: ASDisplayNode {
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - 3.0, y: 0.0), size: CGSize(width: 3.0, height: 3.0)))
context.fill(CGRect(x: 1.5, y: 0.0, width: size.width - 3.0, height: 3.0))
context.fill(CGRect(x: 0.0, y: 2.0, width: size.width, height: 2.0))
})?.resizableImage(withCapInsets: UIEdgeInsets(top: 3.0, left: 2.5, bottom: 0.0, right: 2.5), resizingMode: .stretch)
})?.resizableImage(withCapInsets: UIEdgeInsets(top: 3.0, left: 3.0, bottom: 0.0, right: 3.0), resizingMode: .stretch)
}
if isReordering {

View File

@ -86,7 +86,17 @@ final class AccountManagerImpl {
for record in self.legacyRecordTable.getRecords() {
legacyRecordDict[record.id] = record
}
self.currentAtomicState = AccountManagerAtomicState(records: legacyRecordDict, currentRecordId: self.legacyMetadataTable.getCurrentAccountId(), currentAuthRecord: self.legacyMetadataTable.getCurrentAuthAccount())
self.currentAtomicState = AccountManagerAtomicState(records: legacyRecordDict, currentRecordId: self.legacyMetadataTable.getCurrentAccountId(), currentAuthRecord: self.legacyMetadataTable.getCurrentAuthAccount(), accessChallengeData: self.legacyMetadataTable.getAccessChallengeData())
self.syncAtomicStateToFile()
}
let tableAccessChallengeData = self.legacyMetadataTable.getAccessChallengeData()
if self.currentAtomicState.accessChallengeData != .none {
if tableAccessChallengeData == .none {
self.legacyMetadataTable.setAccessChallengeData(self.currentAtomicState.accessChallengeData)
}
} else if tableAccessChallengeData != .none {
self.currentAtomicState.accessChallengeData = tableAccessChallengeData
self.syncAtomicStateToFile()
}
@ -165,7 +175,9 @@ final class AccountManagerImpl {
return self.legacyMetadataTable.getAccessChallengeData()
}, setAccessChallengeData: { data in
self.currentUpdatedAccessChallengeData = data
self.currentAtomicStateUpdated = true
self.legacyMetadataTable.setAccessChallengeData(data)
self.currentAtomicState.accessChallengeData = data
}, getVersion: {
return self.legacyMetadataTable.getVersion()
}, setVersion: { version in

View File

@ -5,16 +5,19 @@ final class AccountManagerAtomicState: Codable {
case records
case currentRecordId
case currentAuthRecord
case accessChallengeData
}
var records: [AccountRecordId: AccountRecord]
var currentRecordId: AccountRecordId?
var currentAuthRecord: AuthAccountRecord?
var accessChallengeData: PostboxAccessChallengeData
init(records: [AccountRecordId: AccountRecord] = [:], currentRecordId: AccountRecordId? = nil, currentAuthRecord: AuthAccountRecord? = nil) {
init(records: [AccountRecordId: AccountRecord] = [:], currentRecordId: AccountRecordId? = nil, currentAuthRecord: AuthAccountRecord? = nil, accessChallengeData: PostboxAccessChallengeData = .none) {
self.records = records
self.currentRecordId = currentRecordId
self.currentAuthRecord = currentAuthRecord
self.accessChallengeData = accessChallengeData
}
init(from decoder: Decoder) throws {
@ -34,6 +37,12 @@ final class AccountManagerAtomicState: Codable {
self.currentRecordId = try container.decodeIfPresent(AccountRecordId.self, forKey: .currentRecordId)
}
self.currentAuthRecord = try container.decodeIfPresent(AuthAccountRecord.self, forKey: .currentAuthRecord)
if let accessChallengeData = try? container.decodeIfPresent(PostboxAccessChallengeData.self, forKey: .accessChallengeData) {
self.accessChallengeData = accessChallengeData
} else {
self.accessChallengeData = .none
}
}
public func encode(to encoder: Encoder) throws {
@ -43,5 +52,6 @@ final class AccountManagerAtomicState: Codable {
let currentRecordIdString: String? = self.currentRecordId.flatMap({ "\($0.rawValue)" })
try container.encodeIfPresent(currentRecordIdString, forKey: .currentRecordId)
try container.encodeIfPresent(self.currentAuthRecord, forKey: .currentAuthRecord)
try container.encode(self.accessChallengeData, forKey: .accessChallengeData)
}
}

View File

@ -12,7 +12,12 @@ public struct AccessChallengeAttempts: Equatable {
}
}
public enum PostboxAccessChallengeData: PostboxCoding, Equatable {
public enum PostboxAccessChallengeData: PostboxCoding, Equatable, Codable {
enum CodingKeys: String, CodingKey {
case numericalPassword
case plaintextPassword
}
case none
case numericalPassword(value: String)
case plaintextPassword(value: String)
@ -44,6 +49,29 @@ public enum PostboxAccessChallengeData: PostboxCoding, Equatable {
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let value = try? container.decode(String.self, forKey: .numericalPassword) {
self = .numericalPassword(value: value)
} else if let value = try? container.decode(String.self, forKey: .plaintextPassword) {
self = .plaintextPassword(value: value)
} else {
self = .none
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .none:
break
case let .numericalPassword(value):
try container.encode(value, forKey: .numericalPassword)
case let .plaintextPassword(value):
try container.encode(value, forKey: .plaintextPassword)
}
}
public var isLockable: Bool {
if case .none = self {
return false

View File

@ -11,7 +11,7 @@ public enum UpdatePinnedMessageError {
}
public enum PinnedMessageUpdate {
case pin(id: MessageId, silent: Bool)
case pin(id: MessageId, silent: Bool, forThisPeerOnlyIfPossible: Bool)
case clear(id: MessageId)
}
@ -20,7 +20,6 @@ public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update:
return (transaction.getPeer(peerId), transaction.getPeerCachedData(peerId: peerId))
}
|> mapError { _ -> UpdatePinnedMessageError in
return .generic
}
|> mapToSignal { peer, cachedPeerData -> Signal<Void, UpdatePinnedMessageError> in
guard let peer = peer, let inputPeer = apiInputPeer(peer) else {
@ -52,11 +51,14 @@ public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update:
var flags: Int32 = 0
let messageId: Int32
switch update {
case let .pin(id, silent):
case let .pin(id, silent, forThisPeerOnlyIfPossible):
messageId = id.id
if silent {
flags |= (1 << 0)
}
if forThisPeerOnlyIfPossible {
flags |= (1 << 2)
}
case let .clear(id):
messageId = id.id
flags |= 1 << 1
@ -77,7 +79,7 @@ public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update:
if peerId.namespace == Namespaces.Peer.CloudChannel {
let messageId: MessageId
switch update {
case let .pin(id, _):
case let .pin(id, _, _):
messageId = id
case let .clear(id):
messageId = id
@ -103,7 +105,6 @@ public func requestUpdatePinnedMessage(account: Account, peerId: PeerId, update:
}
}
|> mapError { _ -> UpdatePinnedMessageError in
return .generic
}
}
}
@ -114,7 +115,6 @@ public func requestUnpinAllMessages(account: Account, peerId: PeerId) -> Signal<
return (transaction.getPeer(peerId), transaction.getPeerCachedData(peerId: peerId))
}
|> mapError { _ -> UpdatePinnedMessageError in
return .generic
}
|> mapToSignal { peer, cachedPeerData -> Signal<Never, UpdatePinnedMessageError> in
guard let peer = peer, let inputPeer = apiInputPeer(peer) else {

View File

@ -217,7 +217,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
}
let textWithRanges: (String, [(Int, NSRange)])
if clippedText.isEmpty {
textWithRanges = strings.PUSH_PINNED_NOTEXT(authorName)
textWithRanges = strings.Message_PinnedGenericMessage(authorName)
} else {
textWithRanges = strings.Notification_PinnedTextMessage(authorName, clippedText)
}
@ -250,7 +250,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedQuizMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
}
case .deleted:
attributedString = addAttributesToStringWithRanges(strings.PUSH_PINNED_NOTEXT(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
attributedString = addAttributesToStringWithRanges(strings.Message_PinnedGenericMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))
}
case .joinedByLink:
attributedString = addAttributesToStringWithRanges(strings.Notification_JoinedGroupByLink(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)]))

View File

@ -2230,7 +2230,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return items
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: items, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items, reactionItems: [], gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
})
}, openMessageReplies: { [weak self] messageId, isChannelPost, displayModalProgress in
@ -2387,7 +2387,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return items
}
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: items, reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node, passthroughTouches: false)), items: items, reactionItems: [], gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
@ -3387,13 +3387,32 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|> restartIfError
}
struct TopMessage {
var message: Message
var index: Int
}
let topMessage = pinnedHistorySignal(anchorMessageId: nil, count: 3)
|> map { update -> Message? in
|> map { update -> TopMessage? in
switch update {
case .Loading:
return nil
case let .HistoryView(viewValue, _, _, _, _, _, _):
return viewValue.entries.last?.message
if let entry = viewValue.entries.last {
let index: Int
if let location = entry.location {
index = location.index
} else {
index = viewValue.entries.count - 1
}
return TopMessage(
message: entry.message,
index: index
)
} else {
return nil
}
}
}
@ -3529,7 +3548,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if pinnedMessages.messages.isEmpty {
return nil
}
topMessageId = topMessage?.id ?? pinnedMessages.messages[pinnedMessages.messages.count - 1].message.id
topMessageId = topMessage?.message.id ?? pinnedMessages.messages[pinnedMessages.messages.count - 1].message.id
if let referenceMessage = referenceMessage, referenceMessage.isScrolled, !pinnedMessages.messages.isEmpty, referenceMessage.id == pinnedMessages.messages[0].message.id, let topMessage = topMessage {
var index = topMessage.index
for message in pinnedMessages.messages {
if message.message.id == topMessage.message.id {
index = message.index
break
}
}
return ChatPinnedMessage(message: topMessage.message, index: index, totalCount: pinnedMessages.totalCount, topMessageId: topMessageId)
}
//print("reference: \(String(describing: referenceMessage?.id.id)) entries: \(view.entries.map(\.index.id.id))")
for i in 0 ..< pinnedMessages.messages.count {
let entry = pinnedMessages.messages[i]
@ -3646,7 +3678,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let pinnedMessageId = pinnedMessageId {
if let cachedDataMessages = combinedInitialData.cachedDataMessages {
if let message = cachedDataMessages[pinnedMessageId] {
pinnedMessage = ChatPinnedMessage(message: message, index: 1, totalCount: 1, topMessageId: message.id)
pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id)
}
}
}
@ -3798,7 +3830,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if let pinnedMessageId = pinnedMessageId {
if let message = messages?[pinnedMessageId] {
pinnedMessage = ChatPinnedMessage(message: message, index: 1, totalCount: 1, topMessageId: message.id)
pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id)
}
}
case .peer:
@ -5080,29 +5112,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}, unblockPeer: { [weak self] in
self?.unblockPeer()
}, pinMessage: { [weak self] messageId in
}, pinMessage: { [weak self] messageId, contextController in
if let strongSelf = self, case let .peer(currentPeerId) = strongSelf.chatLocation {
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer {
var canManagePin = false
if let channel = peer as? TelegramChannel {
canManagePin = channel.hasPermission(.pinMessages)
} else if let group = peer as? TelegramGroup {
switch group.role {
case .creator, .admin:
canManagePin = true
default:
if let defaultBannedRights = group.defaultBannedRights {
canManagePin = !defaultBannedRights.flags.contains(.banPinMessages)
} else {
canManagePin = true
}
}
} else if let _ = peer as? TelegramUser, strongSelf.presentationInterfaceState.explicitelyCanPinMessages {
canManagePin = true
}
if canManagePin {
let pinAction: (Bool) -> Void = { notify in
if strongSelf.canManagePin() {
let pinAction: (Bool, Bool) -> Void = { notify, forThisPeerOnlyIfPossible in
if let strongSelf = self {
let disposable: MetaDisposable
if let current = strongSelf.unpinMessageDisposable {
@ -5111,60 +5125,94 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
disposable = MetaDisposable()
strongSelf.unpinMessageDisposable = disposable
}
disposable.set(requestUpdatePinnedMessage(account: strongSelf.context.account, peerId: currentPeerId, update: .pin(id: messageId, silent: !notify)).start())
disposable.set(requestUpdatePinnedMessage(account: strongSelf.context.account, peerId: currentPeerId, update: .pin(id: messageId, silent: !notify, forThisPeerOnlyIfPossible: forThisPeerOnlyIfPossible)).start())
}
}
var pinImmediately = false
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
pinImmediately = true
} else if let _ = peer as? TelegramUser {
pinImmediately = true
}
if pinImmediately {
pinAction(true)
} else {
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError> = strongSelf.topPinnedMessageSignal(latest: true)
|> take(1)
if let peer = peer as? TelegramUser, peer.id != strongSelf.context.account.peerId, let contextController = contextController {
var contextItems: [ContextMenuItem] = []
let _ = (topPinnedMessage
|> deliverOnMainQueue).start(next: { value in
contextItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_PinMessagesFor(peer.compactDisplayTitle).0, textColor: .primary, icon: { _ in nil }, action: { c, _ in
c.dismiss(completion: {
pinAction(true, false)
})
})))
contextItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_PinMessagesForMe, textColor: .primary, icon: { _ in nil }, action: { c, _ in
c.dismiss(completion: {
pinAction(true, true)
})
})))
contextController.setItems(.single(contextItems))
return
} else {
let continueAction: () -> Void = {
guard let strongSelf = self else {
return
}
let title: String?
let text: String
let actionLayout: TextAlertContentActionLayout
let actions: [TextAlertAction]
if let value = value, value.message.id > messageId {
title = strongSelf.presentationData.strings.Conversation_PinOlderMessageAlertTitle
text = strongSelf.presentationData.strings.Conversation_PinOlderMessageAlertText
actionLayout = .vertical
actions = [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Conversation_PinMessageAlertPin, action: {
pinAction(false)
}),
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
})
]
} else {
title = nil
text = strongSelf.presentationData.strings.Conversation_PinMessageAlertGroup
actionLayout = .horizontal
actions = [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Conversation_PinMessageAlert_OnlyPin, action: {
pinAction(false)
}),
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Yes, action: {
pinAction(true)
})
]
var pinImmediately = false
if let channel = peer as? TelegramChannel, case .broadcast = channel.info {
pinImmediately = true
} else if let _ = peer as? TelegramUser {
pinImmediately = true
}
strongSelf.present(textAlertController(context: strongSelf.context, title: title, text: text, actions: actions, actionLayout: actionLayout), in: .window(.root))
})
if pinImmediately {
pinAction(true, false)
} else {
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError> = strongSelf.topPinnedMessageSignal(latest: true)
|> take(1)
let _ = (topPinnedMessage
|> deliverOnMainQueue).start(next: { value in
guard let strongSelf = self else {
return
}
let title: String?
let text: String
let actionLayout: TextAlertContentActionLayout
let actions: [TextAlertAction]
if let value = value, value.message.id > messageId {
title = strongSelf.presentationData.strings.Conversation_PinOlderMessageAlertTitle
text = strongSelf.presentationData.strings.Conversation_PinOlderMessageAlertText
actionLayout = .vertical
actions = [
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Conversation_PinMessageAlertPin, action: {
pinAction(false, false)
}),
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
})
]
} else {
title = nil
text = strongSelf.presentationData.strings.Conversation_PinMessageAlertGroup
actionLayout = .horizontal
actions = [
TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Conversation_PinMessageAlert_OnlyPin, action: {
pinAction(false, false)
}),
TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Yes, action: {
pinAction(true, false)
})
]
}
strongSelf.present(textAlertController(context: strongSelf.context, title: title, text: text, actions: actions, actionLayout: actionLayout), in: .window(.root))
})
}
}
if let contextController = contextController {
contextController.dismiss(completion: {
continueAction()
})
} else {
continueAction()
}
}
} else {
if let topPinnedMessageId = strongSelf.presentationInterfaceState.pinnedMessage?.topMessageId {
@ -5758,7 +5806,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(peerId), subject: .pinnedMessages(id: nil), botStart: nil, mode: .standard(previewing: true))
chatController.canReadHistory.set(false)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture)
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, passthroughTouches: true)), items: .single(items), reactionItems: [], gesture: gesture)
strongSelf.presentInGlobalOverlay(contextController)
}, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get(), inlineSearch: self.performingInlineSearch.get()))
@ -11016,11 +11064,12 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
let navigationController: NavigationController? = nil
let passthroughTouches: Bool = false
let passthroughTouches: Bool
init(controller: ViewController, sourceNode: ASDisplayNode?) {
init(controller: ViewController, sourceNode: ASDisplayNode?, passthroughTouches: Bool) {
self.controller = controller
self.sourceNode = sourceNode
self.passthroughTouches = passthroughTouches
}
func transitionInfo() -> ContextControllerTakeControllerInfo? {

View File

@ -344,6 +344,16 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
canPin = false
}
if let peer = messages[0].peers[messages[0].id.peerId] {
if peer.isDeleted {
canPin = false
}
if !(peer is TelegramSecretChat) && messages[0].id.namespace != Namespaces.Message.Cloud {
canPin = false
canReply = false
}
}
var loadStickerSaveStatusSignal: Signal<Bool?, NoError> = .single(nil)
if loadStickerSaveStatus != nil {
loadStickerSaveStatusSignal = context.account.postbox.transaction { transaction -> Bool? in
@ -682,9 +692,8 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
} else {
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Conversation_Pin, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pin"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in
interfaceInteraction.pinMessage(messages[0].id)
f(.dismissWithoutContent)
}, action: { c, _ in
interfaceInteraction.pinMessage(messages[0].id, c)
})))
}
}

View File

@ -60,7 +60,7 @@ class ChatMessageShareButton: HighlightableButtonNode {
fatalError("init(coder:) has not been implemented")
}
func update(presentationData: ChatPresentationData, chatLocation: ChatLocation, message: Message, account: Account) -> CGSize {
func update(presentationData: ChatPresentationData, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account) -> CGSize {
var isReplies = false
var replyCount = 0
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
@ -84,7 +84,9 @@ class ChatMessageShareButton: HighlightableButtonNode {
let graphics = PresentationResourcesChat.additionalGraphics(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners)
var updatedShareButtonBackground: UIImage?
var updatedIconImage: UIImage?
if isReplies {
if case .pinnedMessages = subject {
updatedShareButtonBackground = graphics.chatBubbleNavigateButtonImage
} else if isReplies {
updatedShareButtonBackground = PresentationResourcesChat.chatFreeCommentButtonBackground(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
} else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) {
@ -607,7 +609,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp())
var needShareButton = false
if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) {
if case .pinnedMessages = item.associatedData.subject {
needShareButton = true
} else if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) {
needShareButton = false
} else if item.message.id.peerId.isRepliesOrSavedMessages(accountPeerId: item.context.account.peerId) {
for attribute in item.content.firstMessage.attributes {
@ -941,7 +945,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
strongSelf.addSubnode(updatedShareButtonNode)
updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside)
}
let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, message: item.message, account: item.context.account)
let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account)
updatedShareButtonNode.frame = CGRect(origin: CGPoint(x: updatedImageFrame.maxX + 8.0, y: updatedImageFrame.maxY - buttonSize.height - 4.0), size: buttonSize)
} else if let shareButtonNode = strongSelf.shareButtonNode {
shareButtonNode.removeFromSupernode()
@ -1266,6 +1270,11 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
@objc func shareButtonPressed() {
if let item = self.item {
if case .pinnedMessages = item.associatedData.subject {
item.controllerInteraction.navigateToMessageStandalone(item.content.firstMessage.id)
return
}
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
for attribute in item.message.attributes {
if let _ = attribute as? ReplyThreadMessageAttribute {

View File

@ -162,7 +162,9 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
if hasDiscussion {
var canComment = false
if firstMessage.id.namespace == Namespaces.Message.Local {
if case .pinnedMessages = item.associatedData.subject {
canComment = false
} else if firstMessage.id.namespace == Namespaces.Message.Local {
canComment = true
} else {
for attribute in firstMessage.attributes {

View File

@ -216,7 +216,9 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp())
var needShareButton = false
if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) {
if case .pinnedMessages = item.associatedData.subject {
needShareButton = true
} else if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) {
needShareButton = false
}
else if item.message.id.peerId == item.context.account.peerId {
@ -473,7 +475,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
strongSelf.addSubnode(updatedShareButtonNode)
updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside)
}
let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, message: item.message, account: item.context.account)
let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account)
updatedShareButtonNode.frame = CGRect(origin: CGPoint(x: videoFrame.maxX - 7.0, y: videoFrame.maxY - 24.0 - buttonSize.height), size: buttonSize)
} else if let shareButtonNode = strongSelf.shareButtonNode {
shareButtonNode.removeFromSupernode()
@ -745,6 +747,11 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
@objc func shareButtonPressed() {
if let item = self.item {
if case .pinnedMessages = item.associatedData.subject {
item.controllerInteraction.navigateToMessageStandalone(item.content.firstMessage.id)
return
}
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
for attribute in item.message.attributes {
if let _ = attribute as? ReplyThreadMessageAttribute {

View File

@ -279,7 +279,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
let isFailed = item.content.firstMessage.effectivelyFailed(timestamp: item.context.account.network.getApproximateRemoteTimestamp())
var needShareButton = false
if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) {
if case .pinnedMessages = item.associatedData.subject {
needShareButton = true
} else if isFailed || Namespaces.Message.allScheduled.contains(item.message.id.namespace) {
needShareButton = false
} else if item.message.id.peerId == item.context.account.peerId {
for attribute in item.content.firstMessage.attributes {
@ -568,7 +570,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
strongSelf.addSubnode(updatedShareButtonNode)
updatedShareButtonNode.addTarget(strongSelf, action: #selector(strongSelf.shareButtonPressed), forControlEvents: .touchUpInside)
}
let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, message: item.message, account: item.context.account)
let buttonSize = updatedShareButtonNode.update(presentationData: item.presentationData, chatLocation: item.chatLocation, subject: item.associatedData.subject, message: item.message, account: item.context.account)
let shareButtonFrame = CGRect(origin: CGPoint(x: baseShareButtonFrame.minX, y: baseShareButtonFrame.maxY - buttonSize.height), size: buttonSize)
transition.updateFrame(node: updatedShareButtonNode, frame: shareButtonFrame)
} else if let shareButtonNode = strongSelf.shareButtonNode {
@ -811,6 +813,11 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
@objc func shareButtonPressed() {
if let item = self.item {
if case .pinnedMessages = item.associatedData.subject {
item.controllerInteraction.navigateToMessageStandalone(item.content.firstMessage.id)
return
}
if let channel = item.message.peers[item.message.id.peerId] as? TelegramChannel, case .broadcast = channel.info {
for attribute in item.message.attributes {
if let _ = attribute as? ReplyThreadMessageAttribute {

View File

@ -94,7 +94,7 @@ final class ChatPanelInterfaceInteraction {
let setupMessageAutoremoveTimeout: () -> Void
let sendSticker: (FileMediaReference, ASDisplayNode, CGRect) -> Bool
let unblockPeer: () -> Void
let pinMessage: (MessageId) -> Void
let pinMessage: (MessageId, ContextController?) -> Void
let unpinMessage: (MessageId, Bool) -> Void
let unpinAllMessages: () -> Void
let openPinnedList: (MessageId) -> Void
@ -173,7 +173,7 @@ final class ChatPanelInterfaceInteraction {
setupMessageAutoremoveTimeout: @escaping () -> Void,
sendSticker: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool,
unblockPeer: @escaping () -> Void,
pinMessage: @escaping (MessageId) -> Void,
pinMessage: @escaping (MessageId, ContextController?) -> Void,
unpinMessage: @escaping (MessageId, Bool) -> Void,
unpinAllMessages: @escaping () -> Void,
openPinnedList: @escaping (MessageId) -> Void,

View File

@ -311,7 +311,13 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
var imageDimensions: CGSize?
var titleStrings: [AnimatedCountLabelNode.Segment] = []
if pinnedMessage.totalCount > 1 {
if pinnedMessage.totalCount == 2 {
if pinnedMessage.index == 0 {
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
} else {
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
}
} else if pinnedMessage.totalCount > 1 {
titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))

View File

@ -99,7 +99,7 @@ final class ChatRecentActionsController: TelegramBaseController {
}, sendSticker: { _, _, _ in
return false
}, unblockPeer: {
}, pinMessage: { _ in
}, pinMessage: { _, _ in
}, unpinMessage: { _, _ in
}, unpinAllMessages: {
}, openPinnedList: { _ in

View File

@ -405,7 +405,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
}, sendSticker: { _, _, _ in
return false
}, unblockPeer: {
}, pinMessage: { _ in
}, pinMessage: { _, _ in
}, unpinMessage: { _, _ in
}, unpinAllMessages: {
}, openPinnedList: { _ in