mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
0457354220
@ -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 %@";
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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)]))
|
||||
|
Binary file not shown.
@ -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? {
|
||||
|
@ -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)
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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)))
|
||||
|
@ -99,7 +99,7 @@ final class ChatRecentActionsController: TelegramBaseController {
|
||||
}, sendSticker: { _, _, _ in
|
||||
return false
|
||||
}, unblockPeer: {
|
||||
}, pinMessage: { _ in
|
||||
}, pinMessage: { _, _ in
|
||||
}, unpinMessage: { _, _ in
|
||||
}, unpinAllMessages: {
|
||||
}, openPinnedList: { _ in
|
||||
|
@ -405,7 +405,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
||||
}, sendSticker: { _, _, _ in
|
||||
return false
|
||||
}, unblockPeer: {
|
||||
}, pinMessage: { _ in
|
||||
}, pinMessage: { _, _ in
|
||||
}, unpinMessage: { _, _ in
|
||||
}, unpinAllMessages: {
|
||||
}, openPinnedList: { _ in
|
||||
|
Loading…
x
Reference in New Issue
Block a user