mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Saved messages
This commit is contained in:
parent
ee90dd8332
commit
4b16494e20
@ -10839,3 +10839,5 @@ Sorry for the inconvenience.";
|
|||||||
"RequestPeer.SelectUsers.SearchPlaceholder" = "Search";
|
"RequestPeer.SelectUsers.SearchPlaceholder" = "Search";
|
||||||
"RequestPeer.ReachedMaximum_1" = "You can select up to %@ user.";
|
"RequestPeer.ReachedMaximum_1" = "You can select up to %@ user.";
|
||||||
"RequestPeer.ReachedMaximum_any" = "You can select up to %@ users.";
|
"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 ChatControllerPresentationMode: Equatable {
|
||||||
public enum StandardPresentation {
|
public enum StandardPresentation: Equatable {
|
||||||
case `default`
|
case `default`
|
||||||
case previewing
|
case previewing
|
||||||
case embedded
|
case embedded(invertDirection: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
case standard(StandardPresentation)
|
case standard(StandardPresentation)
|
||||||
@ -912,6 +912,9 @@ public protocol ChatController: ViewController {
|
|||||||
func cancelSelectingMessages()
|
func cancelSelectingMessages()
|
||||||
func activateSearch(domain: ChatSearchDomain, query: String)
|
func activateSearch(domain: ChatSearchDomain, query: String)
|
||||||
func beginClearHistory(type: InteractiveHistoryClearingType)
|
func beginClearHistory(type: InteractiveHistoryClearingType)
|
||||||
|
|
||||||
|
func transferScrollingVelocity(_ velocity: CGFloat)
|
||||||
|
func updateIsScrollingLockedAtTop(isScrollingLockedAtTop: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol ChatMessagePreviewItemNode: AnyObject {
|
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) {
|
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 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 }
|
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
|
peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peer, threadId in
|
||||||
let peerId = peer.id
|
let peerId = peer.id
|
||||||
@ -1432,7 +1446,22 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
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
|
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)
|
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] {
|
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] = []
|
var options: [ItemListRevealOption] = []
|
||||||
if !isEditing {
|
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 {
|
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))
|
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 {
|
} else {
|
||||||
@ -398,28 +404,31 @@ private func revealOptions(strings: PresentationStrings, theme: PresentationThem
|
|||||||
if canDelete {
|
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))
|
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 {
|
if case .savedMessagesChats = location {
|
||||||
var canArchive = false
|
} else {
|
||||||
var canUnarchive = false
|
if !isEditing {
|
||||||
if let filterData = filterData {
|
var canArchive = false
|
||||||
if filterData.excludesArchived {
|
var canUnarchive = false
|
||||||
canArchive = true
|
if let filterData = filterData {
|
||||||
}
|
if filterData.excludesArchived {
|
||||||
} else {
|
|
||||||
if case let .chatList(groupId) = location {
|
|
||||||
if case .root = groupId {
|
|
||||||
canArchive = true
|
canArchive = true
|
||||||
} else {
|
}
|
||||||
canUnarchive = true
|
} else {
|
||||||
|
if case let .chatList(groupId) = location {
|
||||||
|
if case .root = groupId {
|
||||||
|
canArchive = true
|
||||||
|
} else {
|
||||||
|
canUnarchive = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if canArchive {
|
||||||
if canArchive {
|
if canArchivePeer(id: peerId, accountPeerId: accountPeerId) {
|
||||||
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))
|
||||||
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
|
return options
|
||||||
|
@ -22,6 +22,7 @@ import StoryContainerScreen
|
|||||||
import ChatListHeaderComponent
|
import ChatListHeaderComponent
|
||||||
import UndoUI
|
import UndoUI
|
||||||
import NewSessionInfoScreen
|
import NewSessionInfoScreen
|
||||||
|
import PresentationDataUtils
|
||||||
|
|
||||||
public enum ChatListNodeMode {
|
public enum ChatListNodeMode {
|
||||||
case chatList(appendContacts: Bool)
|
case chatList(appendContacts: Bool)
|
||||||
@ -1420,70 +1421,90 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, setItemPinned: { [weak self] itemId, _ in
|
}, setItemPinned: { [weak self] itemId, _ in
|
||||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
if case .savedMessagesChats = location {
|
||||||
|> deliverOnMainQueue).startStandalone(next: { peer in
|
if case let .peer(itemPeerId) = itemId {
|
||||||
guard let strongSelf = self else {
|
let _ = (context.engine.peers.toggleForumChannelTopicPinned(id: context.account.peerId, threadId: itemPeerId.toInt64())
|
||||||
return
|
|> deliverOnMainQueue).start(error: { error in
|
||||||
}
|
guard let self else {
|
||||||
guard case let .chatList(groupId) = strongSelf.location else {
|
return
|
||||||
return
|
}
|
||||||
}
|
switch error {
|
||||||
|
case let .limitReached(count):
|
||||||
let isPremium = peer?.isPremium ?? false
|
let controller = PremiumLimitScreen(context: context, subject: .pinnedSavedPeers, count: Int32(count), action: {
|
||||||
let location: TogglePeerChatPinnedLocation
|
return true
|
||||||
if let chatListFilter = chatListFilter {
|
})
|
||||||
location = .filter(chatListFilter.id)
|
self.push?(controller)
|
||||||
} else {
|
default:
|
||||||
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
|
break
|
||||||
case let .limitExceeded(count, _):
|
}
|
||||||
if isPremium {
|
})
|
||||||
if case .filter = location {
|
}
|
||||||
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {
|
} else {
|
||||||
return true
|
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||||
})
|
|> deliverOnMainQueue).startStandalone(next: { peer in
|
||||||
strongSelf.push?(controller)
|
guard let strongSelf = self else {
|
||||||
} else {
|
return
|
||||||
let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {
|
}
|
||||||
return true
|
guard case let .chatList(groupId) = strongSelf.location else {
|
||||||
})
|
return
|
||||||
strongSelf.push?(controller)
|
}
|
||||||
}
|
|
||||||
} else {
|
let isPremium = peer?.isPremium ?? false
|
||||||
if case .filter = location {
|
let location: TogglePeerChatPinnedLocation
|
||||||
var replaceImpl: ((ViewController) -> Void)?
|
if let chatListFilter = chatListFilter {
|
||||||
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {
|
location = .filter(chatListFilter.id)
|
||||||
let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats)
|
} else {
|
||||||
replaceImpl?(premiumScreen)
|
location = .group(groupId._asGroup())
|
||||||
return true
|
}
|
||||||
})
|
let _ = (context.engine.peers.toggleItemPinned(location: location, itemId: itemId)
|
||||||
strongSelf.push?(controller)
|
|> deliverOnMainQueue).startStandalone(next: { result in
|
||||||
replaceImpl = { [weak controller] c in
|
if let strongSelf = self {
|
||||||
controller?.replace(with: c)
|
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 {
|
} else {
|
||||||
var replaceImpl: ((ViewController) -> Void)?
|
if case .filter = location {
|
||||||
let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {
|
var replaceImpl: ((ViewController) -> Void)?
|
||||||
let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats)
|
let controller = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: Int32(count), action: {
|
||||||
replaceImpl?(premiumScreen)
|
let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats)
|
||||||
return true
|
replaceImpl?(premiumScreen)
|
||||||
})
|
return true
|
||||||
strongSelf.push?(controller)
|
})
|
||||||
replaceImpl = { [weak controller] c in
|
strongSelf.push?(controller)
|
||||||
controller?.replace(with: c)
|
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
|
}, setPeerMuted: { [weak self] peerId, _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
|
@ -16,6 +16,9 @@ swift_library(
|
|||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
"//submodules/AvatarNode:AvatarNode",
|
"//submodules/AvatarNode:AvatarNode",
|
||||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||||
|
"//submodules/ComponentFlow",
|
||||||
|
"//submodules/Components/MultilineTextComponent",
|
||||||
|
"//submodules/Components/BalancedTextComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -6,6 +6,9 @@ import TelegramPresentationData
|
|||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import AvatarNode
|
import AvatarNode
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import ComponentFlow
|
||||||
|
import BalancedTextComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
|
||||||
public enum DeleteChatPeerAction {
|
public enum DeleteChatPeerAction {
|
||||||
case delete
|
case delete
|
||||||
@ -15,6 +18,7 @@ public enum DeleteChatPeerAction {
|
|||||||
case clearCacheSuggestion
|
case clearCacheSuggestion
|
||||||
case removeFromGroup
|
case removeFromGroup
|
||||||
case removeFromChannel
|
case removeFromChannel
|
||||||
|
case deleteSavedPeer
|
||||||
}
|
}
|
||||||
|
|
||||||
private let avatarFont = avatarPlaceholderFont(size: 26.0)
|
private let avatarFont = avatarPlaceholderFont(size: 26.0)
|
||||||
@ -26,18 +30,20 @@ public final class DeleteChatPeerActionSheetItem: ActionSheetItem {
|
|||||||
let action: DeleteChatPeerAction
|
let action: DeleteChatPeerAction
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let nameDisplayOrder: PresentationPersonNameOrder
|
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.context = context
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.chatPeer = chatPeer
|
self.chatPeer = chatPeer
|
||||||
self.action = action
|
self.action = action
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
|
self.balancedLayout = balancedLayout
|
||||||
}
|
}
|
||||||
|
|
||||||
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
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) {
|
public func updateNode(_ node: ActionSheetItemNode) {
|
||||||
@ -47,15 +53,19 @@ public final class DeleteChatPeerActionSheetItem: ActionSheetItem {
|
|||||||
private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
||||||
private let theme: ActionSheetControllerTheme
|
private let theme: ActionSheetControllerTheme
|
||||||
private let strings: PresentationStrings
|
private let strings: PresentationStrings
|
||||||
|
private let balancedLayout: Bool
|
||||||
|
|
||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
private let textNode: ImmediateTextNode
|
|
||||||
|
private var text: NSAttributedString?
|
||||||
|
private let textView = ComponentView<Empty>()
|
||||||
|
|
||||||
private let accessibilityArea: AccessibilityAreaNode
|
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.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
|
self.balancedLayout = balancedLayout
|
||||||
|
|
||||||
let textFont = Font.regular(floor(theme.baseFontSize * 14.0 / 17.0))
|
let textFont = Font.regular(floor(theme.baseFontSize * 14.0 / 17.0))
|
||||||
let boldFont = Font.semibold(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 = AvatarNode(font: avatarFont)
|
||||||
self.avatarNode.isAccessibilityElement = false
|
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()
|
self.accessibilityArea = AccessibilityAreaNode()
|
||||||
|
|
||||||
super.init(theme: theme)
|
super.init(theme: theme)
|
||||||
|
|
||||||
self.addSubnode(self.avatarNode)
|
self.addSubnode(self.avatarNode)
|
||||||
self.addSubnode(self.textNode)
|
|
||||||
self.addSubnode(self.accessibilityArea)
|
self.addSubnode(self.accessibilityArea)
|
||||||
|
|
||||||
if chatPeer.id == context.account.peerId {
|
if chatPeer.id == context.account.peerId {
|
||||||
self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .savedMessagesIcon)
|
self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .savedMessagesIcon)
|
||||||
} else if chatPeer.id.isReplies {
|
} else if chatPeer.id.isReplies {
|
||||||
self.avatarNode.setPeer(context: context, theme: (context.sharedContext.currentPresentationData.with { $0 }).theme, peer: peer, overrideImage: .repliesIcon)
|
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 {
|
} else {
|
||||||
var overrideImage: AvatarNodeImageOverride?
|
var overrideImage: AvatarNodeImageOverride?
|
||||||
if chatPeer.isDeleted {
|
if chatPeer.isDeleted {
|
||||||
@ -127,6 +132,10 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
|||||||
} else {
|
} else {
|
||||||
text = strings.ChatList_DeleteChatConfirmation(peer.displayTitle(strings: strings, displayOrder: nameOrder))
|
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):
|
case let .clearHistory(canClearCache):
|
||||||
if peer.id == context.account.peerId {
|
if peer.id == context.account.peerId {
|
||||||
text = PresentationStrings.FormattedString(string: strings.ChatList_ClearSavedMessagesConfirmation, ranges: [])
|
text = PresentationStrings.FormattedString(string: strings.ChatList_ClearSavedMessagesConfirmation, ranges: [])
|
||||||
@ -162,7 +171,7 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let attributedText = attributedText {
|
if let attributedText = attributedText {
|
||||||
self.textNode.attributedText = attributedText
|
self.text = attributedText
|
||||||
|
|
||||||
self.accessibilityArea.accessibilityLabel = attributedText.string
|
self.accessibilityArea.accessibilityLabel = attributedText.string
|
||||||
self.accessibilityArea.accessibilityTraits = .staticText
|
self.accessibilityArea.accessibilityTraits = .staticText
|
||||||
@ -170,7 +179,21 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
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 topInset: CGFloat = 16.0
|
||||||
let avatarSize: CGFloat = 60.0
|
let avatarSize: CGFloat = 60.0
|
||||||
@ -178,7 +201,13 @@ private final class DeleteChatPeerActionSheetItemNode: ActionSheetItemNode {
|
|||||||
let bottomInset: CGFloat = 15.0
|
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.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)
|
let size = CGSize(width: constrainedSize.width, height: topInset + avatarSize + textSpacing + textSize.height + bottomInset)
|
||||||
self.accessibilityArea.frame = CGRect(origin: CGPoint(), size: size)
|
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) {
|
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
if self.layout != nil && self.layout! != layout {
|
if self.layout != nil && self.layout!.size != layout.size {
|
||||||
if self.dismissImmediatelyOnLayoutUpdate {
|
if self.dismissImmediatelyOnLayoutUpdate {
|
||||||
self.dismissImmediately()
|
self.dismissImmediately()
|
||||||
} else {
|
} else {
|
||||||
|
@ -184,7 +184,21 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
text = ""
|
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 presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
var attemptNavigationImpl: ((@escaping () -> Void) -> Bool)?
|
var attemptNavigationImpl: ((@escaping () -> Void) -> Bool)?
|
||||||
|
var navigationController: (() -> NavigationController?)?
|
||||||
|
|
||||||
var dismissTooltipsImpl: (() -> Void)?
|
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 = {
|
shareController.actionCompleted = {
|
||||||
@ -746,6 +761,9 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
navigationController = { [weak controller] in
|
||||||
|
return controller?.navigationController as? NavigationController
|
||||||
|
}
|
||||||
pushControllerImpl = { [weak controller] c in
|
pushControllerImpl = { [weak controller] c in
|
||||||
if let controller = controller {
|
if let controller = controller {
|
||||||
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
|
(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 pushControllerImpl: ((ViewController) -> Void)?
|
||||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||||
|
var navigationController: (() -> NavigationController?)?
|
||||||
|
|
||||||
var dismissTooltipsImpl: (() -> Void)?
|
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 = {
|
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 = {
|
shareController.actionCompleted = {
|
||||||
@ -925,6 +954,9 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
|
|||||||
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
|
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
navigationController = { [weak controller] in
|
||||||
|
return controller?.navigationController as? NavigationController
|
||||||
|
}
|
||||||
presentControllerImpl = { [weak controller] c, p in
|
presentControllerImpl = { [weak controller] c, p in
|
||||||
if let controller = controller {
|
if let controller = controller {
|
||||||
controller.present(c, in: .window(.root), with: p)
|
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)
|
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
|
self.isLoading = validIndexBoundary == nil
|
||||||
|
|
||||||
if let validIndexBoundary = validIndexBoundary {
|
if let validIndexBoundary = validIndexBoundary {
|
||||||
|
@ -1021,6 +1021,22 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||||
badgeGraphPosition = badgePosition
|
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 {
|
if isPremiumDisabled {
|
||||||
badgeText = "\(limit)"
|
badgeText = "\(limit)"
|
||||||
string = strings.Premium_MaxPinsNoPremiumText("\(limit)").string
|
string = strings.Premium_MaxPinsNoPremiumText("\(limit)").string
|
||||||
@ -1771,6 +1787,7 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
|
|||||||
case folders
|
case folders
|
||||||
case chatsPerFolder
|
case chatsPerFolder
|
||||||
case pins
|
case pins
|
||||||
|
case pinnedSavedPeers
|
||||||
case files
|
case files
|
||||||
case accounts
|
case accounts
|
||||||
case linksPerSharedFolder
|
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 = {
|
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 {
|
public extension Api.functions.messages {
|
||||||
static func deleteScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
static func deleteScheduledMessages(peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
|
@ -458,33 +458,51 @@ public enum SetForumChannelTopicPinnedError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _internal_setForumChannelPinnedTopics(account: Account, id: EnginePeer.Id, threadIds: [Int64]) -> Signal<Never, SetForumChannelTopicPinnedError> {
|
func _internal_setForumChannelPinnedTopics(account: Account, id: EnginePeer.Id, threadIds: [Int64]) -> Signal<Never, SetForumChannelTopicPinnedError> {
|
||||||
return account.postbox.transaction { transaction -> Api.InputChannel? in
|
if id == account.peerId {
|
||||||
guard let inputChannel = transaction.getPeer(id).flatMap(apiInputChannel) else {
|
return account.postbox.transaction { transaction -> [Api.InputDialogPeer] in
|
||||||
return nil
|
transaction.setPeerPinnedThreads(peerId: id, threadIds: threadIds)
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
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),
|
limit: Int32(limit),
|
||||||
hash: 0
|
hash: 0
|
||||||
))
|
))
|
||||||
|> mapError { _ -> LoadMessageHistoryThreadsError in
|
|> `catch` { error -> Signal<Api.messages.SavedDialogs, LoadMessageHistoryThreadsError> in
|
||||||
return .generic
|
if error.errorDescription == "SAVED_DIALOGS_UNSUPPORTED" {
|
||||||
|
return .never()
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<LoadMessageHistoryThreadsResult, LoadMessageHistoryThreadsError> in
|
|> mapToSignal { result -> Signal<LoadMessageHistoryThreadsResult, LoadMessageHistoryThreadsError> in
|
||||||
switch result {
|
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> {
|
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 peer.id.namespace == Namespaces.Peer.CloudGroup || peer.id.namespace == Namespaces.Peer.CloudUser {
|
||||||
if let inputPeer = apiInputPeer(peer) {
|
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 {
|
} else {
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
|
@ -2,34 +2,36 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
public struct UserLimitsConfiguration: Equatable {
|
public struct UserLimitsConfiguration: Equatable {
|
||||||
public let maxPinnedChatCount: Int32
|
public var maxPinnedChatCount: Int32
|
||||||
public let maxArchivedPinnedChatCount: Int32
|
public var maxPinnedSavedChatCount: Int32
|
||||||
public let maxChannelsCount: Int32
|
public var maxArchivedPinnedChatCount: Int32
|
||||||
public let maxPublicLinksCount: Int32
|
public var maxChannelsCount: Int32
|
||||||
public let maxSavedGifCount: Int32
|
public var maxPublicLinksCount: Int32
|
||||||
public let maxFavedStickerCount: Int32
|
public var maxSavedGifCount: Int32
|
||||||
public let maxFoldersCount: Int32
|
public var maxFavedStickerCount: Int32
|
||||||
public let maxFolderChatsCount: Int32
|
public var maxFoldersCount: Int32
|
||||||
public let maxCaptionLength: Int32
|
public var maxFolderChatsCount: Int32
|
||||||
public let maxUploadFileParts: Int32
|
public var maxCaptionLength: Int32
|
||||||
public let maxAboutLength: Int32
|
public var maxUploadFileParts: Int32
|
||||||
public let maxAnimatedEmojisInText: Int32
|
public var maxAboutLength: Int32
|
||||||
public let maxReactionsPerMessage: Int32
|
public var maxAnimatedEmojisInText: Int32
|
||||||
public let maxSharedFolderInviteLinks: Int32
|
public var maxReactionsPerMessage: Int32
|
||||||
public let maxSharedFolderJoin: Int32
|
public var maxSharedFolderInviteLinks: Int32
|
||||||
public let maxStoryCaptionLength: Int32
|
public var maxSharedFolderJoin: Int32
|
||||||
public let maxExpiringStoriesCount: Int32
|
public var maxStoryCaptionLength: Int32
|
||||||
public let maxStoriesWeeklyCount: Int32
|
public var maxExpiringStoriesCount: Int32
|
||||||
public let maxStoriesMonthlyCount: Int32
|
public var maxStoriesWeeklyCount: Int32
|
||||||
public let maxStoriesSuggestedReactions: Int32
|
public var maxStoriesMonthlyCount: Int32
|
||||||
public let maxGiveawayChannelsCount: Int32
|
public var maxStoriesSuggestedReactions: Int32
|
||||||
public let maxGiveawayCountriesCount: Int32
|
public var maxGiveawayChannelsCount: Int32
|
||||||
public let maxGiveawayPeriodSeconds: Int32
|
public var maxGiveawayCountriesCount: Int32
|
||||||
public let maxChannelRecommendationsCount: Int32
|
public var maxGiveawayPeriodSeconds: Int32
|
||||||
|
public var maxChannelRecommendationsCount: Int32
|
||||||
|
|
||||||
public static var defaultValue: UserLimitsConfiguration {
|
public static var defaultValue: UserLimitsConfiguration {
|
||||||
return UserLimitsConfiguration(
|
return UserLimitsConfiguration(
|
||||||
maxPinnedChatCount: 5,
|
maxPinnedChatCount: 5,
|
||||||
|
maxPinnedSavedChatCount: 5,
|
||||||
maxArchivedPinnedChatCount: 100,
|
maxArchivedPinnedChatCount: 100,
|
||||||
maxChannelsCount: 500,
|
maxChannelsCount: 500,
|
||||||
maxPublicLinksCount: 10,
|
maxPublicLinksCount: 10,
|
||||||
@ -58,6 +60,7 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
|
|
||||||
public init(
|
public init(
|
||||||
maxPinnedChatCount: Int32,
|
maxPinnedChatCount: Int32,
|
||||||
|
maxPinnedSavedChatCount: Int32,
|
||||||
maxArchivedPinnedChatCount: Int32,
|
maxArchivedPinnedChatCount: Int32,
|
||||||
maxChannelsCount: Int32,
|
maxChannelsCount: Int32,
|
||||||
maxPublicLinksCount: Int32,
|
maxPublicLinksCount: Int32,
|
||||||
@ -83,6 +86,7 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
maxChannelRecommendationsCount: Int32
|
maxChannelRecommendationsCount: Int32
|
||||||
) {
|
) {
|
||||||
self.maxPinnedChatCount = maxPinnedChatCount
|
self.maxPinnedChatCount = maxPinnedChatCount
|
||||||
|
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
|
||||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||||
self.maxChannelsCount = maxChannelsCount
|
self.maxChannelsCount = maxChannelsCount
|
||||||
self.maxPublicLinksCount = maxPublicLinksCount
|
self.maxPublicLinksCount = maxPublicLinksCount
|
||||||
@ -112,7 +116,10 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
extension UserLimitsConfiguration {
|
extension UserLimitsConfiguration {
|
||||||
init(appConfiguration: AppConfiguration, isPremium: Bool) {
|
init(appConfiguration: AppConfiguration, isPremium: Bool) {
|
||||||
let keySuffix = isPremium ? "_premium" : "_default"
|
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 {
|
func getValue(_ key: String, orElse defaultValue: Int32) -> Int32 {
|
||||||
if let value = appConfiguration.data?[key + keySuffix] as? Double {
|
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.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.maxArchivedPinnedChatCount = getValue("dialogs_folder_pinned_limit", orElse: defaultValue.maxArchivedPinnedChatCount)
|
||||||
self.maxChannelsCount = getValue("channels_limit", orElse: defaultValue.maxChannelsCount)
|
self.maxChannelsCount = getValue("channels_limit", orElse: defaultValue.maxChannelsCount)
|
||||||
self.maxPublicLinksCount = getValue("channels_public_limit", orElse: defaultValue.maxPublicLinksCount)
|
self.maxPublicLinksCount = getValue("channels_public_limit", orElse: defaultValue.maxPublicLinksCount)
|
||||||
|
@ -37,6 +37,7 @@ public enum EngineConfiguration {
|
|||||||
|
|
||||||
public struct UserLimits: Equatable {
|
public struct UserLimits: Equatable {
|
||||||
public let maxPinnedChatCount: Int32
|
public let maxPinnedChatCount: Int32
|
||||||
|
public let maxPinnedSavedChatCount: Int32
|
||||||
public let maxArchivedPinnedChatCount: Int32
|
public let maxArchivedPinnedChatCount: Int32
|
||||||
public let maxChannelsCount: Int32
|
public let maxChannelsCount: Int32
|
||||||
public let maxPublicLinksCount: Int32
|
public let maxPublicLinksCount: Int32
|
||||||
@ -67,6 +68,7 @@ public enum EngineConfiguration {
|
|||||||
|
|
||||||
public init(
|
public init(
|
||||||
maxPinnedChatCount: Int32,
|
maxPinnedChatCount: Int32,
|
||||||
|
maxPinnedSavedChatCount: Int32,
|
||||||
maxArchivedPinnedChatCount: Int32,
|
maxArchivedPinnedChatCount: Int32,
|
||||||
maxChannelsCount: Int32,
|
maxChannelsCount: Int32,
|
||||||
maxPublicLinksCount: Int32,
|
maxPublicLinksCount: Int32,
|
||||||
@ -92,6 +94,7 @@ public enum EngineConfiguration {
|
|||||||
maxChannelRecommendationsCount: Int32
|
maxChannelRecommendationsCount: Int32
|
||||||
) {
|
) {
|
||||||
self.maxPinnedChatCount = maxPinnedChatCount
|
self.maxPinnedChatCount = maxPinnedChatCount
|
||||||
|
self.maxPinnedSavedChatCount = maxPinnedSavedChatCount
|
||||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||||
self.maxChannelsCount = maxChannelsCount
|
self.maxChannelsCount = maxChannelsCount
|
||||||
self.maxPublicLinksCount = maxPublicLinksCount
|
self.maxPublicLinksCount = maxPublicLinksCount
|
||||||
@ -153,6 +156,7 @@ public extension EngineConfiguration.UserLimits {
|
|||||||
init(_ userLimitsConfiguration: UserLimitsConfiguration) {
|
init(_ userLimitsConfiguration: UserLimitsConfiguration) {
|
||||||
self.init(
|
self.init(
|
||||||
maxPinnedChatCount: userLimitsConfiguration.maxPinnedChatCount,
|
maxPinnedChatCount: userLimitsConfiguration.maxPinnedChatCount,
|
||||||
|
maxPinnedSavedChatCount: userLimitsConfiguration.maxPinnedSavedChatCount,
|
||||||
maxArchivedPinnedChatCount: userLimitsConfiguration.maxArchivedPinnedChatCount,
|
maxArchivedPinnedChatCount: userLimitsConfiguration.maxArchivedPinnedChatCount,
|
||||||
maxChannelsCount: userLimitsConfiguration.maxChannelsCount,
|
maxChannelsCount: userLimitsConfiguration.maxChannelsCount,
|
||||||
maxPublicLinksCount: userLimitsConfiguration.maxPublicLinksCount,
|
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 let topIndex = topIndex {
|
||||||
if peerId.namespace == Namespaces.Peer.CloudUser {
|
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 {
|
} else {
|
||||||
updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: peerId, minTimestamp: topIndex.timestamp, forceRootGroupIfNotExists: false)
|
updatePeerChatInclusionWithMinTimestamp(transaction: transaction, id: peerId, minTimestamp: topIndex.timestamp, forceRootGroupIfNotExists: false)
|
||||||
}
|
}
|
||||||
|
@ -1314,6 +1314,21 @@ public extension TelegramEngine {
|
|||||||
return self.account.stateManager.synchronouslyIsMessageDeletedInteractively(ids: ids)
|
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> {
|
public func savedMessagesPeersStats() -> Signal<Int?, NoError> {
|
||||||
return self.account.postbox.combinedView(keys: [.savedMessagesStats(peerId: self.account.peerId)])
|
return self.account.postbox.combinedView(keys: [.savedMessagesStats(peerId: self.account.peerId)])
|
||||||
|> map { views -> Int? in
|
|> map { views -> Int? in
|
||||||
|
@ -1063,13 +1063,23 @@ public extension TelegramEngine {
|
|||||||
|
|
||||||
public func toggleForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64) -> Signal<Never, SetForumChannelTopicPinnedError> {
|
public func toggleForumChannelTopicPinned(id: EnginePeer.Id, threadId: Int64) -> Signal<Never, SetForumChannelTopicPinnedError> {
|
||||||
return self.account.postbox.transaction { transaction -> ([Int64], Int) in
|
return self.account.postbox.transaction { transaction -> ([Int64], Int) in
|
||||||
var limit = 5
|
if id == self.account.peerId {
|
||||||
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
var limit = 5
|
||||||
if let data = appConfiguration.data, let value = data["topics_pinned_limit"] as? Double {
|
let appConfiguration: AppConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration)?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
|
||||||
limit = Int(value)
|
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)
|
|> castError(SetForumChannelTopicPinnedError.self)
|
||||||
|> mapToSignal { threadIds, limit -> Signal<Never, SetForumChannelTopicPinnedError> in
|
|> mapToSignal { threadIds, limit -> Signal<Never, SetForumChannelTopicPinnedError> in
|
||||||
|
@ -188,6 +188,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
|
|||||||
case dismissedPremiumWallpapersBadge = 54
|
case dismissedPremiumWallpapersBadge = 54
|
||||||
case dismissedPremiumColorsBadge = 55
|
case dismissedPremiumColorsBadge = 55
|
||||||
case multipleReactionsSuggestion = 56
|
case multipleReactionsSuggestion = 56
|
||||||
|
case savedMessagesChatsSuggestion = 57
|
||||||
|
|
||||||
var key: ValueBoxKey {
|
var key: ValueBoxKey {
|
||||||
let v = ValueBoxKey(length: 4)
|
let v = ValueBoxKey(length: 4)
|
||||||
@ -465,6 +466,9 @@ private struct ApplicationSpecificNoticeKeys {
|
|||||||
static func multipleReactionsSuggestion() -> NoticeEntryKey {
|
static func multipleReactionsSuggestion() -> NoticeEntryKey {
|
||||||
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.multipleReactionsSuggestion.key)
|
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 {
|
public struct ApplicationSpecificNotice {
|
||||||
@ -1852,4 +1856,31 @@ public struct ApplicationSpecificNotice {
|
|||||||
return Int(previousValue)
|
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 chatInputSearchPanelMembersImage
|
||||||
|
|
||||||
case chatHistoryNavigationButtonImage
|
case chatHistoryNavigationButtonImage
|
||||||
|
case chatHistoryNavigationUpButtonImage
|
||||||
case chatHistoryMentionsButtonImage
|
case chatHistoryMentionsButtonImage
|
||||||
case chatHistoryReactionsButtonImage
|
case chatHistoryReactionsButtonImage
|
||||||
case chatHistoryNavigationButtonBadgeImage
|
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? {
|
public static func chatHistoryMentionsButtonImage(_ theme: PresentationTheme) -> UIImage? {
|
||||||
return theme.image(PresentationResourceKey.chatHistoryMentionsButtonImage.rawValue, { theme in
|
return theme.image(PresentationResourceKey.chatHistoryMentionsButtonImage.rawValue, { theme in
|
||||||
return generateImage(CGSize(width: 38.0, height: 38.0), contextGenerator: { size, context 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 wasPending: Bool = false
|
||||||
private var didChangeFromPendingToSent: Bool = false
|
private var didChangeFromPendingToSent: Bool = false
|
||||||
|
|
||||||
required public init() {
|
required public init(rotated: Bool) {
|
||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
@ -156,7 +156,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
|
|||||||
self.textNode.textNode.displaysAsynchronously = false
|
self.textNode.textNode.displaysAsynchronously = false
|
||||||
self.textNode.textNode.isUserInteractionEnabled = false
|
self.textNode.textNode.isUserInteractionEnabled = false
|
||||||
|
|
||||||
super.init(layerBacked: false)
|
super.init(rotated: rotated)
|
||||||
|
|
||||||
self.containerNode.shouldBegin = { [weak self] location in
|
self.containerNode.shouldBegin = { [weak self] location in
|
||||||
guard let strongSelf = self else {
|
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.mainContextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.mainContainerNode = ContextControllerSourceNode()
|
self.mainContainerNode = ContextControllerSourceNode()
|
||||||
self.backgroundWallpaperNode = ChatMessageBubbleBackdrop()
|
self.backgroundWallpaperNode = ChatMessageBubbleBackdrop()
|
||||||
@ -654,7 +654,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||||||
//self.debugNode = ASDisplayNode()
|
//self.debugNode = ASDisplayNode()
|
||||||
//self.debugNode.backgroundColor = .blue
|
//self.debugNode.backgroundColor = .blue
|
||||||
|
|
||||||
super.init(layerBacked: false)
|
super.init(rotated: rotated)
|
||||||
|
|
||||||
//self.addSubnode(self.debugNode)
|
//self.addSubnode(self.debugNode)
|
||||||
|
|
||||||
|
@ -89,13 +89,13 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureReco
|
|||||||
|
|
||||||
fileprivate var wasPlaying = false
|
fileprivate var wasPlaying = false
|
||||||
|
|
||||||
required public init() {
|
required public init(rotated: Bool) {
|
||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
self.interactiveVideoNode = ChatMessageInteractiveInstantVideoNode()
|
self.interactiveVideoNode = ChatMessageInteractiveInstantVideoNode()
|
||||||
self.messageAccessibilityArea = AccessibilityAreaNode()
|
self.messageAccessibilityArea = AccessibilityAreaNode()
|
||||||
|
|
||||||
super.init(layerBacked: false)
|
super.init(rotated: rotated)
|
||||||
|
|
||||||
self.interactiveVideoNode.shouldOpen = { [weak self] in
|
self.interactiveVideoNode.shouldOpen = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
@ -122,7 +122,7 @@ public protocol ChatMessageItem: ListViewItem {
|
|||||||
var sending: Bool { get }
|
var sending: Bool { get }
|
||||||
var failed: 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 {
|
public func hasCommentButton(item: ChatMessageItem) -> Bool {
|
||||||
|
@ -47,9 +47,13 @@ public final class ChatMessageDateHeader: ListViewItemHeader {
|
|||||||
self.action = action
|
self.action = action
|
||||||
self.roundedTimestamp = dateHeaderTimestampId(timestamp: timestamp)
|
self.roundedTimestamp = dateHeaderTimestampId(timestamp: timestamp)
|
||||||
self.id = ListViewItemNode.HeaderId(space: 0, id: Int64(self.roundedTimestamp))
|
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 stickOverInsets: Bool = true
|
||||||
|
|
||||||
public let height: CGFloat = 34.0
|
public let height: CGFloat = 34.0
|
||||||
@ -191,9 +195,13 @@ public final class ChatMessageDateHeaderNode: ListViewItemHeaderNode {
|
|||||||
}
|
}
|
||||||
self.text = text
|
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)
|
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.controllerInteraction = controllerInteraction
|
||||||
self.id = ListViewItemNode.HeaderId(space: 1, id: Id(peerId: peerId, timestampId: dateHeaderTimestampId(timestamp: timestamp)))
|
self.id = ListViewItemNode.HeaderId(space: 1, id: Id(peerId: peerId, timestampId: dateHeaderTimestampId(timestamp: timestamp)))
|
||||||
self.storyStats = storyStats
|
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 stickOverInsets: Bool = false
|
||||||
|
|
||||||
public let height: CGFloat = 38.0
|
public let height: CGFloat = 38.0
|
||||||
@ -484,9 +496,13 @@ public final class ChatMessageAvatarHeaderNodeImpl: ListViewItemHeaderNode, Chat
|
|||||||
self.avatarNode = AvatarNode(font: avatarFont)
|
self.avatarNode = AvatarNode(font: avatarFont)
|
||||||
self.avatarNode.contentNode.displaysAsynchronously = !presentationData.isPreview
|
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.addSubnode(self.containerNode)
|
||||||
self.containerNode.addSubnode(self.avatarNode)
|
self.containerNode.addSubnode(self.avatarNode)
|
||||||
|
@ -259,7 +259,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
self.associatedData = associatedData
|
self.associatedData = associatedData
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.content = content
|
self.content = content
|
||||||
self.disableDate = disableDate
|
self.disableDate = disableDate || !controllerInteraction.chatIsRotated
|
||||||
self.additionalContent = additionalContent
|
self.additionalContent = additionalContent
|
||||||
|
|
||||||
var avatarHeader: ChatMessageAvatarHeader?
|
var avatarHeader: ChatMessageAvatarHeader?
|
||||||
@ -369,6 +369,9 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
if case .messageOptions = associatedData.subject {
|
if case .messageOptions = associatedData.subject {
|
||||||
headers = []
|
headers = []
|
||||||
}
|
}
|
||||||
|
if !controllerInteraction.chatIsRotated {
|
||||||
|
headers = []
|
||||||
|
}
|
||||||
if let avatarHeader = self.avatarHeader {
|
if let avatarHeader = self.avatarHeader {
|
||||||
headers.append(avatarHeader)
|
headers.append(avatarHeader)
|
||||||
}
|
}
|
||||||
@ -450,11 +453,11 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
}
|
}
|
||||||
|
|
||||||
let configure = {
|
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)
|
node.setupItem(self, synchronousLoad: synchronousLoads)
|
||||||
|
|
||||||
let nodeLayout = node.asyncLayout()
|
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
|
var disableDate = self.disableDate
|
||||||
if let subject = self.associatedData.subject, case let .messageOptions(_, _, info) = subject {
|
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 mergedTop: ChatMessageMerge = .none
|
||||||
var mergedBottom: ChatMessageMerge = .none
|
var mergedBottom: ChatMessageMerge = .none
|
||||||
var dateAtBottom = false
|
var dateAtBottom = false
|
||||||
@ -530,8 +541,10 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
|||||||
|
|
||||||
let nodeLayout = nodeValue.asyncLayout()
|
let nodeLayout = nodeValue.asyncLayout()
|
||||||
|
|
||||||
|
let isRotated = self.controllerInteraction.chatIsRotated
|
||||||
|
|
||||||
async {
|
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
|
var disableDate = self.disableDate
|
||||||
if let subject = self.associatedData.subject, case let .messageOptions(_, _, info) = subject {
|
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)?
|
open var awaitingAppliedReaction: (MessageReaction.Reaction?, () -> Void)?
|
||||||
|
|
||||||
public required convenience init() {
|
public required init(rotated: Bool) {
|
||||||
self.init(layerBacked: false)
|
super.init(layerBacked: false, dynamicBounce: true, rotated: rotated)
|
||||||
}
|
if rotated {
|
||||||
|
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
||||||
public init(layerBacked: Bool) {
|
}
|
||||||
super.init(layerBacked: layerBacked, dynamicBounce: true, rotated: true)
|
|
||||||
self.transform = CATransform3DMakeRotation(CGFloat.pi, 0.0, 0.0, 1.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init?(coder aDecoder: NSCoder) {
|
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?) {
|
override open func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
|
||||||
if let item = item as? ChatMessageItem {
|
if let item = item as? ChatMessageItem {
|
||||||
let doLayout = self.asyncLayout()
|
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)
|
let (layout, apply) = doLayout(item, params, merged.top, merged.bottom, merged.dateAtBottom)
|
||||||
self.contentSize = layout.contentSize
|
self.contentSize = layout.contentSize
|
||||||
self.insets = layout.insets
|
self.insets = layout.insets
|
||||||
|
@ -95,7 +95,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init() {
|
required public init(rotated: Bool) {
|
||||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||||
self.containerNode = ContextControllerSourceNode()
|
self.containerNode = ContextControllerSourceNode()
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
@ -104,7 +104,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView {
|
|||||||
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
self.dateAndStatusNode = ChatMessageDateAndStatusNode()
|
||||||
self.messageAccessibilityArea = AccessibilityAreaNode()
|
self.messageAccessibilityArea = AccessibilityAreaNode()
|
||||||
|
|
||||||
super.init(layerBacked: false)
|
super.init(rotated: rotated)
|
||||||
|
|
||||||
var firstTime = true
|
var firstTime = true
|
||||||
self.imageNode.imageUpdated = { [weak self] image in
|
self.imageNode.imageUpdated = { [weak self] image in
|
||||||
|
@ -259,6 +259,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol
|
|||||||
public var playNextOutgoingGift: Bool = false
|
public var playNextOutgoingGift: Bool = false
|
||||||
public var recommendedChannelsOpenUp: Bool = false
|
public var recommendedChannelsOpenUp: Bool = false
|
||||||
public var enableFullTranslucency: Bool = true
|
public var enableFullTranslucency: Bool = true
|
||||||
|
public var chatIsRotated: Bool = true
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
openMessage: @escaping (Message, OpenMessageParams) -> Bool,
|
openMessage: @escaping (Message, OpenMessageParams) -> Bool,
|
||||||
|
@ -22,6 +22,8 @@ swift_library(
|
|||||||
"//submodules/AppBundle",
|
"//submodules/AppBundle",
|
||||||
"//submodules/ChatListUI",
|
"//submodules/ChatListUI",
|
||||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode",
|
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoPaneNode",
|
||||||
|
"//submodules/DeleteChatPeerActionSheetItem",
|
||||||
|
"//submodules/UndoUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -14,6 +14,8 @@ import TelegramUIPreferences
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import PeerInfoPaneNode
|
import PeerInfoPaneNode
|
||||||
import ChatListUI
|
import ChatListUI
|
||||||
|
import DeleteChatPeerActionSheetItem
|
||||||
|
import UndoUI
|
||||||
|
|
||||||
public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScrollViewDelegate, UIGestureRecognizerDelegate {
|
||||||
private let context: AccountContext
|
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.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 {
|
deinit {
|
||||||
@ -268,3 +377,32 @@ public final class PeerInfoChatListPaneNode: ASDisplayNode, PeerInfoPaneNode, UI
|
|||||||
return result
|
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.navigationController = navigationController
|
||||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
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()
|
super.init()
|
||||||
|
|
||||||
@ -105,6 +105,9 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func transferVelocity(_ velocity: CGFloat) {
|
public func transferVelocity(_ velocity: CGFloat) {
|
||||||
|
if velocity > 0.0 {
|
||||||
|
self.chatController.transferScrollingVelocity(velocity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func cancelPreviewGestures() {
|
public func cancelPreviewGestures() {
|
||||||
@ -142,9 +145,10 @@ public final class PeerInfoChatPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
self.currentParams = (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData)
|
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 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)
|
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? {
|
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> {
|
public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<Bool, NoError> {
|
||||||
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
|
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
|
|> map { panes -> Bool in
|
||||||
if let panes {
|
if let panes {
|
||||||
return !panes.isEmpty
|
return !panes.isEmpty
|
||||||
@ -295,6 +295,22 @@ public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: Peer
|
|||||||
return false
|
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> {
|
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 hasSavedMessages: Signal<Bool, NoError>
|
||||||
|
let hasSavedMessagesChats: Signal<Bool, NoError>
|
||||||
if case .peer = chatLocation {
|
if case .peer = chatLocation {
|
||||||
hasSavedMessages = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: context.account.peerId, threadId: peerId.toInt64(), tag: MessageTags()))
|
hasSavedMessages = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: context.account.peerId, threadId: peerId.toInt64(), tag: MessageTags()))
|
||||||
|> map { count -> Bool in
|
|> map { count -> Bool in
|
||||||
@ -817,8 +834,15 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|
|
||||||
|
hasSavedMessagesChats = context.engine.messages.savedMessagesPeerListHead()
|
||||||
|
|> map { headPeerId -> Bool in
|
||||||
|
return headPeerId != nil
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
} else {
|
} else {
|
||||||
hasSavedMessages = .single(false)
|
hasSavedMessages = .single(false)
|
||||||
|
hasSavedMessagesChats = .single(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return combineLatest(
|
return combineLatest(
|
||||||
@ -830,9 +854,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
hasStories,
|
hasStories,
|
||||||
accountIsPremium,
|
accountIsPremium,
|
||||||
savedMessagesPeer,
|
savedMessagesPeer,
|
||||||
|
hasSavedMessagesChats,
|
||||||
hasSavedMessages
|
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
|
var availablePanes = availablePanes
|
||||||
|
|
||||||
if let hasStories {
|
if let hasStories {
|
||||||
@ -848,7 +873,9 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
|
|
||||||
if case .peer = chatLocation {
|
if case .peer = chatLocation {
|
||||||
if peerId == context.account.peerId {
|
if peerId == context.account.peerId {
|
||||||
availablePanes?.insert(.savedMessagesChats, at: 0)
|
if hasSavedMessagesChats {
|
||||||
|
availablePanes?.insert(.savedMessagesChats, at: 0)
|
||||||
|
}
|
||||||
} else if hasSavedMessages {
|
} else if hasSavedMessages {
|
||||||
if var availablePanesValue = availablePanes {
|
if var availablePanesValue = availablePanes {
|
||||||
if let index = availablePanesValue.firstIndex(of: .media) {
|
if let index = availablePanesValue.firstIndex(of: .media) {
|
||||||
@ -928,6 +955,21 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> 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(
|
return combineLatest(
|
||||||
context.account.viewTracker.peerView(peerId, updateData: true),
|
context.account.viewTracker.peerView(peerId, updateData: true),
|
||||||
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder),
|
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder),
|
||||||
@ -939,9 +981,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
requestsStatePromise.get(),
|
requestsStatePromise.get(),
|
||||||
hasStories,
|
hasStories,
|
||||||
accountIsPremium,
|
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
|
var availablePanes = availablePanes
|
||||||
if let hasStories {
|
if let hasStories {
|
||||||
if hasStories {
|
if hasStories {
|
||||||
@ -952,7 +995,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
}
|
}
|
||||||
|
|
||||||
if case .peer = chatLocation {
|
if case .peer = chatLocation {
|
||||||
if var availablePanesValue = availablePanes {
|
if hasSavedMessages, var availablePanesValue = availablePanes {
|
||||||
if let index = availablePanesValue.firstIndex(of: .media) {
|
if let index = availablePanesValue.firstIndex(of: .media) {
|
||||||
availablePanesValue.insert(.savedMessages, at: index + 1)
|
availablePanesValue.insert(.savedMessages, at: index + 1)
|
||||||
} else {
|
} else {
|
||||||
@ -1138,6 +1181,21 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|> 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(),
|
return combineLatest(queue: .mainQueue(),
|
||||||
context.account.viewTracker.peerView(groupId, updateData: true),
|
context.account.viewTracker.peerView(groupId, updateData: true),
|
||||||
peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder),
|
peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, chatLocationContextHolder: chatLocationContextHolder),
|
||||||
@ -1150,9 +1208,10 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
requestsStatePromise.get(),
|
requestsStatePromise.get(),
|
||||||
threadData,
|
threadData,
|
||||||
context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration]),
|
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?
|
var discussionPeer: Peer?
|
||||||
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
|
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
|
||||||
discussionPeer = peer
|
discussionPeer = peer
|
||||||
@ -1168,7 +1227,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
}
|
}
|
||||||
|
|
||||||
if case .peer = chatLocation {
|
if case .peer = chatLocation {
|
||||||
if var availablePanesValue = availablePanes {
|
if hasSavedMessages, var availablePanesValue = availablePanes {
|
||||||
if let index = availablePanesValue.firstIndex(of: .media) {
|
if let index = availablePanesValue.firstIndex(of: .media) {
|
||||||
availablePanesValue.insert(.savedMessages, at: index + 1)
|
availablePanesValue.insert(.savedMessages, at: index + 1)
|
||||||
} else {
|
} else {
|
||||||
|
@ -581,6 +581,12 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
|||||||
if strongSelf.tabsContainerNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.tabsContainerNode.view)) {
|
if strongSelf.tabsContainerNode.bounds.contains(strongSelf.view.convert(point, to: strongSelf.tabsContainerNode.view)) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
if case .savedMessagesChats = currentPaneKey {
|
||||||
|
if index == 0 {
|
||||||
|
return .leftCenter
|
||||||
|
}
|
||||||
|
return [.leftCenter, .rightCenter]
|
||||||
|
}
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
return .left
|
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
|
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
|
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
|
peerSelectionController.peerSelected = { [weak self, weak peerSelectionController] peer, threadId in
|
||||||
let peerId = peer.id
|
let peerId = peer.id
|
||||||
@ -9107,7 +9163,21 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
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)
|
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone, nil, nil)
|
||||||
|
|
||||||
|
@ -1090,13 +1090,28 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let controller = component.controller() {
|
if let controller = component.controller() {
|
||||||
|
let context = component.context
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
controller.present(UndoOverlayController(
|
controller.present(UndoOverlayController(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
content: .forward(savedMessages: savedMessages, text: text),
|
content: .forward(savedMessages: savedMessages, text: text),
|
||||||
elevatedLayout: false,
|
elevatedLayout: false,
|
||||||
animateInAsReplacement: 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)
|
), in: .current)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -321,6 +321,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
var preloadNextChatPeerId: PeerId?
|
var preloadNextChatPeerId: PeerId?
|
||||||
let preloadNextChatPeerIdDisposable = MetaDisposable()
|
let preloadNextChatPeerIdDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
var preloadSavedMessagesChatsDisposable: Disposable?
|
||||||
|
|
||||||
let botCallbackAlertMessage = Promise<String?>(nil)
|
let botCallbackAlertMessage = Promise<String?>(nil)
|
||||||
var botCallbackAlertMessageDisposable: Disposable?
|
var botCallbackAlertMessageDisposable: Disposable?
|
||||||
|
|
||||||
@ -2513,7 +2515,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()
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
@ -5698,6 +5714,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 {
|
} else if case let .replyThread(messagePromise) = self.chatLocationInfoData, let peerId = peerId {
|
||||||
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
|
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
|
||||||
|
|
||||||
@ -5891,7 +5911,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let mappedPeerData = ChatTitleContent.PeerData(
|
let mappedPeerData = ChatTitleContent.PeerData(
|
||||||
peerId: savedMessagesPeerId,
|
peerId: savedMessagesPeerId,
|
||||||
peer: savedMessagesPeer?.peer?._asPeer(),
|
peer: savedMessagesPeer?.peer?._asPeer(),
|
||||||
isContact: false,
|
isContact: true,
|
||||||
notificationSettings: nil,
|
notificationSettings: nil,
|
||||||
peerPresences: [:],
|
peerPresences: [:],
|
||||||
cachedData: nil
|
cachedData: nil
|
||||||
@ -6743,6 +6763,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
self.automaticMediaDownloadSettingsDisposable?.dispose()
|
self.automaticMediaDownloadSettingsDisposable?.dispose()
|
||||||
self.stickerSettingsDisposable?.dispose()
|
self.stickerSettingsDisposable?.dispose()
|
||||||
self.searchQuerySuggestionState?.1.dispose()
|
self.searchQuerySuggestionState?.1.dispose()
|
||||||
|
self.preloadSavedMessagesChatsDisposable?.dispose()
|
||||||
}
|
}
|
||||||
deallocate()
|
deallocate()
|
||||||
}
|
}
|
||||||
@ -7185,6 +7206,39 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.updateChatPresentationInterfaceState(interactive: false, { $0.updatedHasPlentyOfMessages(hasPlentyOfMessages) })
|
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
|
self.chatDisplayNode.historyNode.addContentOffset = { [weak self] offset, itemNode in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -11944,6 +11998,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func updateIsScrollingLockedAtTop(isScrollingLockedAtTop: Bool) {
|
||||||
|
self.chatDisplayNode.isScrollingLockedAtTop = isScrollingLockedAtTop
|
||||||
|
}
|
||||||
|
|
||||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
self.suspendNavigationBarLayout = true
|
self.suspendNavigationBarLayout = true
|
||||||
super.containerLayoutUpdated(layout, transition: transition)
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
@ -16375,7 +16433,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 {
|
switch mode {
|
||||||
@ -18910,6 +18982,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
)
|
)
|
||||||
self.push(controller)
|
self.push(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func transferScrollingVelocity(_ velocity: CGFloat) {
|
||||||
|
self.chatDisplayNode.historyNode.transferVelocity(velocity)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ChatContextControllerContentSourceImpl: ContextControllerContentSource {
|
final class ChatContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||||
|
@ -132,6 +132,8 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
let loadingNode: ChatLoadingNode
|
let loadingNode: ChatLoadingNode
|
||||||
private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode?
|
private(set) var loadingPlaceholderNode: ChatLoadingPlaceholderNode?
|
||||||
|
|
||||||
|
var isScrollingLockedAtTop: Bool = false
|
||||||
|
|
||||||
private var emptyNode: ChatEmptyNode?
|
private var emptyNode: ChatEmptyNode?
|
||||||
private(set) var emptyType: ChatHistoryNodeLoadState.EmptyType?
|
private(set) var emptyType: ChatHistoryNodeLoadState.EmptyType?
|
||||||
private var didDisplayEmptyGreeting = false
|
private var didDisplayEmptyGreeting = false
|
||||||
@ -579,15 +581,23 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
} else {
|
} else {
|
||||||
source = .default
|
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?)?
|
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?()
|
return getMessageTransitionNode?()
|
||||||
})
|
})
|
||||||
self.historyNode.rotated = true
|
|
||||||
|
|
||||||
//self.historyScrollingArea = SparseDiscreteScrollingArea()
|
|
||||||
//self.historyNode.historyScrollingArea = self.historyScrollingArea
|
|
||||||
|
|
||||||
self.historyNodeContainer = HistoryNodeContainer(isSecret: chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat)
|
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.backgroundColor = self.chatPresentationInterfaceState.theme.chat.inputMediaPanel.panelSeparatorColor
|
||||||
self.inputPanelBottomBackgroundSeparatorNode.isLayerBacked = true
|
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
|
self.navigateButtons.accessibilityElementsHidden = true
|
||||||
|
|
||||||
super.init()
|
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
|
var displayTopDimNode = false
|
||||||
let ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil
|
let ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil
|
||||||
var expandTopDimNode = false
|
var expandTopDimNode = false
|
||||||
@ -1923,6 +1939,17 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
strongSelf.notifyTransitionCompletionListeners(transition: transition)
|
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)
|
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)
|
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
|
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
|
var isInputExpansionEnabled = false
|
||||||
if case .media = self.chatPresentationInterfaceState.inputMode {
|
if case .media = self.chatPresentationInterfaceState.inputMode {
|
||||||
isInputExpansionEnabled = true
|
isInputExpansionEnabled = true
|
||||||
|
@ -621,6 +621,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
public private(set) var hasPlentyOfMessages: Bool = false
|
public private(set) var hasPlentyOfMessages: Bool = false
|
||||||
public var hasPlentyOfMessagesUpdated: ((Bool) -> Void)?
|
public var hasPlentyOfMessagesUpdated: ((Bool) -> Void)?
|
||||||
|
|
||||||
|
public private(set) var hasLotsOfMessages: Bool = false
|
||||||
|
public var hasLotsOfMessagesUpdated: ((Bool) -> Void)?
|
||||||
|
|
||||||
private var loadedMessagesFromCachedDataDisposable: Disposable?
|
private var loadedMessagesFromCachedDataDisposable: Disposable?
|
||||||
|
|
||||||
let isTopReplyThreadMessageShown = ValuePromise<Bool>(false, ignoreRepeated: true)
|
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 allowDustEffect: Bool = true
|
||||||
private var dustEffectLayer: DustEffectLayer?
|
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
|
var tagMask = tagMask
|
||||||
if case .pinnedMessages = subject {
|
if case .pinnedMessages = subject {
|
||||||
tagMask = .pinned
|
tagMask = .pinned
|
||||||
@ -738,6 +741,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
nextClientId += 1
|
nextClientId += 1
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
self.rotated = rotated
|
||||||
|
if rotated {
|
||||||
|
self.transform = CATransform3DMakeRotation(CGFloat(Double.pi), 0.0, 0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
self.clipsToBounds = false
|
self.clipsToBounds = false
|
||||||
|
|
||||||
@ -809,12 +817,6 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.preloadPages = false
|
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(
|
self.beginChatHistoryTransitions(
|
||||||
selectedMessages: selectedMessages,
|
selectedMessages: selectedMessages,
|
||||||
@ -3092,7 +3094,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
dustEffectLayer.bounds = CGRect(origin: CGPoint(), size: self.bounds.size)
|
dustEffectLayer.bounds = CGRect(origin: CGPoint(), size: self.bounds.size)
|
||||||
self.dustEffectLayer = dustEffectLayer
|
self.dustEffectLayer = dustEffectLayer
|
||||||
dustEffectLayer.zPosition = 10.0
|
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)
|
self.layer.addSublayer(dustEffectLayer)
|
||||||
dustEffectLayer.becameEmpty = { [weak self] in
|
dustEffectLayer.becameEmpty = { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -3310,13 +3314,18 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
}
|
}
|
||||||
|
|
||||||
var hasPlentyOfMessages = false
|
var hasPlentyOfMessages = false
|
||||||
|
var hasLotsOfMessages = false
|
||||||
if let historyView = strongSelf.historyView {
|
if let historyView = strongSelf.historyView {
|
||||||
if historyView.originalView.holeEarlier || historyView.originalView.holeLater {
|
if historyView.originalView.holeEarlier || historyView.originalView.holeLater {
|
||||||
hasPlentyOfMessages = true
|
hasPlentyOfMessages = true
|
||||||
|
hasLotsOfMessages = true
|
||||||
} else if !historyView.originalView.holeEarlier && !historyView.originalView.holeLater {
|
} else if !historyView.originalView.holeEarlier && !historyView.originalView.holeLater {
|
||||||
if historyView.filteredEntries.count >= 10 {
|
if historyView.filteredEntries.count >= 10 {
|
||||||
hasPlentyOfMessages = true
|
hasPlentyOfMessages = true
|
||||||
}
|
}
|
||||||
|
if historyView.filteredEntries.count >= 40 {
|
||||||
|
hasLotsOfMessages = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3324,6 +3333,10 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
strongSelf.hasPlentyOfMessages = hasPlentyOfMessages
|
strongSelf.hasPlentyOfMessages = hasPlentyOfMessages
|
||||||
strongSelf.hasPlentyOfMessagesUpdated?(hasPlentyOfMessages)
|
strongSelf.hasPlentyOfMessagesUpdated?(hasPlentyOfMessages)
|
||||||
}
|
}
|
||||||
|
if strongSelf.hasLotsOfMessages != hasLotsOfMessages {
|
||||||
|
strongSelf.hasLotsOfMessages = hasLotsOfMessages
|
||||||
|
strongSelf.hasLotsOfMessagesUpdated?(hasLotsOfMessages)
|
||||||
|
}
|
||||||
|
|
||||||
if let _ = visibleRange.loadedRange {
|
if let _ = visibleRange.loadedRange {
|
||||||
if let visible = visibleRange.visibleRange {
|
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)
|
overscrollView.frame = overscrollView.convert(overscrollView.bounds, to: self.view)
|
||||||
snapshotView.addSubview(overscrollView)
|
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(
|
return SnapshotState(
|
||||||
@ -4195,13 +4210,17 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
|
|||||||
|
|
||||||
let snapshotParentView = UIView()
|
let snapshotParentView = UIView()
|
||||||
snapshotParentView.addSubview(snapshotState.snapshotView)
|
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
|
snapshotParentView.frame = self.view.frame
|
||||||
|
|
||||||
snapshotState.snapshotView.frame = snapshotParentView.bounds
|
snapshotState.snapshotView.frame = snapshotParentView.bounds
|
||||||
|
|
||||||
snapshotState.snapshotView.clipsToBounds = true
|
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)
|
self.view.superview?.insertSubview(snapshotParentView, belowSubview: self.view)
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ private let badgeFont = Font.with(size: 13.0, traits: [.monospacedNumbers])
|
|||||||
|
|
||||||
enum ChatHistoryNavigationButtonType {
|
enum ChatHistoryNavigationButtonType {
|
||||||
case down
|
case down
|
||||||
|
case up
|
||||||
case mentions
|
case mentions
|
||||||
case reactions
|
case reactions
|
||||||
}
|
}
|
||||||
@ -60,6 +61,8 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
|||||||
switch type {
|
switch type {
|
||||||
case .down:
|
case .down:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme)
|
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme)
|
||||||
|
case .up:
|
||||||
|
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationUpButtonImage(theme)
|
||||||
case .mentions:
|
case .mentions:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme)
|
self.imageNode.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme)
|
||||||
case .reactions:
|
case .reactions:
|
||||||
@ -113,6 +116,8 @@ class ChatHistoryNavigationButtonNode: ContextControllerSourceNode {
|
|||||||
switch self.type {
|
switch self.type {
|
||||||
case .down:
|
case .down:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme)
|
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationButtonImage(theme)
|
||||||
|
case .up:
|
||||||
|
self.imageNode.image = PresentationResourcesChat.chatHistoryNavigationUpButtonImage(theme)
|
||||||
case .mentions:
|
case .mentions:
|
||||||
self.imageNode.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme)
|
self.imageNode.image = PresentationResourcesChat.chatHistoryMentionsButtonImage(theme)
|
||||||
case .reactions:
|
case .reactions:
|
||||||
|
@ -8,6 +8,7 @@ import WallpaperBackgroundNode
|
|||||||
final class ChatHistoryNavigationButtons: ASDisplayNode {
|
final class ChatHistoryNavigationButtons: ASDisplayNode {
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
private var dateTimeFormat: PresentationDateTimeFormat
|
private var dateTimeFormat: PresentationDateTimeFormat
|
||||||
|
private let isChatRotated: Bool
|
||||||
|
|
||||||
let reactionsButton: ChatHistoryNavigationButtonNode
|
let reactionsButton: ChatHistoryNavigationButtonNode
|
||||||
let mentionsButton: 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.theme = theme
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
|
|
||||||
@ -80,7 +82,7 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
self.reactionsButton.alpha = 0.0
|
self.reactionsButton.alpha = 0.0
|
||||||
self.reactionsButton.isHidden = true
|
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.alpha = 0.0
|
||||||
self.downButton.isHidden = true
|
self.downButton.isHidden = true
|
||||||
|
|
||||||
@ -186,11 +188,15 @@ final class ChatHistoryNavigationButtons: ASDisplayNode {
|
|||||||
transition.updateTransformScale(node: self.reactionsButton, scale: 0.2)
|
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)
|
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.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)
|
||||||
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 {
|
if let (rect, containerSize) = self.absoluteRect {
|
||||||
self.update(rect: rect, within: containerSize, transition: transition)
|
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) {
|
if let _ = self.openActiveTextItem, let textComponentView = self.textView.view, let result = textComponentView.hitTest(self.view.convert(point, to: textComponentView), with: event) {
|
||||||
return result
|
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
|
var eventIsPresses = false
|
||||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||||
|
@ -639,10 +639,19 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.animatedStickerNode = nil
|
self.animatedStickerNode = nil
|
||||||
|
|
||||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
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)
|
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||||
self.textNode.attributedText = attributedText
|
self.textNode.attributedText = attributedText
|
||||||
self.textNode.maximumNumberOfLines = 2
|
self.textNode.maximumNumberOfLines = 2
|
||||||
|
|
||||||
|
if savedMessages {
|
||||||
|
isUserInteractionEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
self.originalRemainingSeconds = 3
|
self.originalRemainingSeconds = 3
|
||||||
|
Loading…
x
Reference in New Issue
Block a user