mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
6aa462ba8b
@ -10839,3 +10839,5 @@ Sorry for the inconvenience.";
|
||||
"RequestPeer.SelectUsers.SearchPlaceholder" = "Search";
|
||||
"RequestPeer.ReachedMaximum_1" = "You can select up to %@ user.";
|
||||
"RequestPeer.ReachedMaximum_any" = "You can select up to %@ users.";
|
||||
|
||||
"ChatList.DeleteSavedPeerConfirmation" = "Are you sure you want to delete saved messages from %@?";
|
||||
|
@ -758,10 +758,10 @@ public enum ChatControllerSubject: Equatable {
|
||||
}
|
||||
|
||||
public enum ChatControllerPresentationMode: Equatable {
|
||||
public enum StandardPresentation {
|
||||
public enum StandardPresentation: Equatable {
|
||||
case `default`
|
||||
case previewing
|
||||
case embedded
|
||||
case embedded(invertDirection: Bool)
|
||||
}
|
||||
|
||||
case standard(StandardPresentation)
|
||||
@ -912,6 +912,9 @@ public protocol ChatController: ViewController {
|
||||
func cancelSelectingMessages()
|
||||
func activateSearch(domain: ChatSearchDomain, query: String)
|
||||
func beginClearHistory(type: InteractiveHistoryClearingType)
|
||||
|
||||
func transferScrollingVelocity(_ velocity: CGFloat)
|
||||
func updateIsScrollingLockedAtTop(isScrollingLockedAtTop: Bool)
|
||||
}
|
||||
|
||||
public protocol ChatMessagePreviewItemNode: AnyObject {
|
||||
|
@ -854,6 +854,44 @@ func chatForumTopicMenuItems(context: AccountContext, peerId: PeerId, threadId:
|
||||
}
|
||||
}
|
||||
|
||||
public func savedMessagesPeerMenuItems(context: AccountContext, threadId: Int64, parentController: ViewController) -> Signal<[ContextMenuItem], NoError> {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||
let strings = presentationData.strings
|
||||
|
||||
return combineLatest(
|
||||
context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: PeerId(threadId))
|
||||
),
|
||||
context.account.postbox.transaction { transaction -> [Int64] in
|
||||
return transaction.getPeerPinnedThreads(peerId: context.account.peerId)
|
||||
}
|
||||
)
|
||||
|> mapToSignal { [weak parentController] peer, pinnedThreadIds -> Signal<[ContextMenuItem], NoError> in
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
let isPinned = pinnedThreadIds.contains(threadId)
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: isPinned ? strings.ChatList_Context_Unpin : strings.ChatList_Context_Pin, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isPinned ? "Chat/Context Menu/Unpin": "Chat/Context Menu/Pin"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: context.account.peerId, threadId: threadId)
|
||||
|> deliverOnMainQueue).startStandalone(error: { error in
|
||||
switch error {
|
||||
case let .limitReached(count):
|
||||
let controller = PremiumLimitScreen(context: context, subject: .pinnedSavedPeers, count: Int32(count), action: {
|
||||
return true
|
||||
})
|
||||
parentController?.push(controller)
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
})))
|
||||
|
||||
return .single(items)
|
||||
}
|
||||
}
|
||||
|
||||
private func openCustomMute(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, baseController: ViewController) {
|
||||
let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .mute, currentTime: nil, dismissByTapOutside: true, completion: { [weak baseController] value in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
@ -1421,7 +1421,21 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
}
|
||||
|
||||
(strongSelf.navigationController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
(strongSelf.navigationController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.navigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
}
|
||||
peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peer, threadId in
|
||||
let peerId = peer.id
|
||||
@ -1432,7 +1446,22 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
||||
}
|
||||
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
(strongSelf.navigationController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
|
||||
(strongSelf.navigationController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.navigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
}), in: .window(.root))
|
||||
|
||||
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in
|
||||
return .forward(source: id, threadId: threadId, grouping: .auto, attributes: [], correlationId: nil)
|
||||
|
@ -379,7 +379,13 @@ public struct ChatListItemFilterData: Equatable {
|
||||
private func revealOptions(strings: PresentationStrings, theme: PresentationTheme, isPinned: Bool, isMuted: Bool?, location: ChatListControllerLocation, peerId: EnginePeer.Id, accountPeerId: EnginePeer.Id, canDelete: Bool, isEditing: Bool, filterData: ChatListItemFilterData?) -> [ItemListRevealOption] {
|
||||
var options: [ItemListRevealOption] = []
|
||||
if !isEditing {
|
||||
if case .chatList(.archive) = location {
|
||||
if case .savedMessagesChats = location {
|
||||
if isPinned {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
|
||||
} else {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
|
||||
}
|
||||
} else if case .chatList(.archive) = location {
|
||||
if isPinned {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor))
|
||||
} else {
|
||||
@ -398,28 +404,31 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem
|
||||
if canDelete {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.delete.rawValue, title: strings.Common_Delete, icon: deleteIcon, color: theme.list.itemDisclosureActions.destructive.fillColor, textColor: theme.list.itemDisclosureActions.destructive.foregroundColor))
|
||||
}
|
||||
if !isEditing {
|
||||
var canArchive = false
|
||||
var canUnarchive = false
|
||||
if let filterData = filterData {
|
||||
if filterData.excludesArchived {
|
||||
canArchive = true
|
||||
}
|
||||
} else {
|
||||
if case let .chatList(groupId) = location {
|
||||
if case .root = groupId {
|
||||
if case .savedMessagesChats = location {
|
||||
} else {
|
||||
if !isEditing {
|
||||
var canArchive = false
|
||||
var canUnarchive = false
|
||||
if let filterData = filterData {
|
||||
if filterData.excludesArchived {
|
||||
canArchive = true
|
||||
} else {
|
||||
canUnarchive = true
|
||||
}
|
||||
} else {
|
||||
if case let .chatList(groupId) = location {
|
||||
if case .root = groupId {
|
||||
canArchive = true
|
||||
} else {
|
||||
canUnarchive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if canArchive {
|
||||
if canArchivePeer(id: peerId, accountPeerId: accountPeerId) {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.archive.rawValue, title: strings.ChatList_ArchiveAction, icon: archiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor))
|
||||
if canArchive {
|
||||
if canArchivePeer(id: peerId, accountPeerId: accountPeerId) {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.archive.rawValue, title: strings.ChatList_ArchiveAction, icon: archiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor))
|
||||
}
|
||||
} else if canUnarchive {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.unarchive.rawValue, title: strings.ChatList_UnarchiveAction, icon: unarchiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor))
|
||||
}
|
||||
} else if canUnarchive {
|
||||
options.append(ItemListRevealOption(key: RevealOptionKey.unarchive.rawValue, title: strings.ChatList_UnarchiveAction, icon: unarchiveIcon, color: theme.list.itemDisclosureActions.inactive.fillColor, textColor: theme.list.itemDisclosureActions.inactive.foregroundColor))
|
||||
}
|
||||
}
|
||||
return options
|
||||
|
@ -22,6 +22,7 @@ import StoryContainerScreen
|
||||
import ChatListHeaderComponent
|
||||
import UndoUI
|
||||
import NewSessionInfoScreen
|
||||
import PresentationDataUtils
|
||||
|
||||
public enum ChatListNodeMode {
|
||||
case chatList(appendContacts: Bool)
|
||||
@ -1420,70 +1421,90 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
}, setItemPinned: { [weak self] itemId, _ in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).startStandalone(next: { peer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard case let .chatList(groupId) = strongSelf.location else {
|
||||
return
|
||||
}
|
||||
|
||||
let isPremium = peer?.isPremium ?? false
|
||||
let location: TogglePeerChatPinnedLocation
|
||||
if let chatListFilter = chatListFilter {
|
||||
location = .filter(chatListFilter.id)
|
||||
} else {
|
||||
location = .group(groupId._asGroup())
|
||||
}
|
||||
let _ = (context.engine.peers.toggleItemPinned(location: location, itemId: itemId)
|
||||
|> deliverOnMainQueue).startStandalone(next: { result in
|
||||
if let strongSelf = self {
|
||||
switch result {
|
||||
case .done:
|
||||
if case .savedMessagesChats = location {
|
||||
if case let .peer(itemPeerId) = itemId {
|
||||
let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: context.account.peerId, threadId: itemPeerId.toInt64())
|
||||
|> deliverOnMainQueue).start(error: { error in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
switch error {
|
||||
case let .limitReached(count):
|
||||
let controller = PremiumLimitScreen(context: context, subject: .pinnedSavedPeers, count: Int32(count), action: {
|
||||
return true
|
||||
})
|
||||
self.push?(controller)
|
||||
default:
|
||||
break
|
||||
case let .limitExceeded(count, _):
|
||||
if isPremium {
|
||||
if case .filter = location {
|
||||
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {
|
||||
return true
|
||||
})
|
||||
strongSelf.push?(controller)
|
||||
} else {
|
||||
let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {
|
||||
return true
|
||||
})
|
||||
strongSelf.push?(controller)
|
||||
}
|
||||
} else {
|
||||
if case .filter = location {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {
|
||||
let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats)
|
||||
replaceImpl?(premiumScreen)
|
||||
return true
|
||||
})
|
||||
strongSelf.push?(controller)
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).startStandalone(next: { peer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard case let .chatList(groupId) = strongSelf.location else {
|
||||
return
|
||||
}
|
||||
|
||||
let isPremium = peer?.isPremium ?? false
|
||||
let location: TogglePeerChatPinnedLocation
|
||||
if let chatListFilter = chatListFilter {
|
||||
location = .filter(chatListFilter.id)
|
||||
} else {
|
||||
location = .group(groupId._asGroup())
|
||||
}
|
||||
let _ = (context.engine.peers.toggleItemPinned(location: location, itemId: itemId)
|
||||
|> deliverOnMainQueue).startStandalone(next: { result in
|
||||
if let strongSelf = self {
|
||||
switch result {
|
||||
case .done:
|
||||
break
|
||||
case let .limitExceeded(count, _):
|
||||
if isPremium {
|
||||
if case .filter = location {
|
||||
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {
|
||||
return true
|
||||
})
|
||||
strongSelf.push?(controller)
|
||||
} else {
|
||||
let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {
|
||||
return true
|
||||
})
|
||||
strongSelf.push?(controller)
|
||||
}
|
||||
} else {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {
|
||||
let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats)
|
||||
replaceImpl?(premiumScreen)
|
||||
return true
|
||||
})
|
||||
strongSelf.push?(controller)
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
if case .filter = location {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {
|
||||
let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats)
|
||||
replaceImpl?(premiumScreen)
|
||||
return true
|
||||
})
|
||||
strongSelf.push?(controller)
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
} else {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {
|
||||
let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats)
|
||||
replaceImpl?(premiumScreen)
|
||||
return true
|
||||
})
|
||||
strongSelf.push?(controller)
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}, setPeerMuted: { [weak self] peerId, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
|
@ -16,6 +16,9 @@ swift_library(
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/AvatarNode:AvatarNode",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -6,6 +6,9 @@ import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AvatarNode
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import BalancedTextComponent
|
||||
import MultilineTextComponent
|
||||
|
||||
public enum DeleteChatPeerAction {
|
||||
case delete
|
||||
@ -15,6 +18,7 @@ public enum DeleteChatPeerAction {
|
||||
case clearCacheSuggestion
|
||||
case removeFromGroup
|
||||
case removeFromChannel
|
||||
case deleteSavedPeer
|
||||
}
|
||||
|
||||
private let avatarFont = avatarPlaceholderFont(size: 26.0)
|
||||
@ -26,18 +30,20 @@ public final class DeleteChatPeerActionSheetItem: ActionSheetItem {
|
||||
let action: DeleteChatPeerAction
|
||||
let strings: PresentationStrings
|
||||
let nameDisplayOrder: PresentationPersonNameOrder
|
||||
let balancedLayout: Bool
|
||||
|
||||
public init(context: AccountContext, peer: EnginePeer, chatPeer: EnginePeer, action: DeleteChatPeerAction, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder) {
|
||||
public init(context: AccountContext, peer: EnginePeer, chatPeer: EnginePeer, action: DeleteChatPeerAction, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, balancedLayout: Bool = false) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.chatPeer = chatPeer
|
||||
self.action = action
|
||||
self.strings = strings
|
||||
self.nameDisplayOrder = nameDisplayOrder
|
||||
self.balancedLayout = balancedLayout
|
||||
}
|
||||
|
||||
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
return DeleteChatPeerActionSheetItemNode(theme: theme, strings: self.strings, nameOrder: self.nameDisplayOrder, context: self.context, peer: self.peer, chatPeer: self.chatPeer, action: self.action)
|
||||
return DeleteChatPeerActionSheetItemNode(theme: theme, strings: self.strings, nameOrder: self.nameDisplayOrder, context: self.context, peer: self.peer, chatPeer: self.chatPeer, action: self.action, balancedLayout: self.balancedLayout)
|
||||
}
|
||||
|
||||
public func updateNode(_ node: ActionSheetItemNode) {
|
||||
@ -47,15 +53,19 @@ public final class DeleteChatPeerActionSheetItem: ActionSheetItem {
|
||||
private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||
private let theme: ActionSheetControllerTheme
|
||||
private let strings: PresentationStrings
|
||||
private let balancedLayout: Bool
|
||||
|
||||
private let avatarNode: AvatarNode
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private var text: NSAttributedString?
|
||||
private let textView = ComponentView<Empty>()
|
||||
|
||||
private let accessibilityArea: AccessibilityAreaNode
|
||||
|
||||
init(theme: ActionSheetControllerTheme, strings: PresentationStrings, nameOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, chatPeer: EnginePeer, action: DeleteChatPeerAction) {
|
||||
init(theme: ActionSheetControllerTheme, strings: PresentationStrings, nameOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, chatPeer: EnginePeer, action: DeleteChatPeerAction, balancedLayout: Bool) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.balancedLayout = balancedLayout
|
||||
|
||||
let textFont = Font.regular(floor(theme.baseFontSize * 14.0 / 17.0))
|
||||
let boldFont = Font.semibold(floor(theme.baseFontSize * 14.0 / 17.0))
|
||||
@ -63,24 +73,19 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.isAccessibilityElement = false
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
self.textNode.textAlignment = .center
|
||||
self.textNode.isAccessibilityElement = false
|
||||
|
||||
self.accessibilityArea = AccessibilityAreaNode()
|
||||
|
||||
super.init(theme: theme)
|
||||
|
||||
self.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.accessibilityArea)
|
||||
|
||||
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 if chatPeer.id.isAnonymousSavedMessages {
|
||||
self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .anonymousSavedMessagesIcon)
|
||||
} else {
|
||||
var overrideImage: AvatarNodeImageOverride?
|
||||
if chatPeer.isDeleted {
|
||||
@ -127,6 +132,10 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||
} else {
|
||||
text = strings.ChatList_DeleteChatConfirmation(peer.displayTitle(strings: strings, displayOrder: nameOrder))
|
||||
}
|
||||
case .deleteSavedPeer:
|
||||
//TODO:localize
|
||||
let peerTitle = peer.displayTitle(strings: strings, displayOrder: nameOrder)
|
||||
text = strings.ChatList_DeleteSavedPeerConfirmation(peerTitle)
|
||||
case let .clearHistory(canClearCache):
|
||||
if peer.id == context.account.peerId {
|
||||
text = PresentationStrings.FormattedString(string: strings.ChatList_ClearSavedMessagesConfirmation, ranges: [])
|
||||
@ -162,7 +171,7 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||
}
|
||||
|
||||
if let attributedText = attributedText {
|
||||
self.textNode.attributedText = attributedText
|
||||
self.text = attributedText
|
||||
|
||||
self.accessibilityArea.accessibilityLabel = attributedText.string
|
||||
self.accessibilityArea.accessibilityTraits = .staticText
|
||||
@ -170,7 +179,21 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||
}
|
||||
|
||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: constrainedSize.width - 20.0, height: .greatestFiniteMagnitude))
|
||||
let textComponent: AnyComponent<Empty>
|
||||
if self.balancedLayout {
|
||||
textComponent = AnyComponent(BalancedTextComponent(
|
||||
text: .plain(self.text ?? NSAttributedString()),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0
|
||||
))
|
||||
} else {
|
||||
textComponent = AnyComponent(MultilineTextComponent(
|
||||
text: .plain(self.text ?? NSAttributedString()),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0
|
||||
))
|
||||
}
|
||||
let textSize = self.textView.update(transition: .immediate, component: textComponent, environment: {}, containerSize: CGSize(width: constrainedSize.width - 20.0, height: 1000.0))
|
||||
|
||||
let topInset: CGFloat = 16.0
|
||||
let avatarSize: CGFloat = 60.0
|
||||
@ -178,7 +201,13 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||
let bottomInset: CGFloat = 15.0
|
||||
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - avatarSize) / 2.0), y: topInset), size: CGSize(width: avatarSize, height: avatarSize))
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - textSize.width) / 2.0), y: topInset + avatarSize + textSpacing), size: textSize)
|
||||
|
||||
if let textComponentView = self.textView.view {
|
||||
if textComponentView.superview == nil {
|
||||
self.view.addSubview(textComponentView)
|
||||
}
|
||||
textComponentView.frame = CGRect(origin: CGPoint(x: floor((constrainedSize.width - textSize.width) / 2.0), y: topInset + avatarSize + textSpacing), size: textSize)
|
||||
}
|
||||
|
||||
let size = CGSize(width: constrainedSize.width, height: topInset + avatarSize + textSpacing + textSize.height + bottomInset)
|
||||
self.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
@ -178,7 +178,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
if self.layout != nil && self.layout! != layout {
|
||||
if self.layout != nil && self.layout!.size != layout.size {
|
||||
if self.dismissImmediatelyOnLayoutUpdate {
|
||||
self.dismissImmediately()
|
||||
} else {
|
||||
|
@ -184,7 +184,21 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
text = ""
|
||||
}
|
||||
}
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), nil)
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.getNavigationController() else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -311,6 +311,7 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var attemptNavigationImpl: ((@escaping () -> Void) -> Bool)?
|
||||
var navigationController: (() -> NavigationController?)?
|
||||
|
||||
var dismissTooltipsImpl: (() -> Void)?
|
||||
|
||||
@ -385,7 +386,21 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
}
|
||||
}
|
||||
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), nil)
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = navigationController?() else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), nil)
|
||||
})
|
||||
}
|
||||
shareController.actionCompleted = {
|
||||
@ -746,6 +761,9 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
||||
return true
|
||||
}
|
||||
}
|
||||
navigationController = { [weak controller] in
|
||||
return controller?.navigationController as? NavigationController
|
||||
}
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
if let controller = controller {
|
||||
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
|
||||
|
@ -466,7 +466,21 @@ public final class InviteLinkInviteController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -397,6 +397,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||
var navigationController: (() -> NavigationController?)?
|
||||
|
||||
var dismissTooltipsImpl: (() -> Void)?
|
||||
|
||||
@ -463,7 +464,21 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
}
|
||||
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), nil)
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = navigationController?() else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), nil)
|
||||
})
|
||||
}
|
||||
shareController.actionCompleted = {
|
||||
@ -665,7 +680,21 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
}
|
||||
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), nil)
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = navigationController?() else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), nil)
|
||||
})
|
||||
}
|
||||
shareController.actionCompleted = {
|
||||
@ -925,6 +954,9 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
||||
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
|
||||
}
|
||||
}
|
||||
navigationController = { [weak controller] in
|
||||
return controller?.navigationController as? NavigationController
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, p in
|
||||
if let controller = controller {
|
||||
controller.present(c, in: .window(.root), with: p)
|
||||
|
@ -536,7 +536,21 @@ public final class InviteLinkViewController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ final class MutableMessageHistorySavedMessagesIndexView: MutablePostboxView {
|
||||
|
||||
self.peer = postbox.peerTable.get(self.peerId)
|
||||
|
||||
let validIndexBoundary = postbox.peerThreadCombinedStateTable.get(peerId: peerId)?.validIndexBoundary
|
||||
let validIndexBoundary = postbox.peerThreadCombinedStateTable.get(peerId: self.peerId)?.validIndexBoundary
|
||||
self.isLoading = validIndexBoundary == nil
|
||||
|
||||
if let validIndexBoundary = validIndexBoundary {
|
||||
|
@ -1021,6 +1021,22 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||
badgeGraphPosition = badgePosition
|
||||
|
||||
if isPremiumDisabled {
|
||||
badgeText = "\(limit)"
|
||||
string = strings.Premium_MaxPinsNoPremiumText("\(limit)").string
|
||||
}
|
||||
case .pinnedSavedPeers:
|
||||
//TODO:localize
|
||||
let limit = state.limits.maxPinnedSavedChatCount
|
||||
let premiumLimit = state.premiumLimits.maxPinnedSavedChatCount
|
||||
iconName = "Premium/Pin"
|
||||
badgeText = "\(component.count)"
|
||||
string = component.count >= premiumLimit ? strings.Premium_MaxPinsFinalText("\(premiumLimit)").string : strings.Premium_MaxPinsText("\(limit)", "\(premiumLimit)").string
|
||||
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||
badgeGraphPosition = badgePosition
|
||||
|
||||
if isPremiumDisabled {
|
||||
badgeText = "\(limit)"
|
||||
string = strings.Premium_MaxPinsNoPremiumText("\(limit)").string
|
||||
@ -1771,6 +1787,7 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
|
||||
case folders
|
||||
case chatsPerFolder
|
||||
case pins
|
||||
case pinnedSavedPeers
|
||||
case files
|
||||
case accounts
|
||||
case linksPerSharedFolder
|
||||
|
@ -1147,7 +1147,18 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
}
|
||||
}
|
||||
|
||||
presentImpl?(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }))
|
||||
presentImpl?(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
navigateToChatImpl?(peer)
|
||||
})
|
||||
}
|
||||
return false
|
||||
}))
|
||||
})
|
||||
}
|
||||
shareController.actionCompleted = {
|
||||
|
@ -4583,6 +4583,25 @@ public extension Api.functions.messages {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func deleteSavedHistory(flags: Int32, peer: Api.InputPeer, maxId: Int32, minDate: Int32?, maxDate: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.AffectedHistory>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1855459371)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(maxId, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(minDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(maxDate!, buffer: buffer, boxed: false)}
|
||||
return (FunctionDescription(name: "messages.deleteSavedHistory", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("maxId", String(describing: maxId)), ("minDate", String(describing: minDate)), ("maxDate", String(describing: maxDate))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.AffectedHistory? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.messages.AffectedHistory?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.messages.AffectedHistory
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func deleteScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
|
@ -458,33 +458,51 @@ public enum SetForumChannelTopicPinnedError {
|
||||
}
|
||||
|
||||
func _internal_setForumChannelPinnedTopics(account: Account, id: EnginePeer.Id, threadIds: [Int64]) -> Signal<Never, SetForumChannelTopicPinnedError> {
|
||||
return account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
guard let inputChannel = transaction.getPeer(id).flatMap(apiInputChannel) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
transaction.setPeerPinnedThreads(peerId: id, threadIds: threadIds)
|
||||
|
||||
return inputChannel
|
||||
}
|
||||
|> castError(SetForumChannelTopicPinnedError.self)
|
||||
|> mapToSignal { inputChannel -> Signal<Never, SetForumChannelTopicPinnedError> in
|
||||
guard let inputChannel = inputChannel else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.channels.reorderPinnedForumTopics(
|
||||
flags: 1 << 0,
|
||||
channel: inputChannel,
|
||||
order: threadIds.map(Int32.init(clamping:))
|
||||
))
|
||||
|> mapError { _ -> SetForumChannelTopicPinnedError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, SetForumChannelTopicPinnedError> in
|
||||
account.stateManager.addUpdates(result)
|
||||
if id == account.peerId {
|
||||
return account.postbox.transaction { transaction -> [Api.InputDialogPeer] in
|
||||
transaction.setPeerPinnedThreads(peerId: id, threadIds: threadIds)
|
||||
|
||||
return .complete()
|
||||
return threadIds.compactMap { transaction.getPeer(PeerId($0)).flatMap(apiInputPeer).flatMap({ .inputDialogPeer(peer: $0) }) }
|
||||
}
|
||||
|> castError(SetForumChannelTopicPinnedError.self)
|
||||
|> mapToSignal { inputPeers -> Signal<Never, SetForumChannelTopicPinnedError> in
|
||||
return account.network.request(Api.functions.messages.reorderPinnedSavedDialogs(flags: 1 << 0, order: inputPeers))
|
||||
|> mapError { _ -> SetForumChannelTopicPinnedError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { _ -> Signal<Never, SetForumChannelTopicPinnedError> in
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return account.postbox.transaction { transaction -> Api.InputChannel? in
|
||||
guard let inputChannel = transaction.getPeer(id).flatMap(apiInputChannel) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
transaction.setPeerPinnedThreads(peerId: id, threadIds: threadIds)
|
||||
|
||||
return inputChannel
|
||||
}
|
||||
|> castError(SetForumChannelTopicPinnedError.self)
|
||||
|> mapToSignal { inputChannel -> Signal<Never, SetForumChannelTopicPinnedError> in
|
||||
guard let inputChannel = inputChannel else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
|
||||
return account.network.request(Api.functions.channels.reorderPinnedForumTopics(
|
||||
flags: 1 << 0,
|
||||
channel: inputChannel,
|
||||
order: threadIds.map(Int32.init(clamping:))
|
||||
))
|
||||
|> mapError { _ -> SetForumChannelTopicPinnedError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Never, SetForumChannelTopicPinnedError> in
|
||||
account.stateManager.addUpdates(result)
|
||||
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -687,8 +705,12 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post
|
||||
limit: Int32(limit),
|
||||
hash: 0
|
||||
))
|
||||
|> mapError { _ -> LoadMessageHistoryThreadsError in
|
||||
return .generic
|
||||
|> `catch` { error -> Signal<Api.messages.SavedDialogs, LoadMessageHistoryThreadsError> in
|
||||
if error.errorDescription == "SAVED_DIALOGS_UNSUPPORTED" {
|
||||
return .never()
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<LoadMessageHistoryThreadsResult, LoadMessageHistoryThreadsError> in
|
||||
switch result {
|
||||
|
@ -387,7 +387,50 @@ private func requestClearHistory(postbox: Postbox, network: Network, stateManage
|
||||
private func _internal_clearHistory(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, peer: Peer, operation: CloudChatClearHistoryOperation) -> Signal<Void, NoError> {
|
||||
if peer.id.namespace == Namespaces.Peer.CloudGroup || peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||
if let inputPeer = apiInputPeer(peer) {
|
||||
return requestClearHistory(postbox: postbox, network: network, stateManager: stateManager, inputPeer: inputPeer, maxId: operation.topMessageId.id, justClear: true, minTimestamp: operation.minTimestamp, maxTimestamp: operation.maxTimestamp, type: operation.type)
|
||||
if peer.id == stateManager.accountPeerId, let threadId = operation.threadId {
|
||||
guard let inputSubPeer = transaction.getPeer(PeerId(threadId)).flatMap(apiInputPeer) else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
var flags: Int32 = 0
|
||||
var updatedMaxId = operation.topMessageId.id
|
||||
if operation.minTimestamp != nil {
|
||||
flags |= 1 << 2
|
||||
updatedMaxId = 0
|
||||
}
|
||||
if operation.maxTimestamp != nil {
|
||||
flags |= 1 << 3
|
||||
updatedMaxId = 0
|
||||
}
|
||||
let signal = network.request(Api.functions.messages.deleteSavedHistory(flags: flags, peer: inputSubPeer, maxId: updatedMaxId, minDate: operation.minTimestamp, maxDate: operation.maxTimestamp))
|
||||
|> map { result -> Api.messages.AffectedHistory? in
|
||||
return result
|
||||
}
|
||||
|> `catch` { _ -> Signal<Api.messages.AffectedHistory?, Bool> in
|
||||
return .fail(true)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<Void, Bool> in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .affectedHistory(pts, ptsCount, offset):
|
||||
stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)])
|
||||
if offset == 0 {
|
||||
return .fail(true)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .fail(true)
|
||||
}
|
||||
}
|
||||
return (signal |> restart)
|
||||
|> `catch` { _ -> Signal<Void, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return requestClearHistory(postbox: postbox, network: network, stateManager: stateManager, inputPeer: inputPeer, maxId: operation.topMessageId.id, justClear: true, minTimestamp: operation.minTimestamp, maxTimestamp: operation.maxTimestamp, type: operation.type)
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
|
@ -2,34 +2,36 @@ import Postbox
|
||||
import SwiftSignalKit
|
||||
|
||||
public struct UserLimitsConfiguration: Equatable {
|
||||
public let maxPinnedChatCount: Int32
|
||||
public let maxArchivedPinnedChatCount: Int32
|
||||
public let maxChannelsCount: Int32
|
||||
public let maxPublicLinksCount: Int32
|
||||
public let maxSavedGifCount: Int32
|
||||
public let maxFavedStickerCount: Int32
|
||||
public let maxFoldersCount: Int32
|
||||
public let maxFolderChatsCount: Int32
|
||||
public let maxCaptionLength: Int32
|
||||
public let maxUploadFileParts: Int32
|
||||
public let maxAboutLength: Int32
|
||||
public let maxAnimatedEmojisInText: Int32
|
||||
public let maxReactionsPerMessage: Int32
|
||||
public let maxSharedFolderInviteLinks: Int32
|
||||
public let maxSharedFolderJoin: Int32
|
||||
public let maxStoryCaptionLength: Int32
|
||||
public let maxExpiringStoriesCount: Int32
|
||||
public let maxStoriesWeeklyCount: Int32
|
||||
public let maxStoriesMonthlyCount: Int32
|
||||
public let maxStoriesSuggestedReactions: Int32
|
||||
public let maxGiveawayChannelsCount: Int32
|
||||
public let maxGiveawayCountriesCount: Int32
|
||||
public let maxGiveawayPeriodSeconds: Int32
|
||||
public let maxChannelRecommendationsCount: Int32
|
||||
public var maxPinnedChatCount: Int32
|
||||
public var maxPinnedSavedChatCount: Int32
|
||||
public var maxArchivedPinnedChatCount: Int32
|
||||
public var maxChannelsCount: Int32
|
||||
public var maxPublicLinksCount: Int32
|
||||
public var maxSavedGifCount: Int32
|
||||
public var maxFavedStickerCount: Int32
|
||||
public var maxFoldersCount: Int32
|
||||
public var maxFolderChatsCount: Int32
|
||||
public var maxCaptionLength: Int32
|
||||
public var maxUploadFileParts: Int32
|
||||
public var maxAboutLength: Int32
|
||||
public var maxAnimatedEmojisInText: Int32
|
||||
public var maxReactionsPerMessage: Int32
|
||||
public var maxSharedFolderInviteLinks: Int32
|
||||
public var maxSharedFolderJoin: Int32
|
||||
public var maxStoryCaptionLength: Int32
|
||||
public var maxExpiringStoriesCount: Int32
|
||||
public var maxStoriesWeeklyCount: Int32
|
||||
public var maxStoriesMonthlyCount: Int32
|
||||
public var maxStoriesSuggestedReactions: Int32
|
||||
public var maxGiveawayChannelsCount: Int32
|
||||
public var maxGiveawayCountriesCount: Int32
|
||||
public var maxGiveawayPeriodSeconds: Int32
|
||||
public var maxChannelRecommendationsCount: Int32
|
||||
|
||||
public static var defaultValue: UserLimitsConfiguration {
|
||||
return UserLimitsConfiguration(
|
||||
maxPinnedChatCount: 5,
|
||||
maxPinnedSavedChatCount: 5,
|
||||
maxArchivedPinnedChatCount: 100,
|
||||
maxChannelsCount: 500,
|
||||
maxPublicLinksCount: 10,
|
||||
@ -58,6 +60,7 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
|
||||
public init(
|
||||
maxPinnedChatCount: Int32,
|
||||
maxPinnedSavedChatCount: Int32,
|
||||
maxArchivedPinnedChatCount: Int32,
|
||||
maxChannelsCount: Int32,
|
||||
maxPublicLinksCount: Int32,
|
||||
@ -83,6 +86,7 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
maxChannelRecommendationsCount: Int32
|
||||
) {
|
||||
self.maxPinnedChatCount = maxPinnedChatCount
|
||||
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
|
||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||
self.maxChannelsCount = maxChannelsCount
|
||||
self.maxPublicLinksCount = maxPublicLinksCount
|
||||
@ -112,7 +116,10 @@ public struct UserLimitsConfiguration: Equatable {
|
||||
extension UserLimitsConfiguration {
|
||||
init(appConfiguration: AppConfiguration, isPremium: Bool) {
|
||||
let keySuffix = isPremium ? "_premium" : "_default"
|
||||
let defaultValue = UserLimitsConfiguration.defaultValue
|
||||
var defaultValue = UserLimitsConfiguration.defaultValue
|
||||
if isPremium {
|
||||
defaultValue.maxPinnedSavedChatCount = 100
|
||||
}
|
||||
|
||||
func getValue(_ key: String, orElse defaultValue: Int32) -> Int32 {
|
||||
if let value = appConfiguration.data?[key + keySuffix] as? Double {
|
||||
@ -131,6 +138,7 @@ extension UserLimitsConfiguration {
|
||||
}
|
||||
|
||||
self.maxPinnedChatCount = getValue("dialogs_pinned_limit", orElse: defaultValue.maxPinnedChatCount)
|
||||
self.maxPinnedSavedChatCount = getValue("saved_pinned_limit", orElse: defaultValue.maxPinnedSavedChatCount)
|
||||
self.maxArchivedPinnedChatCount = getValue("dialogs_folder_pinned_limit", orElse: defaultValue.maxArchivedPinnedChatCount)
|
||||
self.maxChannelsCount = getValue("channels_limit", orElse: defaultValue.maxChannelsCount)
|
||||
self.maxPublicLinksCount = getValue("channels_public_limit", orElse: defaultValue.maxPublicLinksCount)
|
||||
|
@ -37,6 +37,7 @@ public enum EngineConfiguration {
|
||||
|
||||
public struct UserLimits: Equatable {
|
||||
public let maxPinnedChatCount: Int32
|
||||
public let maxPinnedSavedChatCount: Int32
|
||||
public let maxArchivedPinnedChatCount: Int32
|
||||
public let maxChannelsCount: Int32
|
||||
public let maxPublicLinksCount: Int32
|
||||
@ -67,6 +68,7 @@ public enum EngineConfiguration {
|
||||
|
||||
public init(
|
||||
maxPinnedChatCount: Int32,
|
||||
maxPinnedSavedChatCount: Int32,
|
||||
maxArchivedPinnedChatCount: Int32,
|
||||
maxChannelsCount: Int32,
|
||||
maxPublicLinksCount: Int32,
|
||||
@ -92,6 +94,7 @@ public enum EngineConfiguration {
|
||||
maxChannelRecommendationsCount: Int32
|
||||
) {
|
||||
self.maxPinnedChatCount = maxPinnedChatCount
|
||||
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
|
||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||
self.maxChannelsCount = maxChannelsCount
|
||||
self.maxPublicLinksCount = maxPublicLinksCount
|
||||
@ -153,6 +156,7 @@ public extension EngineConfiguration.UserLimits {
|
||||
init(_ userLimitsConfiguration: UserLimitsConfiguration) {
|
||||
self.init(
|
||||
maxPinnedChatCount: userLimitsConfiguration.maxPinnedChatCount,
|
||||
maxPinnedSavedChatCount: userLimitsConfiguration.maxPinnedSavedChatCount,
|
||||
maxArchivedPinnedChatCount: userLimitsConfiguration.maxArchivedPinnedChatCount,
|
||||
maxChannelsCount: userLimitsConfiguration.maxChannelsCount,
|
||||
maxPublicLinksCount: userLimitsConfiguration.maxPublicLinksCount,
|
||||
|
@ -1153,5 +1153,6 @@ public extension TelegramEngine.EngineData.Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +148,18 @@ func _internal_clearHistoryInteractively(postbox: Postbox, peerId: PeerId, threa
|
||||
}
|
||||
if let topIndex = topIndex {
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
let _ = transaction.addMessages([StoreMessage(id: topIndex.id, globallyUniqueId: nil, groupingKey: nil, threadId: nil, timestamp: topIndex.timestamp, flags: StoreMessageFlags(), tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [TelegramMediaAction(action: .historyCleared)])], location: .Random)
|
||||
var addEmptyMessage = false
|
||||
if threadId == nil {
|
||||
addEmptyMessage = true
|
||||
} else {
|
||||
if transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.Cloud) == nil {
|
||||
addEmptyMessage = true
|
||||
}
|
||||
}
|
||||
|
||||
if addEmptyMessage {
|
||||
let _ = transaction.addMessages([StoreMessage(id: topIndex.id, globallyUniqueId: nil, groupingKey: nil, threadId: nil, timestamp: topIndex.timestamp, flags: StoreMessageFlags(), tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: nil, text: "", attributes: [], media: [TelegramMediaAction(action: .historyCleared)])], location: .Random)
|
||||
}
|
||||
} else {
|
||||
updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: peerId, minTimestamp: topIndex.timestamp, forceRootGroupIfNotExists: false)
|
||||
}
|
||||
|
@ -1314,6 +1314,21 @@ public extension TelegramEngine {
|
||||
return self.account.stateManager.synchronouslyIsMessageDeletedInteractively(ids: ids)
|
||||
}
|
||||
|
||||
public func savedMessagesPeerListHead() -> Signal<EnginePeer.Id?, NoError> {
|
||||
return self.account.postbox.combinedView(keys: [.savedMessagesIndex(peerId: self.account.peerId)])
|
||||
|> map { views -> EnginePeer.Id? in
|
||||
//TODO:api optimize
|
||||
guard let view = views.views[.savedMessagesIndex(peerId: self.account.peerId)] as? MessageHistorySavedMessagesIndexView else {
|
||||
return nil
|
||||
}
|
||||
if view.isLoading {
|
||||
return nil
|
||||
} else {
|
||||
return view.items.first?.peer?.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func savedMessagesPeersStats() -> Signal<Int?, NoError> {
|
||||
return self.account.postbox.combinedView(keys: [.savedMessagesStats(peerId: self.account.peerId)])
|
||||
|> map { views -> Int? in
|
||||
|
@ -1063,13 +1063,23 @@ public extension TelegramEngine {
|
||||
|
||||
public func toggleForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64) -> Signal<Never, SetForumChannelTopicPinnedError> {
|
||||
return self.account.postbox.transaction { transaction -> ([Int64], Int) in
|
||||
var limit = 5
|
||||
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||
if let data = appConfiguration.data, let value = data["topics_pinned_limit"] as? Double {
|
||||
limit = Int(value)
|
||||
if id == self.account.peerId {
|
||||
var limit = 5
|
||||
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||
if let data = appConfiguration.data, let value = data["saved_pinned_limit"] as? Double {
|
||||
limit = Int(value)
|
||||
}
|
||||
|
||||
return (transaction.getPeerPinnedThreads(peerId: id), limit)
|
||||
} else {
|
||||
var limit = 5
|
||||
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||
if let data = appConfiguration.data, let value = data["topics_pinned_limit"] as? Double {
|
||||
limit = Int(value)
|
||||
}
|
||||
|
||||
return (transaction.getPeerPinnedThreads(peerId: id), limit)
|
||||
}
|
||||
|
||||
return (transaction.getPeerPinnedThreads(peerId: id), limit)
|
||||
}
|
||||
|> castError(SetForumChannelTopicPinnedError.self)
|
||||
|> mapToSignal { threadIds, limit -> Signal<Never, SetForumChannelTopicPinnedError> in
|
||||
|
@ -188,6 +188,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
||||
case dismissedPremiumWallpapersBadge = 54
|
||||
case dismissedPremiumColorsBadge = 55
|
||||
case multipleReactionsSuggestion = 56
|
||||
case savedMessagesChatsSuggestion = 57
|
||||
|
||||
var key: ValueBoxKey {
|
||||
let v = ValueBoxKey(length: 4)
|
||||
@ -465,6 +466,9 @@ private struct ApplicationSpecificNoticeKeys {
|
||||
static func multipleReactionsSuggestion() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.multipleReactionsSuggestion.key)
|
||||
}
|
||||
static func savedMessagesChatsSuggestion() -> NoticeEntryKey {
|
||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessagesChatsSuggestion.key)
|
||||
}
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificNotice {
|
||||
@ -1852,4 +1856,31 @@ public struct ApplicationSpecificNotice {
|
||||
return Int(previousValue)
|
||||
}
|
||||
}
|
||||
|
||||
public static func getSavedMessagesChatsSuggestion(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
|
||||
return accountManager.transaction { transaction -> Int32 in
|
||||
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.savedMessagesChatsSuggestion())?.get(ApplicationSpecificCounterNotice.self) {
|
||||
return value.value
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func incrementSavedMessagesChatsSuggestion(accountManager: AccountManager<TelegramAccountManagerTypes>, count: Int = 1) -> Signal<Int, NoError> {
|
||||
return accountManager.transaction { transaction -> Int in
|
||||
var currentValue: Int32 = 0
|
||||
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.savedMessagesChatsSuggestion())?.get(ApplicationSpecificCounterNotice.self) {
|
||||
currentValue = value.value
|
||||
}
|
||||
let previousValue = currentValue
|
||||
currentValue += Int32(count)
|
||||
|
||||
if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) {
|
||||
transaction.setNotice(ApplicationSpecificNoticeKeys.savedMessagesChatsSuggestion(), entry)
|
||||
}
|
||||
|
||||
return Int(previousValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -213,6 +213,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
case chatInputSearchPanelMembersImage
|
||||
|
||||
case chatHistoryNavigationButtonImage
|
||||
case chatHistoryNavigationUpButtonImage
|
||||
case chatHistoryMentionsButtonImage
|
||||
case chatHistoryReactionsButtonImage
|
||||
case chatHistoryNavigationButtonBadgeImage
|
||||
|
@ -606,6 +606,28 @@ public struct PresentationResourcesChat {
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatHistoryNavigationUpButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatHistoryNavigationUpButtonImage.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setLineWidth(0.5)
|
||||
context.setStrokeColor(theme.chat.historyNavigation.strokeColor.cgColor)
|
||||
context.strokeEllipse(in: CGRect(origin: CGPoint(x: 0.25, y: 0.25), size: CGSize(width: size.width - 0.5, height: size.height - 0.5)))
|
||||
context.setStrokeColor(theme.chat.historyNavigation.foregroundColor.cgColor)
|
||||
context.setLineWidth(1.5)
|
||||
|
||||
context.translateBy(x: size.width * 0.5, y: size.height * 0.5)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -size.width * 0.5, y: -size.height * 0.5)
|
||||
let position = CGPoint(x: 9.0 - 0.5, y: 24.0)
|
||||
context.move(to: CGPoint(x: position.x + 1.0, y: position.y - 1.0))
|
||||
context.addLine(to: CGPoint(x: position.x + 10.0, y: position.y - 10.0))
|
||||
context.addLine(to: CGPoint(x: position.x + 19.0, y: position.y - 1.0))
|
||||
context.strokePath()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public static func chatHistoryMentionsButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.chatHistoryMentionsButtonImage.rawValue, { theme in
|
||||
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context in
|
||||
|
@ -141,7 +141,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
private var wasPending: Bool = false
|
||||
private var didChangeFromPendingToSent: Bool = false
|
||||
|
||||
required public init() {
|
||||
required public init(rotated: Bool) {
|
||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.imageNode = TransformImageNode()
|
||||
@ -156,7 +156,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
||||
self.textNode.textNode.displaysAsynchronously = false
|
||||
self.textNode.textNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init(layerBacked: false)
|
||||
super.init(rotated: rotated)
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] location in
|
||||
guard let strongSelf = self else {
|
||||
|
@ -636,7 +636,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
}
|
||||
|
||||
required public init() {
|
||||
required public init(rotated: Bool) {
|
||||
self.mainContextSourceNode = ContextExtractedContentContainingNode()
|
||||
self.mainContainerNode = ContextControllerSourceNode()
|
||||
self.backgroundWallpaperNode = ChatMessageBubbleBackdrop()
|
||||
@ -654,7 +654,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
//self.debugNode = ASDisplayNode()
|
||||
//self.debugNode.backgroundColor = .blue
|
||||
|
||||
super.init(layerBacked: false)
|
||||
super.init(rotated: rotated)
|
||||
|
||||
//self.addSubnode(self.debugNode)
|
||||
|
||||
|
@ -89,13 +89,13 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
|
||||
|
||||
fileprivate var wasPlaying = false
|
||||
|
||||
required public init() {
|
||||
required public init(rotated: Bool) {
|
||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.interactiveVideoNode = ChatMessageInteractiveInstantVideoNode()
|
||||
self.messageAccessibilityArea = AccessibilityAreaNode()
|
||||
|
||||
super.init(layerBacked: false)
|
||||
super.init(rotated: rotated)
|
||||
|
||||
self.interactiveVideoNode.shouldOpen = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
|
@ -122,7 +122,7 @@ public protocol ChatMessageItem: ListViewItem {
|
||||
var sending: Bool { get }
|
||||
var failed: Bool { get }
|
||||
|
||||
func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: Bool)
|
||||
func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?, isRotated: Bool) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: Bool)
|
||||
}
|
||||
|
||||
public func hasCommentButton(item: ChatMessageItem) -> Bool {
|
||||
|
@ -47,9 +47,13 @@ public final class ChatMessageDateHeader: ListViewItemHeader {
|
||||
self.action = action
|
||||
self.roundedTimestamp = dateHeaderTimestampId(timestamp: timestamp)
|
||||
self.id = ListViewItemNode.HeaderId(space: 0, id: Int64(self.roundedTimestamp))
|
||||
|
||||
let isRotated = controllerInteraction?.chatIsRotated ?? true
|
||||
|
||||
self.stickDirection = isRotated ? .bottom : .top
|
||||
}
|
||||
|
||||
public let stickDirection: ListViewItemHeaderStickDirection = .bottom
|
||||
public let stickDirection: ListViewItemHeaderStickDirection
|
||||
public let stickOverInsets: Bool = true
|
||||
|
||||
public let height: CGFloat = 34.0
|
||||
@ -191,9 +195,13 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
||||
}
|
||||
self.text = text
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: true, isRotated: true, seeThrough: false)
|
||||
let isRotated = controllerInteraction?.chatIsRotated ?? true
|
||||
|
||||
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
super.init(layerBacked: false, dynamicBounce: true, isRotated: isRotated, seeThrough: false)
|
||||
|
||||
if isRotated {
|
||||
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
}
|
||||
|
||||
let graphics = PresentationResourcesChat.principalGraphics(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners)
|
||||
|
||||
@ -398,9 +406,13 @@ public final class ChatMessageAvatarHeader: ListViewItemHeader {
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.id = ListViewItemNode.HeaderId(space: 1, id: Id(peerId: peerId, timestampId: dateHeaderTimestampId(timestamp: timestamp)))
|
||||
self.storyStats = storyStats
|
||||
|
||||
let isRotated = controllerInteraction?.chatIsRotated ?? true
|
||||
|
||||
self.stickDirection = isRotated ? .top : .bottom
|
||||
}
|
||||
|
||||
public let stickDirection: ListViewItemHeaderStickDirection = .top
|
||||
public let stickDirection: ListViewItemHeaderStickDirection
|
||||
public let stickOverInsets: Bool = false
|
||||
|
||||
public let height: CGFloat = 38.0
|
||||
@ -484,9 +496,13 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
|
||||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.contentNode.displaysAsynchronously = !presentationData.isPreview
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: true, isRotated: true, seeThrough: false)
|
||||
let isRotated = controllerInteraction?.chatIsRotated ?? true
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: true, isRotated: isRotated, seeThrough: false)
|
||||
|
||||
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
if isRotated {
|
||||
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
}
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
self.containerNode.addSubnode(self.avatarNode)
|
||||
|
@ -259,7 +259,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
self.associatedData = associatedData
|
||||
self.controllerInteraction = controllerInteraction
|
||||
self.content = content
|
||||
self.disableDate = disableDate
|
||||
self.disableDate = disableDate || !controllerInteraction.chatIsRotated
|
||||
self.additionalContent = additionalContent
|
||||
|
||||
var avatarHeader: ChatMessageAvatarHeader?
|
||||
@ -369,6 +369,9 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
if case .messageOptions = associatedData.subject {
|
||||
headers = []
|
||||
}
|
||||
if !controllerInteraction.chatIsRotated {
|
||||
headers = []
|
||||
}
|
||||
if let avatarHeader = self.avatarHeader {
|
||||
headers.append(avatarHeader)
|
||||
}
|
||||
@ -450,11 +453,11 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
}
|
||||
|
||||
let configure = {
|
||||
let node = (viewClassName as! ChatMessageItemView.Type).init()
|
||||
let node = (viewClassName as! ChatMessageItemView.Type).init(rotated: self.controllerInteraction.chatIsRotated)
|
||||
node.setupItem(self, synchronousLoad: synchronousLoads)
|
||||
|
||||
let nodeLayout = node.asyncLayout()
|
||||
let (top, bottom, dateAtBottom) = self.mergedWithItems(top: previousItem, bottom: nextItem)
|
||||
let (top, bottom, dateAtBottom) = self.mergedWithItems(top: previousItem, bottom: nextItem, isRotated: self.controllerInteraction.chatIsRotated)
|
||||
|
||||
var disableDate = self.disableDate
|
||||
if let subject = self.associatedData.subject, case let .messageOptions(_, _, info) = subject {
|
||||
@ -490,7 +493,15 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
}
|
||||
}
|
||||
|
||||
public func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: Bool) {
|
||||
public func mergedWithItems(top: ListViewItem?, bottom: ListViewItem?, isRotated: Bool) -> (top: ChatMessageMerge, bottom: ChatMessageMerge, dateAtBottom: Bool) {
|
||||
var top = top
|
||||
var bottom = bottom
|
||||
if !isRotated {
|
||||
let previousTop = top
|
||||
top = bottom
|
||||
bottom = previousTop
|
||||
}
|
||||
|
||||
var mergedTop: ChatMessageMerge = .none
|
||||
var mergedBottom: ChatMessageMerge = .none
|
||||
var dateAtBottom = false
|
||||
@ -530,8 +541,10 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
|
||||
let nodeLayout = nodeValue.asyncLayout()
|
||||
|
||||
let isRotated = self.controllerInteraction.chatIsRotated
|
||||
|
||||
async {
|
||||
let (top, bottom, dateAtBottom) = self.mergedWithItems(top: previousItem, bottom: nextItem)
|
||||
let (top, bottom, dateAtBottom) = self.mergedWithItems(top: previousItem, bottom: nextItem, isRotated: isRotated)
|
||||
|
||||
var disableDate = self.disableDate
|
||||
if let subject = self.associatedData.subject, case let .messageOptions(_, _, info) = subject {
|
||||
|
@ -653,13 +653,11 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
||||
|
||||
open var awaitingAppliedReaction: (MessageReaction.Reaction?, () -> Void)?
|
||||
|
||||
public required convenience init() {
|
||||
self.init(layerBacked: false)
|
||||
}
|
||||
|
||||
public init(layerBacked: Bool) {
|
||||
super.init(layerBacked: layerBacked, dynamicBounce: true, rotated: true)
|
||||
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
public required init(rotated: Bool) {
|
||||
super.init(layerBacked: false, dynamicBounce: true, rotated: rotated)
|
||||
if rotated {
|
||||
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
@ -684,7 +682,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol {
|
||||
override open func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
||||
if let item = item as? ChatMessageItem {
|
||||
let doLayout = self.asyncLayout()
|
||||
let merged = item.mergedWithItems(top: previousItem, bottom: nextItem)
|
||||
let merged = item.mergedWithItems(top: previousItem, bottom: nextItem, isRotated: item.controllerInteraction.chatIsRotated)
|
||||
let (layout, apply) = doLayout(item, params, merged.top, merged.bottom, merged.dateAtBottom)
|
||||
self.contentSize = layout.contentSize
|
||||
self.insets = layout.insets
|
||||
|
@ -95,7 +95,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
}
|
||||
}
|
||||
|
||||
required public init() {
|
||||
required public init(rotated: Bool) {
|
||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.imageNode = TransformImageNode()
|
||||
@ -104,7 +104,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||
self.messageAccessibilityArea = AccessibilityAreaNode()
|
||||
|
||||
super.init(layerBacked: false)
|
||||
super.init(rotated: rotated)
|
||||
|
||||
var firstTime = true
|
||||
self.imageNode.imageUpdated = { [weak self] image in
|
||||
|
@ -259,6 +259,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
||||
public var playNextOutgoingGift: Bool = false
|
||||
public var recommendedChannelsOpenUp: Bool = false
|
||||
public var enableFullTranslucency: Bool = true
|
||||
public var chatIsRotated: Bool = true
|
||||
|
||||
public init(
|
||||
openMessage: @escaping (Message, OpenMessageParams) -> Bool,
|
||||
|
@ -22,6 +22,8 @@ swift_library(
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/ChatListUI",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode",
|
||||
"//submodules/DeleteChatPeerActionSheetItem",
|
||||
"//submodules/UndoUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -14,6 +14,8 @@ import TelegramUIPreferences
|
||||
import AppBundle
|
||||
import PeerInfoPaneNode
|
||||
import ChatListUI
|
||||
import DeleteChatPeerActionSheetItem
|
||||
import UndoUI
|
||||
|
||||
public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||
private let context: AccountContext
|
||||
@ -174,6 +176,113 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI
|
||||
self.layoutEmptyShimmerEffectNode(node: emptyShimmerEffectNode, size: currentParams.size, insets: UIEdgeInsets(top: currentParams.topInset, left: currentParams.sideInset, bottom: currentParams.bottomInset, right: currentParams.sideInset), verticalOffset: offset + self.shimmerNodeOffset, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
self.chatListNode.push = { [weak self] c in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.parentController?.push(c)
|
||||
}
|
||||
|
||||
self.chatListNode.present = { [weak self] c in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.parentController?.present(c, in: .window(.root))
|
||||
}
|
||||
|
||||
self.chatListNode.deletePeerChat = { [weak self] peerId, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
|
||||
self.view.window?.endEditing(true)
|
||||
|
||||
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(DeleteChatPeerActionSheetItem(context: self.context, peer: peer, chatPeer: peer, action: .deleteSavedPeer, strings: self.presentationData.strings, nameDisplayOrder: self.presentationData.nameDisplayOrder, balancedLayout: true))
|
||||
items.append(ActionSheetButtonItem(title: self.presentationData.strings.Common_Delete, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.chatListNode.updateState({ state in
|
||||
var state = state
|
||||
state.pendingRemovalItemIds.insert(ChatListNodeState.ItemId(peerId: peer.id, threadId: nil))
|
||||
return state
|
||||
})
|
||||
self.parentController?.forEachController({ controller in
|
||||
if let controller = controller as? UndoOverlayController {
|
||||
controller.dismissWithCommitActionAndReplacementAnimation()
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
//TODO:localize
|
||||
self.parentController?.present(UndoOverlayController(presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, content: .removedChat(title: "Saved messages deleted.", text: nil), elevatedLayout: false, animateInAsReplacement: true, action: { [weak self] value in
|
||||
guard let self else {
|
||||
return false
|
||||
}
|
||||
if value == .commit {
|
||||
let _ = self.context.engine.messages.clearHistoryInteractively(peerId: self.context.account.peerId, threadId: peer.id.toInt64(), type: .forLocalPeer).startStandalone(completed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.chatListNode.updateState({ state in
|
||||
var state = state
|
||||
state.pendingRemovalItemIds.remove(ChatListNodeState.ItemId(peerId: peer.id, threadId: nil))
|
||||
return state
|
||||
})
|
||||
})
|
||||
return true
|
||||
} else if value == .undo {
|
||||
self.chatListNode.updateState({ state in
|
||||
var state = state
|
||||
state.pendingRemovalItemIds.remove(ChatListNodeState.ItemId(peerId: peer.id, threadId: nil))
|
||||
return state
|
||||
})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
}))
|
||||
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items),
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
})
|
||||
])
|
||||
])
|
||||
self.parentController?.present(actionSheet, in: .window(.root))
|
||||
})
|
||||
}
|
||||
|
||||
self.chatListNode.activateChatPreview = { [weak self] item, _, node, gesture, location in
|
||||
guard let self, let parentController = self.parentController else {
|
||||
gesture?.cancel()
|
||||
return
|
||||
}
|
||||
|
||||
if case let .peer(peerData) = item.content {
|
||||
let threadId = peerData.peer.peerId.toInt64()
|
||||
let chatController = self.context.sharedContext.makeChatController(context: self.context, chatLocation: .replyThread(message: ChatReplyThreadMessage(
|
||||
peerId: self.context.account.peerId, threadId: threadId, channelMessageId: nil, isChannelPost: false, isForumPost: false, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false
|
||||
)), subject: nil, botStart: nil, mode: .standard(.previewing))
|
||||
chatController.canReadHistory.set(false)
|
||||
let source: ContextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: parentController.navigationController as? NavigationController))
|
||||
|
||||
let contextController = ContextController(presentationData: self.presentationData, source: source, items: savedMessagesPeerMenuItems(context: self.context, threadId: threadId, parentController: parentController) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
parentController.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -268,3 +377,32 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
let controller: ViewController
|
||||
weak var sourceNode: ASDisplayNode?
|
||||
|
||||
let navigationController: NavigationController?
|
||||
|
||||
let passthroughTouches: Bool = true
|
||||
|
||||
init(controller: ViewController, sourceNode: ASDisplayNode?, navigationController: NavigationController?) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
self.navigationController = navigationController
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
||||
let sourceNode = self.sourceNode
|
||||
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
|
||||
if let sourceNode = sourceNode {
|
||||
return (sourceNode.view, sourceNode.bounds)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func animatedIn() {
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
self.navigationController = navigationController
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.chatController = context.sharedContext.makeChatController(context: context, chatLocation: .replyThread(message: ChatReplyThreadMessage(peerId: context.account.peerId, threadId: peerId.toInt64(), channelMessageId: nil, isChannelPost: false, isForumPost: false, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)), subject: nil, botStart: nil, mode: .standard(.embedded))
|
||||
self.chatController = context.sharedContext.makeChatController(context: context, chatLocation: .replyThread(message: ChatReplyThreadMessage(peerId: context.account.peerId, threadId: peerId.toInt64(), channelMessageId: nil, isChannelPost: false, isForumPost: false, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)), subject: nil, botStart: nil, mode: .standard(.embedded(invertDirection: true)))
|
||||
|
||||
super.init()
|
||||
|
||||
@ -105,6 +105,9 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
}
|
||||
|
||||
public func transferVelocity(_ velocity: CGFloat) {
|
||||
if velocity > 0.0 {
|
||||
self.chatController.transferScrollingVelocity(velocity)
|
||||
}
|
||||
}
|
||||
|
||||
public func cancelPreviewGestures() {
|
||||
@ -142,9 +145,10 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
||||
self.currentParams = (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData)
|
||||
let chatFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: size.height - topInset))
|
||||
|
||||
let combinedBottomInset = max(0.0, size.height - visibleHeight) + bottomInset
|
||||
let combinedBottomInset = bottomInset
|
||||
transition.updateFrame(node: self.chatController.displayNode, frame: chatFrame)
|
||||
self.chatController.containerLayoutUpdated(ContainerViewLayout(size: chatFrame.size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), safeInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition)
|
||||
self.chatController.updateIsScrollingLockedAtTop(isScrollingLockedAtTop: isScrollingLockedAtTop)
|
||||
self.chatController.containerLayoutUpdated(ContainerViewLayout(size: chatFrame.size, metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact, orientation: nil), deviceMetrics: deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), safeInsets: UIEdgeInsets(top: 4.0, left: sideInset, bottom: combinedBottomInset, right: sideInset), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition)
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
|
@ -287,7 +287,7 @@ private enum PeerInfoScreenInputData: Equatable {
|
||||
|
||||
public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<Bool, NoError> {
|
||||
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
||||
return peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: .peer(id: peerId), chatLocationContextHolder: chatLocationContextHolder)
|
||||
let mediaPanes = peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: .peer(id: peerId), chatLocationContextHolder: chatLocationContextHolder)
|
||||
|> map { panes -> Bool in
|
||||
if let panes {
|
||||
return !panes.isEmpty
|
||||
@ -295,6 +295,22 @@ public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: Peer
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let hasSavedMessagesChats: Signal<Bool, NoError>
|
||||
if peerId == context.account.peerId {
|
||||
hasSavedMessagesChats = context.engine.messages.savedMessagesPeerListHead()
|
||||
|> map { headPeerId -> Bool in
|
||||
return headPeerId != nil
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
hasSavedMessagesChats = .single(false)
|
||||
}
|
||||
|
||||
return combineLatest(queue: .mainQueue(), [mediaPanes, hasSavedMessagesChats])
|
||||
|> map { values in
|
||||
return values.contains(true)
|
||||
}
|
||||
}
|
||||
|
||||
private func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<[PeerInfoPaneKey]?, NoError> {
|
||||
@ -807,6 +823,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
|
||||
let hasSavedMessages: Signal<Bool, NoError>
|
||||
let hasSavedMessagesChats: Signal<Bool, NoError>
|
||||
if case .peer = chatLocation {
|
||||
hasSavedMessages = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: context.account.peerId, threadId: peerId.toInt64(), tag: MessageTags()))
|
||||
|> map { count -> Bool in
|
||||
@ -817,8 +834,15 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
hasSavedMessagesChats = context.engine.messages.savedMessagesPeerListHead()
|
||||
|> map { headPeerId -> Bool in
|
||||
return headPeerId != nil
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
hasSavedMessages = .single(false)
|
||||
hasSavedMessagesChats = .single(false)
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
@ -830,9 +854,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
hasStories,
|
||||
accountIsPremium,
|
||||
savedMessagesPeer,
|
||||
hasSavedMessagesChats,
|
||||
hasSavedMessages
|
||||
)
|
||||
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, accountIsPremium, savedMessagesPeer, hasSavedMessages -> PeerInfoScreenData in
|
||||
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages -> PeerInfoScreenData in
|
||||
var availablePanes = availablePanes
|
||||
|
||||
if let hasStories {
|
||||
@ -848,7 +873,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
|
||||
if case .peer = chatLocation {
|
||||
if peerId == context.account.peerId {
|
||||
availablePanes?.insert(.savedMessagesChats, at: 0)
|
||||
if hasSavedMessagesChats {
|
||||
availablePanes?.insert(.savedMessagesChats, at: 0)
|
||||
}
|
||||
} else if hasSavedMessages {
|
||||
if var availablePanesValue = availablePanes {
|
||||
if let index = availablePanesValue.firstIndex(of: .media) {
|
||||
@ -928,6 +955,21 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
let hasSavedMessages: Signal<Bool, NoError>
|
||||
if case .peer = chatLocation {
|
||||
hasSavedMessages = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: context.account.peerId, threadId: peerId.toInt64(), tag: MessageTags()))
|
||||
|> map { count -> Bool in
|
||||
if let count, count != 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
hasSavedMessages = .single(false)
|
||||
}
|
||||
|
||||
return combineLatest(
|
||||
context.account.viewTracker.peerView(peerId, updateData: true),
|
||||
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder),
|
||||
@ -939,9 +981,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
requestsStatePromise.get(),
|
||||
hasStories,
|
||||
accountIsPremium,
|
||||
context.engine.peers.recommendedChannels(peerId: peerId)
|
||||
context.engine.peers.recommendedChannels(peerId: peerId),
|
||||
hasSavedMessages
|
||||
)
|
||||
|> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels -> PeerInfoScreenData in
|
||||
|> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages -> PeerInfoScreenData in
|
||||
var availablePanes = availablePanes
|
||||
if let hasStories {
|
||||
if hasStories {
|
||||
@ -952,7 +995,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
|
||||
if case .peer = chatLocation {
|
||||
if var availablePanesValue = availablePanes {
|
||||
if hasSavedMessages, var availablePanesValue = availablePanes {
|
||||
if let index = availablePanesValue.firstIndex(of: .media) {
|
||||
availablePanesValue.insert(.savedMessages, at: index + 1)
|
||||
} else {
|
||||
@ -1138,6 +1181,21 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
let hasSavedMessages: Signal<Bool, NoError>
|
||||
if case .peer = chatLocation {
|
||||
hasSavedMessages = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: context.account.peerId, threadId: peerId.toInt64(), tag: MessageTags()))
|
||||
|> map { count -> Bool in
|
||||
if let count, count != 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
hasSavedMessages = .single(false)
|
||||
}
|
||||
|
||||
return combineLatest(queue: .mainQueue(),
|
||||
context.account.viewTracker.peerView(groupId, updateData: true),
|
||||
peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder),
|
||||
@ -1150,9 +1208,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
requestsStatePromise.get(),
|
||||
threadData,
|
||||
context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]),
|
||||
accountIsPremium
|
||||
accountIsPremium,
|
||||
hasSavedMessages
|
||||
)
|
||||
|> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView, accountIsPremium -> Signal<PeerInfoScreenData, NoError> in
|
||||
|> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView, accountIsPremium, hasSavedMessages -> Signal<PeerInfoScreenData, NoError> in
|
||||
var discussionPeer: Peer?
|
||||
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
|
||||
discussionPeer = peer
|
||||
@ -1168,7 +1227,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
}
|
||||
|
||||
if case .peer = chatLocation {
|
||||
if var availablePanesValue = availablePanes {
|
||||
if hasSavedMessages, var availablePanesValue = availablePanes {
|
||||
if let index = availablePanesValue.firstIndex(of: .media) {
|
||||
availablePanesValue.insert(.savedMessages, at: index + 1)
|
||||
} else {
|
||||
|
@ -581,6 +581,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
||||
if strongSelf.tabsContainerNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.tabsContainerNode.view)) {
|
||||
return []
|
||||
}
|
||||
if case .savedMessagesChats = currentPaneKey {
|
||||
if index == 0 {
|
||||
return .leftCenter
|
||||
}
|
||||
return [.leftCenter, .rightCenter]
|
||||
}
|
||||
if index == 0 {
|
||||
return .left
|
||||
}
|
||||
|
@ -5452,7 +5452,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -6334,7 +6348,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
})
|
||||
}
|
||||
shareController.actionCompleted = { [weak self] in
|
||||
@ -7153,7 +7181,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
})
|
||||
}
|
||||
shareController.actionCompleted = { [weak self] in
|
||||
@ -9095,7 +9137,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
}
|
||||
peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peer, threadId in
|
||||
let peerId = peer.id
|
||||
@ -9107,7 +9163,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
}
|
||||
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.controller?.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
|
||||
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil)
|
||||
|
||||
|
@ -1090,13 +1090,28 @@ final class StoryItemSetContainerSendMessage {
|
||||
}
|
||||
|
||||
if let controller = component.controller() {
|
||||
let context = component.context
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
controller.present(UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .forward(savedMessages: savedMessages, text: text),
|
||||
elevatedLayout: false,
|
||||
animateInAsReplacement: false,
|
||||
action: { _ in return false }
|
||||
action: { [weak controller] _ in
|
||||
if savedMessages {
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { peer in
|
||||
guard let controller, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
), in: .current)
|
||||
}
|
||||
})
|
||||
|
@ -321,6 +321,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
var preloadNextChatPeerId: PeerId?
|
||||
let preloadNextChatPeerIdDisposable = MetaDisposable()
|
||||
|
||||
var preloadSavedMessagesChatsDisposable: Disposable?
|
||||
|
||||
let botCallbackAlertMessage = Promise<String?>(nil)
|
||||
var botCallbackAlertMessageDisposable: Disposable?
|
||||
|
||||
@ -2518,7 +2520,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
})
|
||||
}
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
@ -5703,6 +5719,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
if peerId == context.account.peerId {
|
||||
self.preloadSavedMessagesChatsDisposable = context.engine.messages.savedMessagesPeerListHead().start()
|
||||
}
|
||||
} else if case let .replyThread(messagePromise) = self.chatLocationInfoData, let peerId = peerId {
|
||||
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
|
||||
|
||||
@ -5896,7 +5916,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
let mappedPeerData = ChatTitleContent.PeerData(
|
||||
peerId: savedMessagesPeerId,
|
||||
peer: savedMessagesPeer?.peer?._asPeer(),
|
||||
isContact: false,
|
||||
isContact: true,
|
||||
notificationSettings: nil,
|
||||
peerPresences: [:],
|
||||
cachedData: nil
|
||||
@ -6748,6 +6768,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.automaticMediaDownloadSettingsDisposable?.dispose()
|
||||
self.stickerSettingsDisposable?.dispose()
|
||||
self.searchQuerySuggestionState?.1.dispose()
|
||||
self.preloadSavedMessagesChatsDisposable?.dispose()
|
||||
}
|
||||
deallocate()
|
||||
}
|
||||
@ -7190,6 +7211,39 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
strongSelf.updateChatPresentationInterfaceState(interactive: false, { $0.updatedHasPlentyOfMessages(hasPlentyOfMessages) })
|
||||
}
|
||||
}
|
||||
if case .peer(self.context.account.peerId) = self.chatLocation {
|
||||
var didDisplayTooltip = false
|
||||
self.chatDisplayNode.historyNode.hasLotsOfMessagesUpdated = { [weak self] hasLotsOfMessages in
|
||||
guard let self, hasLotsOfMessages else {
|
||||
return
|
||||
}
|
||||
if didDisplayTooltip {
|
||||
return
|
||||
}
|
||||
didDisplayTooltip = true
|
||||
|
||||
let _ = (ApplicationSpecificNotice.getSavedMessagesChatsSuggestion(accountManager: self.context.sharedContext.accountManager)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] counter in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if counter >= 3 {
|
||||
return
|
||||
}
|
||||
guard let navigationBar = self.navigationBar else {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Tap to view your Saved Messages organized by type or source"), location: .point(navigationBar.frame, .top), displayDuration: .manual, shouldDismissOnTouch: { point, _ in
|
||||
return .ignore
|
||||
})
|
||||
self.present(tooltipScreen, in: .current)
|
||||
|
||||
let _ = ApplicationSpecificNotice.incrementSavedMessagesChatsSuggestion(accountManager: self.context.sharedContext.accountManager).startStandalone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self.chatDisplayNode.historyNode.addContentOffset = { [weak self] offset, itemNode in
|
||||
guard let strongSelf = self else {
|
||||
@ -11949,6 +12003,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return nil
|
||||
}
|
||||
|
||||
public func updateIsScrollingLockedAtTop(isScrollingLockedAtTop: Bool) {
|
||||
self.chatDisplayNode.isScrollingLockedAtTop = isScrollingLockedAtTop
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.suspendNavigationBarLayout = true
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
@ -16380,7 +16438,21 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
}
|
||||
|
||||
switch mode {
|
||||
@ -18943,6 +19015,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
)
|
||||
self.push(controller)
|
||||
}
|
||||
|
||||
public func transferScrollingVelocity(_ velocity: CGFloat) {
|
||||
self.chatDisplayNode.historyNode.transferVelocity(velocity)
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
|
@ -132,6 +132,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
let loadingNode: ChatLoadingNode
|
||||
private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode?
|
||||
|
||||
var isScrollingLockedAtTop: Bool = false
|
||||
|
||||
private var emptyNode: ChatEmptyNode?
|
||||
private(set) var emptyType: ChatHistoryNodeLoadState.EmptyType?
|
||||
private var didDisplayEmptyGreeting = false
|
||||
@ -579,15 +581,23 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
} else {
|
||||
source = .default
|
||||
}
|
||||
|
||||
var historyNodeRotated = true
|
||||
switch chatPresentationInterfaceState.mode {
|
||||
case let .standard(standardMode):
|
||||
if case .embedded(true) = standardMode {
|
||||
historyNodeRotated = false
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.controllerInteraction.chatIsRotated = historyNodeRotated
|
||||
|
||||
var getMessageTransitionNode: (() -> ChatMessageTransitionNodeImpl?)?
|
||||
self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: controller?.updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: nil, source: source, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), messageTransitionNode: {
|
||||
self.historyNode = ChatHistoryListNodeImpl(context: context, updatedPresentationData: controller?.updatedPresentationData ?? (context.sharedContext.currentPresentationData.with({ $0 }), context.sharedContext.presentationData), chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder, tagMask: nil, source: source, subject: subject, controllerInteraction: controllerInteraction, selectedMessages: self.selectedMessagesPromise.get(), rotated: historyNodeRotated, messageTransitionNode: {
|
||||
return getMessageTransitionNode?()
|
||||
})
|
||||
self.historyNode.rotated = true
|
||||
|
||||
//self.historyScrollingArea = SparseDiscreteScrollingArea()
|
||||
//self.historyNode.historyScrollingArea = self.historyScrollingArea
|
||||
|
||||
self.historyNodeContainer = HistoryNodeContainer(isSecret: chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat)
|
||||
|
||||
@ -625,7 +635,7 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.inputPanelBottomBackgroundSeparatorNode.backgroundColor = self.chatPresentationInterfaceState.theme.chat.inputMediaPanel.panelSeparatorColor
|
||||
self.inputPanelBottomBackgroundSeparatorNode.isLayerBacked = true
|
||||
|
||||
self.navigateButtons = ChatHistoryNavigationButtons(theme: self.chatPresentationInterfaceState.theme, dateTimeFormat: self.chatPresentationInterfaceState.dateTimeFormat, backgroundNode: self.backgroundNode)
|
||||
self.navigateButtons = ChatHistoryNavigationButtons(theme: self.chatPresentationInterfaceState.theme, dateTimeFormat: self.chatPresentationInterfaceState.dateTimeFormat, backgroundNode: self.backgroundNode, isChatRotated: historyNodeRotated)
|
||||
self.navigateButtons.accessibilityElementsHidden = true
|
||||
|
||||
super.init()
|
||||
@ -1857,6 +1867,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if !self.historyNode.rotated {
|
||||
let current = listInsets
|
||||
listInsets.top = current.bottom
|
||||
listInsets.bottom = current.top
|
||||
}
|
||||
|
||||
var displayTopDimNode = false
|
||||
let ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil
|
||||
var expandTopDimNode = false
|
||||
@ -1923,6 +1939,17 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
strongSelf.notifyTransitionCompletionListeners(transition: transition)
|
||||
}
|
||||
})
|
||||
if self.isScrollingLockedAtTop {
|
||||
switch self.historyNode.visibleContentOffset() {
|
||||
case let .known(value) where value <= CGFloat.ulpOfOne:
|
||||
break
|
||||
case .none:
|
||||
break
|
||||
default:
|
||||
self.historyNode.scrollToEndOfHistory()
|
||||
}
|
||||
}
|
||||
self.historyNode.scrollEnabled = !self.isScrollingLockedAtTop
|
||||
|
||||
let navigateButtonsSize = self.navigateButtons.updateLayout(transition: transition)
|
||||
var navigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 6.0, y: layout.size.height - containerInsets.bottom - inputPanelsHeight - navigateButtonsSize.height - 6.0), size: navigateButtonsSize)
|
||||
@ -1946,6 +1973,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
apparentNavigateButtonsFrame.origin.y -= 16.0
|
||||
}
|
||||
|
||||
if !self.historyNode.rotated {
|
||||
apparentNavigateButtonsFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - navigateButtonsSize.width - 6.0, y: 6.0), size: navigateButtonsSize)
|
||||
}
|
||||
|
||||
var isInputExpansionEnabled = false
|
||||
if case .media = self.chatPresentationInterfaceState.inputMode {
|
||||
isInputExpansionEnabled = true
|
||||
|
@ -621,6 +621,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
public private(set) var hasPlentyOfMessages: Bool = false
|
||||
public var hasPlentyOfMessagesUpdated: ((Bool) -> Void)?
|
||||
|
||||
public private(set) var hasLotsOfMessages: Bool = false
|
||||
public var hasLotsOfMessagesUpdated: ((Bool) -> Void)?
|
||||
|
||||
private var loadedMessagesFromCachedDataDisposable: Disposable?
|
||||
|
||||
let isTopReplyThreadMessageShown = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
@ -683,7 +686,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
private var allowDustEffect: Bool = true
|
||||
private var dustEffectLayer: DustEffectLayer?
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, NoError>, mode: ChatHistoryListMode = .bubbles, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, NoError>, mode: ChatHistoryListMode = .bubbles, rotated: Bool = false, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) {
|
||||
var tagMask = tagMask
|
||||
if case .pinnedMessages = subject {
|
||||
tagMask = .pinned
|
||||
@ -738,6 +741,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
nextClientId += 1
|
||||
|
||||
super.init()
|
||||
|
||||
self.rotated = rotated
|
||||
if rotated {
|
||||
self.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
|
||||
}
|
||||
|
||||
self.clipsToBounds = false
|
||||
|
||||
@ -809,12 +817,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
}
|
||||
|
||||
self.preloadPages = false
|
||||
switch self.mode {
|
||||
case .bubbles:
|
||||
self.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
|
||||
case .list:
|
||||
break
|
||||
}
|
||||
|
||||
self.beginChatHistoryTransitions(
|
||||
selectedMessages: selectedMessages,
|
||||
@ -3092,7 +3094,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
dustEffectLayer.bounds = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||
self.dustEffectLayer = dustEffectLayer
|
||||
dustEffectLayer.zPosition = 10.0
|
||||
dustEffectLayer.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
|
||||
if self.rotated {
|
||||
dustEffectLayer.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
|
||||
}
|
||||
self.layer.addSublayer(dustEffectLayer)
|
||||
dustEffectLayer.becameEmpty = { [weak self] in
|
||||
guard let self else {
|
||||
@ -3310,13 +3314,18 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
}
|
||||
|
||||
var hasPlentyOfMessages = false
|
||||
var hasLotsOfMessages = false
|
||||
if let historyView = strongSelf.historyView {
|
||||
if historyView.originalView.holeEarlier || historyView.originalView.holeLater {
|
||||
hasPlentyOfMessages = true
|
||||
hasLotsOfMessages = true
|
||||
} else if !historyView.originalView.holeEarlier && !historyView.originalView.holeLater {
|
||||
if historyView.filteredEntries.count >= 10 {
|
||||
hasPlentyOfMessages = true
|
||||
}
|
||||
if historyView.filteredEntries.count >= 40 {
|
||||
hasLotsOfMessages = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3324,6 +3333,10 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
strongSelf.hasPlentyOfMessages = hasPlentyOfMessages
|
||||
strongSelf.hasPlentyOfMessagesUpdated?(hasPlentyOfMessages)
|
||||
}
|
||||
if strongSelf.hasLotsOfMessages != hasLotsOfMessages {
|
||||
strongSelf.hasLotsOfMessages = hasLotsOfMessages
|
||||
strongSelf.hasLotsOfMessagesUpdated?(hasLotsOfMessages)
|
||||
}
|
||||
|
||||
if let _ = visibleRange.loadedRange {
|
||||
if let visible = visibleRange.visibleRange {
|
||||
@ -4170,7 +4183,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
overscrollView.frame = overscrollView.convert(overscrollView.bounds, to: self.view)
|
||||
snapshotView.addSubview(overscrollView)
|
||||
|
||||
overscrollView.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
if self.rotated {
|
||||
overscrollView.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
return SnapshotState(
|
||||
@ -4195,13 +4210,17 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
||||
|
||||
let snapshotParentView = UIView()
|
||||
snapshotParentView.addSubview(snapshotState.snapshotView)
|
||||
snapshotParentView.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
|
||||
if self.rotated {
|
||||
snapshotParentView.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
|
||||
}
|
||||
snapshotParentView.frame = self.view.frame
|
||||
|
||||
snapshotState.snapshotView.frame = snapshotParentView.bounds
|
||||
|
||||
snapshotState.snapshotView.clipsToBounds = true
|
||||
snapshotState.snapshotView.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
if self.rotated {
|
||||
snapshotState.snapshotView.layer.sublayerTransform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||
}
|
||||
|
||||
self.view.superview?.insertSubview(snapshotParentView, belowSubview: self.view)
|
||||
|
||||
|
@ -10,6 +10,7 @@ private let badgeFont = Font.with(size: 13.0, traits: [.monospacedNumbers])
|
||||
|
||||
enum ChatHistoryNavigationButtonType {
|
||||
case down
|
||||
case up
|
||||
case mentions
|
||||
case reactions
|
||||
}
|
||||
@ -60,6 +61,8 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||
switch type {
|
||||
case .down:
|
||||
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme)
|
||||
case .up:
|
||||
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationUpButtonImage(theme)
|
||||
case .mentions:
|
||||
self.imageNode.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme)
|
||||
case .reactions:
|
||||
@ -113,6 +116,8 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
||||
switch self.type {
|
||||
case .down:
|
||||
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme)
|
||||
case .up:
|
||||
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationUpButtonImage(theme)
|
||||
case .mentions:
|
||||
self.imageNode.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme)
|
||||
case .reactions:
|
||||
|
@ -8,6 +8,7 @@ import WallpaperBackgroundNode
|
||||
final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
private var dateTimeFormat: PresentationDateTimeFormat
|
||||
private let isChatRotated: Bool
|
||||
|
||||
let reactionsButton: ChatHistoryNavigationButtonNode
|
||||
let mentionsButton: ChatHistoryNavigationButtonNode
|
||||
@ -68,7 +69,8 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
init(theme: PresentationTheme, dateTimeFormat: PresentationDateTimeFormat, backgroundNode: WallpaperBackgroundNode) {
|
||||
init(theme: PresentationTheme, dateTimeFormat: PresentationDateTimeFormat, backgroundNode: WallpaperBackgroundNode, isChatRotated: Bool) {
|
||||
self.isChatRotated = isChatRotated
|
||||
self.theme = theme
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
|
||||
@ -80,7 +82,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
self.reactionsButton.alpha = 0.0
|
||||
self.reactionsButton.isHidden = true
|
||||
|
||||
self.downButton = ChatHistoryNavigationButtonNode(theme: theme, backgroundNode: backgroundNode, type: .down)
|
||||
self.downButton = ChatHistoryNavigationButtonNode(theme: theme, backgroundNode: backgroundNode, type: isChatRotated ? .down : .up)
|
||||
self.downButton.alpha = 0.0
|
||||
self.downButton.isHidden = true
|
||||
|
||||
@ -186,11 +188,15 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||
transition.updateTransformScale(node: self.reactionsButton, scale: 0.2)
|
||||
}
|
||||
|
||||
transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height), size: buttonSize).center)
|
||||
|
||||
transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize).center)
|
||||
|
||||
transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset - reactionsOffset), size: buttonSize).center)
|
||||
if self.isChatRotated {
|
||||
transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height), size: buttonSize).center)
|
||||
transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset), size: buttonSize).center)
|
||||
transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: completeSize.height - buttonSize.height - mentionsOffset - reactionsOffset), size: buttonSize).center)
|
||||
} else {
|
||||
transition.updatePosition(node: self.downButton, position: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: buttonSize).center)
|
||||
transition.updatePosition(node: self.mentionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: mentionsOffset), size: buttonSize).center)
|
||||
transition.updatePosition(node: self.reactionsButton, position: CGRect(origin: CGPoint(x: 0.0, y: mentionsOffset + reactionsOffset), size: buttonSize).center)
|
||||
}
|
||||
|
||||
if let (rect, containerSize) = self.absoluteRect {
|
||||
self.update(rect: rect, within: containerSize, transition: transition)
|
||||
|
@ -104,7 +104,21 @@ final class OverlayAudioPlayerControllerImpl: ViewController, OverlayAudioPlayer
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in
|
||||
if savedMessages, let self {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = self.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer)))
|
||||
})
|
||||
}
|
||||
return false
|
||||
}), in: .current)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -810,6 +810,9 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
|
||||
if let _ = self.openActiveTextItem, let textComponentView = self.textView.view, let result = textComponentView.hitTest(self.view.convert(point, to: textComponentView), with: event) {
|
||||
return result
|
||||
}
|
||||
if let closeButtonNode = self.closeButtonNode, let result = closeButtonNode.hitTest(self.view.convert(point, to: closeButtonNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
|
||||
var eventIsPresses = false
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
|
@ -639,10 +639,19 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
self.animatedStickerNode = nil
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let bold: MarkdownAttributeSet
|
||||
if savedMessages {
|
||||
bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: presentationData.theme.list.itemAccentColor.withMultiplied(hue: 0.933, saturation: 0.61, brightness: 1.0), additionalAttributes: ["URL": ""])
|
||||
} else {
|
||||
bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
}
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
|
||||
if savedMessages {
|
||||
isUserInteractionEnabled = true
|
||||
}
|
||||
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = 3
|
||||
|
Loading…
x
Reference in New Issue
Block a user