mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
12c0603ac4
@ -36,7 +36,7 @@ func unreadMessages(account: Account) -> Signal<[INMessage], NoError> {
|
||||
|> mapToSignal { view -> Signal<[INMessage], NoError> in
|
||||
var signals: [Signal<[INMessage], NoError>] = []
|
||||
for entry in view.0.entries {
|
||||
if case let .MessageEntry(index, _, readState, isMuted, _, _, _, _, _, _, _, _) = entry {
|
||||
if case let .MessageEntry(index, _, readState, isMuted, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if index.messageIndex.id.peerId.namespace != Namespaces.Peer.CloudUser {
|
||||
continue
|
||||
}
|
||||
|
@ -8318,6 +8318,8 @@ Sorry for the inconvenience.";
|
||||
"Stickers.EmojiPackInfoText" = "This message contains **%@** emoji.";
|
||||
"PeerInfo.TopicIconInfoText" = "This topic's icon is from **%@**.";
|
||||
|
||||
"AutoremoveSetup.AdditionalGlobalSettingsInfo" = "You can also set your default [self-destruct timer]() for all chats in Settings.";
|
||||
|
||||
"Login.EnterCodeFragmentTitle" = "Enter Code";
|
||||
"Login.EnterCodeFragmentText" = "Check the Anonymous Numbers section on **Fragment** to get the code.";
|
||||
"Login.OpenFragment" = "Open Fragment";
|
||||
|
@ -179,6 +179,8 @@ public enum WallpaperUrlParameter {
|
||||
public enum ResolvedUrlSettingsSection {
|
||||
case theme
|
||||
case devices
|
||||
case autoremoveMessages
|
||||
case twoStepAuth
|
||||
}
|
||||
|
||||
public struct ResolvedBotChoosePeerTypes: OptionSet {
|
||||
@ -373,6 +375,17 @@ public enum ChatLocation: Equatable {
|
||||
case feed(id: Int32)
|
||||
}
|
||||
|
||||
public extension ChatLocation {
|
||||
var normalized: ChatLocation {
|
||||
switch self {
|
||||
case .peer, .feed:
|
||||
return self
|
||||
case let .replyThread(message):
|
||||
return .replyThread(message: message.normalized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ChatControllerActivateInput {
|
||||
case text
|
||||
case entityInput
|
||||
|
@ -36,10 +36,35 @@ public struct ContactMultiselectionControllerAdditionalCategories {
|
||||
}
|
||||
|
||||
public enum ContactMultiselectionControllerMode {
|
||||
public struct ChatSelection {
|
||||
public var title: String
|
||||
public var searchPlaceholder: String
|
||||
public var selectedChats: Set<PeerId>
|
||||
public var additionalCategories: ContactMultiselectionControllerAdditionalCategories?
|
||||
public var chatListFilters: [ChatListFilter]?
|
||||
public var displayAutoremoveTimeout: Bool
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
searchPlaceholder: String,
|
||||
selectedChats: Set<PeerId>,
|
||||
additionalCategories: ContactMultiselectionControllerAdditionalCategories?,
|
||||
chatListFilters: [ChatListFilter]?,
|
||||
displayAutoremoveTimeout: Bool = false
|
||||
) {
|
||||
self.title = title
|
||||
self.searchPlaceholder = searchPlaceholder
|
||||
self.selectedChats = selectedChats
|
||||
self.additionalCategories = additionalCategories
|
||||
self.chatListFilters = chatListFilters
|
||||
self.displayAutoremoveTimeout = displayAutoremoveTimeout
|
||||
}
|
||||
}
|
||||
|
||||
case groupCreation
|
||||
case peerSelection(searchChatList: Bool, searchGroups: Bool, searchChannels: Bool)
|
||||
case channelCreation
|
||||
case chatSelection(title: String, selectedChats: Set<PeerId>, additionalCategories: ContactMultiselectionControllerAdditionalCategories?, chatListFilters: [ChatListFilter]?)
|
||||
case chatSelection(ChatSelection)
|
||||
}
|
||||
|
||||
public enum ContactListFilter {
|
||||
@ -54,16 +79,18 @@ public final class ContactMultiselectionControllerParams {
|
||||
public let mode: ContactMultiselectionControllerMode
|
||||
public let options: [ContactListAdditionalOption]
|
||||
public let filters: [ContactListFilter]
|
||||
public let isPeerEnabled: ((EnginePeer) -> Bool)?
|
||||
public let alwaysEnabled: Bool
|
||||
public let limit: Int32?
|
||||
public let reachedLimit: ((Int32) -> Void)?
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter] = [.excludeSelf], isPeerEnabled: ((EnginePeer) -> Bool)? = nil, alwaysEnabled: Bool = false, limit: Int32? = nil, reachedLimit: ((Int32) -> Void)? = nil) {
|
||||
self.context = context
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.mode = mode
|
||||
self.options = options
|
||||
self.filters = filters
|
||||
self.isPeerEnabled = isPeerEnabled
|
||||
self.alwaysEnabled = alwaysEnabled
|
||||
self.limit = limit
|
||||
self.reachedLimit = reachedLimit
|
||||
|
@ -1254,7 +1254,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
chatController.canReadHistory.set(false)
|
||||
source = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
|
||||
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: true) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: source, items: chatForumTopicMenuItems(context: strongSelf.context, peerId: peer.peerId, threadId: threadId, isPinned: nil, isClosed: nil, chatListController: strongSelf, joined: joined, canSelect: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
strongSelf.presentInGlobalOverlay(contextController)
|
||||
} else {
|
||||
let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false)
|
||||
@ -1302,21 +1302,28 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return
|
||||
}
|
||||
|
||||
let contextContentSource: ContextContentSource
|
||||
if peer.id.namespace == Namespaces.Peer.SecretChat, let node = node.subnodes?.first as? ContextExtractedContentContainingNode {
|
||||
contextContentSource = .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: node, keepInPlace: false))
|
||||
if case let .channel(channel) = peer, channel.flags.contains(.isForum) {
|
||||
let chatListController = ChatListControllerImpl(context: strongSelf.context, location: .forum(peerId: channel.id), controlsHistoryPreload: false, hideNetworkActivityStatus: true, previewing: true, enableDebugActions: false)
|
||||
chatListController.navigationPresentation = .master
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatListController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController)), items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
strongSelf.presentInGlobalOverlay(contextController)
|
||||
} else {
|
||||
var subject: ChatControllerSubject?
|
||||
if case let .search(messageId) = source, let id = messageId {
|
||||
subject = .message(id: .id(id), highlight: false, timecode: nil)
|
||||
let contextContentSource: ContextContentSource
|
||||
if peer.id.namespace == Namespaces.Peer.SecretChat, let node = node.subnodes?.first as? ContextExtractedContentContainingNode {
|
||||
contextContentSource = .extracted(ChatListHeaderBarContextExtractedContentSource(controller: strongSelf, sourceNode: node, keepInPlace: false))
|
||||
} else {
|
||||
var subject: ChatControllerSubject?
|
||||
if case let .search(messageId) = source, let id = messageId {
|
||||
subject = .message(id: .id(id), highlight: false, timecode: nil)
|
||||
}
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, botStart: nil, mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
|
||||
}
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peer.id), subject: subject, botStart: nil, mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
contextContentSource = .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, navigationController: strongSelf.navigationController as? NavigationController))
|
||||
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
strongSelf.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: contextContentSource, items: chatContextMenuItems(context: strongSelf.context, peerId: peer.id, promoInfo: nil, source: .search(source), chatListController: strongSelf, joined: false) |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
strongSelf.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
self.tabContainerNode.tabSelected = { [weak self] id, isDisabled in
|
||||
@ -2048,8 +2055,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
|
||||
let navigationBarHeight = self.navigationBar?.frame.maxY ?? 0.0
|
||||
|
||||
transition.updateAlpha(node: self.tabContainerNode, alpha: self.chatListDisplayNode.inlineStackContainerTransitionFraction * 0.5 + (1.0 - self.chatListDisplayNode.inlineStackContainerTransitionFraction) * 1.0)
|
||||
self.tabContainerNode.isUserInteractionEnabled = self.chatListDisplayNode.inlineStackContainerNode == nil
|
||||
//transition.updateAlpha(node: self.tabContainerNode, alpha: self.chatListDisplayNode.inlineStackContainerTransitionFraction * 0.5 + (1.0 - self.chatListDisplayNode.inlineStackContainerTransitionFraction) * 1.0)
|
||||
//self.tabContainerNode.isUserInteractionEnabled = self.chatListDisplayNode.inlineStackContainerNode == nil
|
||||
|
||||
transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0)))
|
||||
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.mainContainerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.effectiveContainerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: self.chatListDisplayNode.effectiveContainerNode.currentItemNode.currentState.editing, canReorderAllChats: self.isPremium, filtersLimit: self.tabContainerData?.2, transitionFraction: self.chatListDisplayNode.effectiveContainerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
@ -4689,12 +4696,6 @@ private final class ChatListLocationContext {
|
||||
longTapped: {
|
||||
}
|
||||
)
|
||||
self.rightButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent(
|
||||
content: .text(title: presentationData.strings.Common_Done, isBold: true),
|
||||
pressed: { [weak self] _ in
|
||||
self?.parentController?.donePressed()
|
||||
}
|
||||
)))
|
||||
} else {
|
||||
self.chatTitleComponent = ChatTitleComponent(
|
||||
context: self.context,
|
||||
@ -4724,7 +4725,16 @@ private final class ChatListLocationContext {
|
||||
self.parentController?.activateSearch()
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
if stateAndFilterId.state.editing {
|
||||
self.rightButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent(
|
||||
content: .text(title: presentationData.strings.Common_Done, isBold: true),
|
||||
pressed: { [weak self] _ in
|
||||
self?.parentController?.donePressed()
|
||||
}
|
||||
)))
|
||||
} else {
|
||||
self.rightButton = AnyComponentWithIdentity(id: "more", component: AnyComponent(NavigationButtonComponent(
|
||||
content: .more,
|
||||
pressed: { [weak self] sourceView in
|
||||
|
@ -601,7 +601,13 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
|
||||
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_IncludeChatsTitle, selectedChats: Set(filterData.includePeers.peers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters), options: [], filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection(
|
||||
title: presentationData.strings.ChatListFolder_IncludeChatsTitle,
|
||||
searchPlaceholder: presentationData.strings.ChatListFilter_AddChatsTitle,
|
||||
selectedChats: Set(filterData.includePeers.peers),
|
||||
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
|
||||
chatListFilters: allFilters
|
||||
)), options: [], filters: [], alwaysEnabled: true, limit: isPremium ? premiumLimit : limit, reachedLimit: { count in
|
||||
if count >= premiumLimit {
|
||||
let limitController = PremiumLimitScreen(context: context, subject: .chatsPerFolder, count: min(premiumLimit, count), action: {})
|
||||
pushImpl?(limitController)
|
||||
@ -723,7 +729,13 @@ private func internalChatListFilterExcludeChatsController(context: AccountContex
|
||||
selectedCategories.insert(AdditionalExcludeCategoryId.archived.rawValue)
|
||||
}
|
||||
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(title: presentationData.strings.ChatListFolder_ExcludeChatsTitle, selectedChats: Set(filterData.excludePeers), additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories), chatListFilters: allFilters), options: [], filters: [], alwaysEnabled: true, limit: 100))
|
||||
let controller = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(context: context, mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection(
|
||||
title: presentationData.strings.ChatListFolder_ExcludeChatsTitle,
|
||||
searchPlaceholder: presentationData.strings.ChatListFilter_AddChatsTitle,
|
||||
selectedChats: Set(filterData.excludePeers),
|
||||
additionalCategories: ContactMultiselectionControllerAdditionalCategories(categories: additionalCategories, selectedCategories: selectedCategories),
|
||||
chatListFilters: allFilters
|
||||
)), options: [], filters: [], alwaysEnabled: true, limit: 100))
|
||||
controller.navigationPresentation = .modal
|
||||
let _ = (controller.result
|
||||
|> take(1)
|
||||
|
@ -157,9 +157,9 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
} else if case let .user(user) = primaryPeer {
|
||||
let servicePeer = isServicePeer(primaryPeer._asPeer())
|
||||
if user.flags.contains(.isSupport) && !servicePeer {
|
||||
status = .custom(string: strings.Bot_GenericSupportStatus, multiline: false)
|
||||
status = .custom(string: strings.Bot_GenericSupportStatus, multiline: false, isActive: false, icon: nil)
|
||||
} else if let _ = user.botInfo {
|
||||
status = .custom(string: strings.Bot_GenericBotStatus, multiline: false)
|
||||
status = .custom(string: strings.Bot_GenericBotStatus, multiline: false, isActive: false, icon: nil)
|
||||
} else if user.id != context.account.peerId && !servicePeer {
|
||||
let presence = peer.presence ?? TelegramUserPresence(status: .none, lastActivity: 0)
|
||||
status = .presence(EnginePeer.Presence(presence), timeFormat)
|
||||
@ -167,19 +167,19 @@ private enum ChatListRecentEntry: Comparable, Identifiable {
|
||||
status = .none
|
||||
}
|
||||
} else if case let .legacyGroup(group) = primaryPeer {
|
||||
status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(group.participantCount)), multiline: false)
|
||||
status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(group.participantCount)), multiline: false, isActive: false, icon: nil)
|
||||
} else if case let .channel(channel) = primaryPeer {
|
||||
if case .group = channel.info {
|
||||
if let count = peer.subpeerSummary?.count {
|
||||
status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(count)), multiline: false)
|
||||
status = .custom(string: strings.GroupInfo_ParticipantCount(Int32(count)), multiline: false, isActive: false, icon: nil)
|
||||
} else {
|
||||
status = .custom(string: strings.Group_Status, multiline: false)
|
||||
status = .custom(string: strings.Group_Status, multiline: false, isActive: false, icon: nil)
|
||||
}
|
||||
} else {
|
||||
if let count = peer.subpeerSummary?.count {
|
||||
status = .custom(string: strings.Conversation_StatusSubscribers(Int32(count)), multiline: false)
|
||||
status = .custom(string: strings.Conversation_StatusSubscribers(Int32(count)), multiline: false, isActive: false, icon: nil)
|
||||
} else {
|
||||
status = .custom(string: strings.Channel_Status, multiline: false)
|
||||
status = .custom(string: strings.Channel_Status, multiline: false, isActive: false, icon: nil)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -925,7 +925,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
private let presentationDataPromise = Promise<ChatListPresentationData>()
|
||||
private var searchStateValue = ChatListSearchListPaneNodeState()
|
||||
private let searchStatePromise = ValuePromise<ChatListSearchListPaneNodeState>()
|
||||
private let searchContextValue = Atomic<ChatListSearchMessagesContext?>(value: nil)
|
||||
private let searchContextsValue = Atomic<[Int: ChatListSearchMessagesContext]>(value: [:])
|
||||
var searchCurrentMessages: [EngineMessage]?
|
||||
var currentEntries: [ChatListSearchEntry]?
|
||||
|
||||
@ -1081,11 +1081,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
self.addSubnode(self.shimmerNode)
|
||||
self.addSubnode(self.mediaAccessoryPanelContainer)
|
||||
|
||||
let searchContext = Promise<ChatListSearchMessagesContext?>(nil)
|
||||
let searchContextValue = self.searchContextValue
|
||||
let updateSearchContext: ((ChatListSearchMessagesContext?) -> (ChatListSearchMessagesContext?, Bool)) -> Void = { f in
|
||||
let searchContexts = Promise<[Int: ChatListSearchMessagesContext]>([:])
|
||||
let searchContextsValue = self.searchContextsValue
|
||||
let updateSearchContexts: (([Int: ChatListSearchMessagesContext]) -> ([Int: ChatListSearchMessagesContext], Bool)) -> Void = { f in
|
||||
var shouldUpdate = false
|
||||
let updated = searchContextValue.modify { current in
|
||||
let updated = searchContextsValue.modify { current in
|
||||
let (u, s) = f(current)
|
||||
shouldUpdate = s
|
||||
if s {
|
||||
@ -1095,7 +1095,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
}
|
||||
if shouldUpdate {
|
||||
searchContext.set(.single(updated))
|
||||
searchContexts.set(.single(updated))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1419,32 +1419,32 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
} else {
|
||||
foundRemotePeers = .single(([], [], false))
|
||||
}
|
||||
let searchLocation: SearchMessagesLocation
|
||||
let searchLocations: [SearchMessagesLocation]
|
||||
if let options = options {
|
||||
if case let .forum(peerId) = location {
|
||||
searchLocation = .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1), .general(tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)]
|
||||
} else if let (peerId, _, _) = options.peer {
|
||||
searchLocation = .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: options.date?.0, maxDate: options.date?.1)]
|
||||
} else {
|
||||
if case let .chatList(groupId) = location, case .archive = groupId {
|
||||
searchLocation = .group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)]
|
||||
} else {
|
||||
searchLocation = .general(tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)
|
||||
searchLocations = [.general(tags: tagMask, minDate: options.date?.0, maxDate: options.date?.1)]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if case let .forum(peerId) = location {
|
||||
searchLocation = .peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: nil, maxDate: nil)
|
||||
searchLocations = [.peer(peerId: peerId, fromId: nil, tags: tagMask, topMsgId: nil, minDate: nil, maxDate: nil), .general(tags: tagMask, minDate: nil, maxDate: nil)]
|
||||
} else if case let .chatList(groupId) = location, case .archive = groupId {
|
||||
searchLocation = .group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil)
|
||||
searchLocations = [.group(groupId: groupId._asGroup(), tags: tagMask, minDate: nil, maxDate: nil)]
|
||||
} else {
|
||||
searchLocation = .general(tags: tagMask, minDate: nil, maxDate: nil)
|
||||
searchLocations = [.general(tags: tagMask, minDate: nil, maxDate: nil)]
|
||||
}
|
||||
}
|
||||
|
||||
let finalQuery = query ?? ""
|
||||
updateSearchContext { _ in
|
||||
return (nil, true)
|
||||
updateSearchContexts { _ in
|
||||
return ([:], true)
|
||||
}
|
||||
let foundRemoteMessages: Signal<(([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], [EngineMessage.Id: MessageHistoryThreadData], Int32), Bool), NoError>
|
||||
if peersFilter.contains(.doNotSearchMessages) {
|
||||
@ -1454,23 +1454,28 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
addAppLogEvent(postbox: context.account.postbox, type: "search_global_query")
|
||||
}
|
||||
|
||||
let searchSignal = context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: nil, limit: 50)
|
||||
|> map { result, updatedState -> ChatListSearchMessagesResult in
|
||||
let searchSignals: [Signal<(SearchMessagesResult, SearchMessagesState), NoError>] = searchLocations.map { searchLocation in
|
||||
return context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: nil, limit: 50)
|
||||
}
|
||||
|
||||
let searchSignal = combineLatest(searchSignals)
|
||||
|> map { results -> ChatListSearchMessagesResult in
|
||||
let (result, updatedState) = results[0]
|
||||
return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.map({ EngineMessage($0) }).sorted(by: { $0.index > $1.index }), readStates: result.readStates.mapValues { EnginePeerReadCounters(state: $0, isMuted: false) }, threadInfo: result.threadInfo, hasMore: !result.completed, totalCount: result.totalCount, state: updatedState)
|
||||
}
|
||||
|
||||
let loadMore = searchContext.get()
|
||||
|> mapToSignal { searchContext -> Signal<(([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], [EngineMessage.Id: MessageHistoryThreadData], Int32), Bool), NoError> in
|
||||
if let searchContext = searchContext, searchContext.result.hasMore {
|
||||
let loadMore = searchContexts.get()
|
||||
|> mapToSignal { searchContexts -> Signal<(([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], [EngineMessage.Id: MessageHistoryThreadData], Int32), Bool), NoError> in
|
||||
if let searchContext = searchContexts[0], searchContext.result.hasMore {
|
||||
if let _ = searchContext.loadMoreIndex {
|
||||
return context.engine.messages.searchMessages(location: searchLocation, query: finalQuery, state: searchContext.result.state, limit: 80)
|
||||
return context.engine.messages.searchMessages(location: searchLocations[0], query: finalQuery, state: searchContext.result.state, limit: 80)
|
||||
|> map { result, updatedState -> ChatListSearchMessagesResult in
|
||||
return ChatListSearchMessagesResult(query: finalQuery, messages: result.messages.map({ EngineMessage($0) }).sorted(by: { $0.index > $1.index }), readStates: result.readStates.mapValues { EnginePeerReadCounters(state: $0, isMuted: false) }, threadInfo: result.threadInfo, hasMore: !result.completed, totalCount: result.totalCount, state: updatedState)
|
||||
}
|
||||
|> mapToSignal { foundMessages -> Signal<(([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], [EngineMessage.Id: MessageHistoryThreadData], Int32), Bool), NoError> in
|
||||
updateSearchContext { previous in
|
||||
updateSearchContexts { previous in
|
||||
let updated = ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil)
|
||||
return (updated, true)
|
||||
return ([0: updated], true)
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
@ -1486,8 +1491,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|> then(
|
||||
searchSignal
|
||||
|> map { foundMessages -> (([EngineMessage], [EnginePeer.Id: EnginePeerReadCounters], [EngineMessage.Id: MessageHistoryThreadData], Int32), Bool) in
|
||||
updateSearchContext { _ in
|
||||
return (ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil), true)
|
||||
updateSearchContexts { _ in
|
||||
return ([0: ChatListSearchMessagesContext(result: foundMessages, loadMoreIndex: nil)], true)
|
||||
}
|
||||
return ((foundMessages.messages, foundMessages.readStates, foundMessages.threadInfo, foundMessages.totalCount), false)
|
||||
}
|
||||
@ -1510,7 +1515,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
})
|
||||
|
||||
let foundThreads: Signal<[EngineChatList.Item], NoError>
|
||||
if case .forum = location {
|
||||
if case .forum = location, (key == .topics || key == .chats) {
|
||||
foundThreads = chatListViewForLocation(chatListLocation: location, location: .initial(count: 1000, filter: nil), account: context.account)
|
||||
|> map { view -> [EngineChatList.Item] in
|
||||
var filteredItems: [EngineChatList.Item] = []
|
||||
@ -1773,7 +1778,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
}
|
||||
|
||||
let foundMessages = searchContext.get() |> map { searchContext -> ([EngineMessage], Int32, Bool) in
|
||||
let foundMessages = searchContexts.get() |> map { searchContexts -> ([EngineMessage], Int32, Bool) in
|
||||
let searchContext = searchContexts[0]
|
||||
if let result = searchContext?.result {
|
||||
return (result.messages, result.totalCount, result.hasMore)
|
||||
} else {
|
||||
@ -1782,17 +1788,17 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
|
||||
let loadMore = {
|
||||
updateSearchContext { previous in
|
||||
guard let previous = previous else {
|
||||
return (nil, false)
|
||||
updateSearchContexts { previousMap in
|
||||
guard let previous = previousMap[0] else {
|
||||
return ([:], false)
|
||||
}
|
||||
if previous.loadMoreIndex != nil {
|
||||
return (previous, false)
|
||||
return ([0: previous], false)
|
||||
}
|
||||
guard let last = previous.result.messages.last else {
|
||||
return (previous, false)
|
||||
return ([0: previous], false)
|
||||
}
|
||||
return (ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index), true)
|
||||
return ([0: ChatListSearchMessagesContext(result: previous.result, loadMoreIndex: last.index)], true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3302,7 +3308,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
|
||||
if let itemNode = itemNodes[sampleIndex] as? ChatListItemNode {
|
||||
if !isInlineMode {
|
||||
if !itemNode.avatarNode.isHidden {
|
||||
context.fillEllipse(in: itemNode.avatarNode.frame.offsetBy(dx: 0.0, dy: currentY))
|
||||
context.fillEllipse(in: itemNode.avatarNode.view.convert(itemNode.avatarNode.bounds, to: itemNode.view).offsetBy(dx: 0.0, dy: currentY))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1142,7 +1142,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
strongSelf.contextContainer.additionalActivationProgressLayer = nil
|
||||
if item.interaction.inlineNavigationLocation != nil {
|
||||
if let inlineNavigationLocation = item.interaction.inlineNavigationLocation {
|
||||
if case let .peer(peerId) = inlineNavigationLocation.location {
|
||||
if case let .chatList(index) = item.index, index.messageIndex.id.peerId == peerId {
|
||||
return false
|
||||
}
|
||||
}
|
||||
strongSelf.contextContainer.targetNodeForActivationProgress = strongSelf.avatarContainerNode
|
||||
} else if let value = strongSelf.hitTest(location, with: nil), value === strongSelf.compoundTextButtonNode?.view {
|
||||
strongSelf.contextContainer.targetNodeForActivationProgress = strongSelf.compoundTextButtonNode
|
||||
@ -1313,9 +1318,6 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
reallyHighlighted = true
|
||||
}
|
||||
}
|
||||
if item.interaction.inlineNavigationLocation != nil {
|
||||
reallyHighlighted = false
|
||||
}
|
||||
}
|
||||
return reallyHighlighted
|
||||
}
|
||||
@ -1403,7 +1405,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let textFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
|
||||
let dateFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
|
||||
let badgeFont = Font.with(size: floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers])
|
||||
let avatarBadgeFont = Font.with(size: 16.0, design: .regular, weight: .regular, traits: [.monospacedNumbers])
|
||||
let avatarBadgeFont = Font.with(size: floor(item.presentationData.fontSize.itemListBaseFontSize * 16.0 / 17.0), design: .regular, weight: .regular, traits: [.monospacedNumbers])
|
||||
|
||||
let account = item.context.account
|
||||
var messages: [EngineMessage]
|
||||
@ -1579,7 +1581,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
let badgeDiameter = floor(item.presentationData.fontSize.baseDisplaySize * 20.0 / 17.0)
|
||||
let avatarBadgeDiameter: CGFloat = 22.0
|
||||
let avatarBadgeDiameter: CGFloat = floor(floor(item.presentationData.fontSize.itemListBaseFontSize * 22.0 / 17.0))
|
||||
|
||||
let currentAvatarBadgeCleanBackgroundImage: UIImage? = PresentationResourcesChatList.badgeBackgroundBorder(item.presentationData.theme, diameter: avatarBadgeDiameter + 4.0)
|
||||
|
||||
@ -2354,7 +2356,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
itemHeight += titleSpacing
|
||||
itemHeight += authorSpacing
|
||||
|
||||
let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + 8.0), size: CGSize(width: rawContentWidth, height: itemHeight - 12.0 - 9.0))
|
||||
let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + floor(item.presentationData.fontSize.itemListBaseFontSize * 8.0 / 17.0)), size: CGSize(width: rawContentWidth, height: itemHeight - 12.0 - 9.0))
|
||||
|
||||
let insets = ChatListItemNode.insets(first: first, last: last, firstWithHeader: firstWithHeader)
|
||||
var heightOffset: CGFloat = 0.0
|
||||
@ -2407,7 +2409,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var mainContentAlpha: CGFloat = 1.0
|
||||
|
||||
if case .chatList = item.chatListLocation {
|
||||
mainContentFrame = CGRect(origin: CGPoint(x: params.leftInset + 72.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height))
|
||||
mainContentFrame = CGRect(origin: CGPoint(x: leftInset - 2.0, y: 0.0), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height))
|
||||
mainContentBoundsOffset = mainContentFrame.origin.x
|
||||
|
||||
if let inlineNavigationLocation = item.interaction.inlineNavigationLocation {
|
||||
@ -2497,7 +2499,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
var avatarScaleOffset: CGFloat = 0.0
|
||||
var avatarScale: CGFloat = 1.0
|
||||
if let inlineNavigationLocation = item.interaction.inlineNavigationLocation {
|
||||
let targetAvatarScale: CGFloat = 54.0 / avatarFrame.width
|
||||
let targetAvatarScale: CGFloat = floor(item.presentationData.fontSize.itemListBaseFontSize * 54.0 / 17.0) / avatarFrame.width
|
||||
avatarScale = targetAvatarScale * inlineNavigationLocation.progress + 1.0 * (1.0 - inlineNavigationLocation.progress)
|
||||
|
||||
let targetAvatarScaleOffset: CGFloat = -(avatarFrame.width - avatarFrame.width * avatarScale) * 0.5
|
||||
@ -2863,7 +2865,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
finalBottomRect
|
||||
], color: theme.pinnedItemBackgroundColor.mixedWith(theme.unreadBadgeInactiveBackgroundColor, alpha: 0.1))
|
||||
|
||||
compoundTextButtonNode.frame = compoundHighlightingNode.frame
|
||||
transition.updateFrame(node: compoundTextButtonNode, frame: compoundHighlightingNode.frame)
|
||||
|
||||
if let textArrowImage = textArrowImage {
|
||||
let textArrowNode: ASImageNode
|
||||
@ -3043,9 +3045,11 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
let titlePosition = strongSelf.titleNode.position
|
||||
transition.animatePosition(node: strongSelf.titleNode, from: CGPoint(x: titlePosition.x - contentDelta.x, y: titlePosition.y - contentDelta.y))
|
||||
|
||||
transition.animatePositionAdditive(node: strongSelf.textNode.textNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y))
|
||||
if let dustNode = strongSelf.dustNode {
|
||||
transition.animatePositionAdditive(node: dustNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y))
|
||||
if strongSelf.textNode.textNode.supernode === strongSelf.mainContentContainerNode {
|
||||
transition.animatePositionAdditive(node: strongSelf.textNode.textNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y))
|
||||
if let dustNode = strongSelf.dustNode {
|
||||
transition.animatePositionAdditive(node: dustNode, offset: CGPoint(x: -contentDelta.x, y: -contentDelta.y))
|
||||
}
|
||||
}
|
||||
|
||||
let authorPosition = strongSelf.authorNode.position
|
||||
|
@ -20,7 +20,7 @@ import Postbox
|
||||
|
||||
public enum ChatListNodeMode {
|
||||
case chatList
|
||||
case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?)
|
||||
case peers(filter: ChatListNodePeersFilter, isSelecting: Bool, additionalCategories: [ChatListNodeAdditionalCategory], chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool)
|
||||
}
|
||||
|
||||
struct ChatListNodeListViewTransition {
|
||||
@ -292,7 +292,7 @@ public struct ChatListNodeState: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] {
|
||||
private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionInsertEntry]) -> [ListViewInsertItem] {
|
||||
return entries.map { entry -> ListViewInsertItem in
|
||||
switch entry.entry {
|
||||
case .HeaderEntry:
|
||||
@ -315,7 +315,28 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
nodeInteraction.additionalCategorySelected(id)
|
||||
}
|
||||
), directionHint: entry.directionHint)
|
||||
case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems, revealed):
|
||||
case let .PeerEntry(peerEntry):
|
||||
let index = peerEntry.index
|
||||
let presentationData = peerEntry.presentationData
|
||||
let combinedReadState = peerEntry.readState
|
||||
let isRemovedFromTotalUnreadCount = peerEntry.isRemovedFromTotalUnreadCount
|
||||
let draftState = peerEntry.draftState
|
||||
let peer = peerEntry.peer
|
||||
let threadInfo = peerEntry.threadInfo
|
||||
let presence = peerEntry.presence
|
||||
let hasUnseenMentions = peerEntry.hasUnseenMentions
|
||||
let hasUnseenReactions = peerEntry.hasUnseenReactions
|
||||
let editing = peerEntry.editing
|
||||
let hasActiveRevealControls = peerEntry.hasActiveRevealControls
|
||||
let selected = peerEntry.selected
|
||||
let inputActivities = peerEntry.inputActivities
|
||||
let promoInfo = peerEntry.promoInfo
|
||||
let hasFailedMessages = peerEntry.hasFailedMessages
|
||||
let isContact = peerEntry.isContact
|
||||
let forumTopicData = peerEntry.forumTopicData
|
||||
let topForumTopicItems = peerEntry.topForumTopicItems
|
||||
let revealed = peerEntry.revealed
|
||||
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
|
||||
@ -325,7 +346,7 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
filterData: filterData,
|
||||
index: index,
|
||||
content: .peer(
|
||||
messages: messages,
|
||||
messages: peerEntry.messages,
|
||||
peer: peer,
|
||||
threadInfo: threadInfo,
|
||||
combinedReadState: combinedReadState,
|
||||
@ -350,84 +371,90 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
hiddenOffset: threadInfo?.isHidden == true && !revealed,
|
||||
interaction: nodeInteraction
|
||||
), directionHint: entry.directionHint)
|
||||
case let .peers(filter, isSelecting, _, filters):
|
||||
case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: EnginePeer?
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
chatPeer = peer
|
||||
}
|
||||
var enabled = true
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if !canSendMessagesToPeer(peer._asPeer()) {
|
||||
if let isPeerEnabled {
|
||||
if let itemPeer {
|
||||
enabled = isPeerEnabled(itemPeer)
|
||||
}
|
||||
} else {
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if !canSendMessagesToPeer(peer._asPeer()) {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
|
||||
if let threadInfo, threadInfo.isClosed, case let .channel(channel) = itemPeer {
|
||||
if threadInfo.isOwnedByMe || channel.hasPermission(.manageTopics) {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let threadInfo, threadInfo.isClosed, case let .channel(channel) = itemPeer {
|
||||
if threadInfo.isOwnedByMe || channel.hasPermission(.manageTopics) {
|
||||
if filter.contains(.onlyPrivateChats) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
switch peer {
|
||||
case .user, .secretChat:
|
||||
break
|
||||
default:
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if filter.contains(.onlyPrivateChats) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
switch peer {
|
||||
case .user, .secretChat:
|
||||
break
|
||||
default:
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.onlyGroups) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if case .legacyGroup = peer {
|
||||
} else if case let .channel(peer) = peer, case .group = peer.info {
|
||||
if filter.contains(.onlyGroups) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if case .legacyGroup = peer {
|
||||
} else if case let .channel(peer) = peer, case .group = peer.info {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.onlyManageable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
var canManage = false
|
||||
if case let .legacyGroup(peer) = peer {
|
||||
switch peer.role {
|
||||
if filter.contains(.onlyManageable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
var canManage = false
|
||||
if case let .legacyGroup(peer) = peer {
|
||||
switch peer.role {
|
||||
case .creator, .admin:
|
||||
canManage = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if canManage {
|
||||
} else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
if canManage {
|
||||
} else if case let .channel(peer) = peer, case .group = peer.info, peer.hasPermission(.inviteMembers) {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
if filter.contains(.excludeChannels) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if case let .channel(peer) = peer, case .broadcast = peer.info {
|
||||
enabled = false
|
||||
if filter.contains(.excludeChannels) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if case let .channel(peer) = peer, case .broadcast = peer.info {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var header: ChatListSearchItemHeader?
|
||||
switch mode {
|
||||
case let .peers(_, _, additionalCategories, _):
|
||||
case let .peers(_, _, additionalCategories, _, _):
|
||||
if !additionalCategories.isEmpty {
|
||||
let headerType: ChatListSearchItemHeaderType
|
||||
if case .action = additionalCategories[0].appearance {
|
||||
@ -444,8 +471,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
|
||||
var status: ContactsPeerItemStatus = .none
|
||||
if isSelecting, let itemPeer = itemPeer {
|
||||
if let (string, multiline) = statusStringForPeerType(accountPeerId: context.account.peerId, strings: presentationData.strings, peer: itemPeer, isMuted: isRemovedFromTotalUnreadCount, isUnread: combinedReadState?.isUnread ?? false, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: filters) {
|
||||
status = .custom(string: string, multiline: multiline)
|
||||
if let (string, multiline, isActive, icon) = statusStringForPeerType(accountPeerId: context.account.peerId, strings: presentationData.strings, peer: itemPeer, isMuted: isRemovedFromTotalUnreadCount, isUnread: combinedReadState?.isUnread ?? false, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: filters, displayAutoremoveTimeout: displayAutoremoveTimeout, autoremoveTimeout: peerEntry.autoremoveTimeout) {
|
||||
status = .custom(string: string, multiline: multiline, isActive: isActive, icon: icon)
|
||||
} else {
|
||||
status = .none
|
||||
}
|
||||
@ -541,10 +568,31 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
}
|
||||
}
|
||||
|
||||
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
|
||||
private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, entries: [ChatListNodeViewTransitionUpdateEntry]) -> [ListViewUpdateItem] {
|
||||
return entries.map { entry -> ListViewUpdateItem in
|
||||
switch entry.entry {
|
||||
case let .PeerEntry(index, presentationData, messages, combinedReadState, isRemovedFromTotalUnreadCount, draftState, peer, threadInfo, presence, hasUnseenMentions, hasUnseenReactions, editing, hasActiveRevealControls, selected, inputActivities, promoInfo, hasFailedMessages, isContact, forumTopicData, topForumTopicItems, revealed):
|
||||
case let .PeerEntry(peerEntry):
|
||||
let index = peerEntry.index
|
||||
let presentationData = peerEntry.presentationData
|
||||
let combinedReadState = peerEntry.readState
|
||||
let isRemovedFromTotalUnreadCount = peerEntry.isRemovedFromTotalUnreadCount
|
||||
let draftState = peerEntry.draftState
|
||||
let peer = peerEntry.peer
|
||||
let threadInfo = peerEntry.threadInfo
|
||||
let presence = peerEntry.presence
|
||||
let hasUnseenMentions = peerEntry.hasUnseenMentions
|
||||
let hasUnseenReactions = peerEntry.hasUnseenReactions
|
||||
let editing = peerEntry.editing
|
||||
let hasActiveRevealControls = peerEntry.hasActiveRevealControls
|
||||
let selected = peerEntry.selected
|
||||
let inputActivities = peerEntry.inputActivities
|
||||
let promoInfo = peerEntry.promoInfo
|
||||
let hasFailedMessages = peerEntry.hasFailedMessages
|
||||
let isContact = peerEntry.isContact
|
||||
let forumTopicData = peerEntry.forumTopicData
|
||||
let topForumTopicItems = peerEntry.topForumTopicItems
|
||||
let revealed = peerEntry.revealed
|
||||
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListItem(
|
||||
@ -554,7 +602,7 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
filterData: filterData,
|
||||
index: index,
|
||||
content: .peer(
|
||||
messages: messages,
|
||||
messages: peerEntry.messages,
|
||||
peer: peer,
|
||||
threadInfo: threadInfo,
|
||||
combinedReadState: combinedReadState,
|
||||
@ -579,37 +627,44 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
hiddenOffset: threadInfo?.isHidden == true && !revealed,
|
||||
interaction: nodeInteraction
|
||||
), directionHint: entry.directionHint)
|
||||
case let .peers(filter, isSelecting, _, filters):
|
||||
case let .peers(filter, isSelecting, _, filters, displayAutoremoveTimeout):
|
||||
let itemPeer = peer.chatMainPeer
|
||||
var chatPeer: EnginePeer?
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
chatPeer = peer
|
||||
}
|
||||
var enabled = true
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if !canSendMessagesToPeer(peer._asPeer()) {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
if let isPeerEnabled {
|
||||
if let itemPeer {
|
||||
enabled = isPeerEnabled(itemPeer)
|
||||
}
|
||||
|
||||
if let threadInfo, threadInfo.isClosed, case let .channel(channel) = itemPeer {
|
||||
if threadInfo.isOwnedByMe || channel.hasPermission(.manageTopics) {
|
||||
} else {
|
||||
if filter.contains(.onlyWriteable) {
|
||||
if let peer = peer.peers[peer.peerId] {
|
||||
if !canSendMessagesToPeer(peer._asPeer()) {
|
||||
enabled = false
|
||||
}
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
if let threadInfo, threadInfo.isClosed, case let .channel(channel) = itemPeer {
|
||||
if threadInfo.isOwnedByMe || channel.hasPermission(.manageTopics) {
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if filter.contains(.excludeChannels) {
|
||||
if case let .channel(peer) = peer.chatMainPeer, case .broadcast = peer.info {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if filter.contains(.excludeChannels) {
|
||||
if case let .channel(peer) = peer.chatMainPeer, case .broadcast = peer.info {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
var header: ChatListSearchItemHeader?
|
||||
switch mode {
|
||||
case let .peers(_, _, additionalCategories, _):
|
||||
case let .peers(_, _, additionalCategories, _, _):
|
||||
if !additionalCategories.isEmpty {
|
||||
let headerType: ChatListSearchItemHeaderType
|
||||
if case .action = additionalCategories[0].appearance {
|
||||
@ -626,8 +681,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
|
||||
var status: ContactsPeerItemStatus = .none
|
||||
if isSelecting, let itemPeer = itemPeer {
|
||||
if let (string, multiline) = statusStringForPeerType(accountPeerId: context.account.peerId, strings: presentationData.strings, peer: itemPeer, isMuted: isRemovedFromTotalUnreadCount, isUnread: combinedReadState?.isUnread ?? false, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: filters) {
|
||||
status = .custom(string: string, multiline: multiline)
|
||||
if let (string, multiline, isActive, icon) = statusStringForPeerType(accountPeerId: context.account.peerId, strings: presentationData.strings, peer: itemPeer, isMuted: isRemovedFromTotalUnreadCount, isUnread: combinedReadState?.isUnread ?? false, isContact: isContact, hasUnseenMentions: hasUnseenMentions, chatListFilters: filters, displayAutoremoveTimeout: displayAutoremoveTimeout, autoremoveTimeout: peerEntry.autoremoveTimeout) {
|
||||
status = .custom(string: string, multiline: multiline, isActive: isActive, icon: icon)
|
||||
} else {
|
||||
status = .none
|
||||
}
|
||||
@ -743,8 +798,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
}
|
||||
}
|
||||
|
||||
private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition {
|
||||
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade)
|
||||
private func mappedChatListNodeViewListTransition(context: AccountContext, nodeInteraction: ChatListNodeInteraction, location: ChatListControllerLocation, filterData: ChatListItemFilterData?, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)?, transition: ChatListNodeViewTransition) -> ChatListNodeListViewTransition {
|
||||
return ChatListNodeListViewTransition(chatListView: transition.chatListView, deleteItems: transition.deleteItems, insertItems: mappedInsertEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.insertEntries), updateItems: mappedUpdateEntries(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, isPeerEnabled: isPeerEnabled, entries: transition.updateEntries), options: transition.options, scrollToItem: transition.scrollToItem, stationaryItemRange: transition.stationaryItemRange, adjustScrollToFirstItem: transition.adjustScrollToFirstItem, animateCrossfade: transition.animateCrossfade)
|
||||
}
|
||||
|
||||
private final class ChatListOpaqueTransactionState {
|
||||
@ -907,7 +962,7 @@ public final class ChatListNode: ListView {
|
||||
|
||||
let hideArhiveIntro = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
|
||||
public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool) {
|
||||
public init(context: AccountContext, location: ChatListControllerLocation, chatListFilter: ChatListFilter? = nil, previewing: Bool, fillPreloadItems: Bool, mode: ChatListNodeMode, isPeerEnabled: ((EnginePeer) -> Bool)? = nil, theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, disableAnimations: Bool, isInlineMode: Bool) {
|
||||
self.context = context
|
||||
self.location = location
|
||||
self.chatListFilter = chatListFilter
|
||||
@ -918,7 +973,7 @@ public final class ChatListNode: ListView {
|
||||
self.animationRenderer = animationRenderer
|
||||
|
||||
var isSelecting = false
|
||||
if case .peers(_, true, _, _) = mode {
|
||||
if case .peers(_, true, _, _, _) = mode {
|
||||
isSelecting = true
|
||||
}
|
||||
|
||||
@ -950,6 +1005,12 @@ public final class ChatListNode: ListView {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if case .peers = strongSelf.mode {
|
||||
if let strongSelf = self, let peerSelected = strongSelf.peerSelected {
|
||||
peerSelected(peer, nil, true, true, nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
var didBeginSelecting = false
|
||||
var count = 0
|
||||
strongSelf.updateState { [weak self] state in
|
||||
@ -1213,7 +1274,7 @@ public final class ChatListNode: ListView {
|
||||
let currentRemovingItemId = self.currentRemovingItemId
|
||||
|
||||
let savedMessagesPeer: Signal<EnginePeer?, NoError>
|
||||
if case let .peers(filter, _, _, _) = mode, filter.contains(.onlyWriteable), case .chatList = location {
|
||||
if case let .peers(filter, _, _, _, _) = mode, filter.contains(.onlyWriteable), case .chatList = location {
|
||||
savedMessagesPeer = context.account.postbox.loadedPeerWithId(context.account.peerId)
|
||||
|> map(Optional.init)
|
||||
|> map { peer in
|
||||
@ -1269,11 +1330,13 @@ public final class ChatListNode: ListView {
|
||||
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location)
|
||||
let entries = rawEntries.filter { entry in
|
||||
switch entry {
|
||||
case let .PeerEntry(_, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .PeerEntry(peerEntry):
|
||||
let peer = peerEntry.peer
|
||||
|
||||
switch mode {
|
||||
case .chatList:
|
||||
return true
|
||||
case let .peers(filter, _, _, _):
|
||||
case let .peers(filter, _, _, _, _):
|
||||
guard !filter.contains(.excludeSavedMessages) || peer.peerId != currentPeerId else { return false }
|
||||
guard !filter.contains(.excludeSavedMessages) || !peer.peerId.isReplies else { return false }
|
||||
guard !filter.contains(.excludeSecretChats) || peer.peerId.namespace != Namespaces.Peer.SecretChat else { return false }
|
||||
@ -1444,7 +1507,10 @@ public final class ChatListNode: ListView {
|
||||
var didIncludeHiddenThread = false
|
||||
if let previous = previousView {
|
||||
for entry in previous.filteredEntries {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if case let .PeerEntry(peerEntry) = entry {
|
||||
let index = peerEntry.index
|
||||
let threadInfo = peerEntry.threadInfo
|
||||
|
||||
if let threadInfo, threadInfo.isHidden {
|
||||
didIncludeHiddenThread = true
|
||||
}
|
||||
@ -1474,7 +1540,10 @@ public final class ChatListNode: ListView {
|
||||
|
||||
var doesIncludeHiddenThread = false
|
||||
for entry in processedView.filteredEntries {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry {
|
||||
if case let .PeerEntry(peerEntry) = entry {
|
||||
let index = peerEntry.index
|
||||
let threadInfo = peerEntry.threadInfo
|
||||
|
||||
if let threadInfo, threadInfo.isHidden {
|
||||
doesIncludeHiddenThread = true
|
||||
}
|
||||
@ -1547,7 +1616,7 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
|
||||
return preparedChatListNodeViewTransition(from: previousView, to: processedView, reason: reason, previewing: previewing, disableAnimations: disableAnimations, account: context.account, scrollPosition: updatedScrollPosition, searchMode: searchMode)
|
||||
|> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, transition: $0) })
|
||||
|> map({ mappedChatListNodeViewListTransition(context: context, nodeInteraction: nodeInteraction, location: location, filterData: filterData, mode: mode, isPeerEnabled: isPeerEnabled, transition: $0) })
|
||||
|> runOn(prepareOnMainQueue ? Queue.mainQueue() : viewProcessingQueue)
|
||||
}
|
||||
|
||||
@ -1585,7 +1654,9 @@ public final class ChatListNode: ListView {
|
||||
continue
|
||||
}
|
||||
switch chatListView.filteredEntries[entryCount - i - 1] {
|
||||
case let .PeerEntry(_, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .PeerEntry(peerEntry):
|
||||
let threadInfo = peerEntry.threadInfo
|
||||
|
||||
if let threadInfo, threadInfo.isHidden {
|
||||
isHiddenItemVisible = true
|
||||
}
|
||||
@ -1792,7 +1863,10 @@ public final class ChatListNode: ListView {
|
||||
var referenceId: EngineChatList.PinnedItem.Id?
|
||||
var beforeAll = false
|
||||
switch toEntry {
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _):
|
||||
case let .PeerEntry(peerEntry):
|
||||
let index = peerEntry.index
|
||||
let promoInfo = peerEntry.promoInfo
|
||||
|
||||
if promoInfo != nil {
|
||||
beforeAll = true
|
||||
} else {
|
||||
@ -1819,8 +1893,8 @@ public final class ChatListNode: ListView {
|
||||
|
||||
var itemId: EngineChatList.PinnedItem.Id?
|
||||
switch fromEntry {
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if case let .chatList(index) = index {
|
||||
case let .PeerEntry(peerEntry):
|
||||
if case let .chatList(index) = peerEntry.index {
|
||||
itemId = .peer(index.messageIndex.id.peerId)
|
||||
}
|
||||
default:
|
||||
@ -1865,11 +1939,11 @@ public final class ChatListNode: ListView {
|
||||
var referenceId: Int64?
|
||||
var beforeAll = false
|
||||
switch toEntry {
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _):
|
||||
if promoInfo != nil {
|
||||
case let .PeerEntry(peerEntry):
|
||||
if peerEntry.promoInfo != nil {
|
||||
beforeAll = true
|
||||
} else {
|
||||
if case let .forum(_, _, threadId, _, _) = index {
|
||||
if case let .forum(_, _, threadId, _, _) = peerEntry.index {
|
||||
referenceId = threadId
|
||||
}
|
||||
}
|
||||
@ -1885,8 +1959,8 @@ public final class ChatListNode: ListView {
|
||||
|
||||
var itemId: Int64?
|
||||
switch fromEntry {
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if case let .forum(_, _, threadId, _, _) = index {
|
||||
case let .PeerEntry(peerEntry):
|
||||
if case let .forum(_, _, threadId, _, _) = peerEntry.index {
|
||||
itemId = threadId
|
||||
}
|
||||
default:
|
||||
@ -2154,15 +2228,15 @@ public final class ChatListNode: ListView {
|
||||
if !transition.chatListView.originalList.hasLater {
|
||||
for entry in filteredEntries.reversed() {
|
||||
switch entry {
|
||||
case let .PeerEntry(index, _, _, combinedReadState, isMuted, _, _, _, _, _, _, _, _, _, _, promoInfo, _, _, _, _, _):
|
||||
if promoInfo == nil {
|
||||
case let .PeerEntry(peerEntry):
|
||||
if peerEntry.promoInfo == nil {
|
||||
var hasUnread = false
|
||||
if let combinedReadState = combinedReadState {
|
||||
if let combinedReadState = peerEntry.readState {
|
||||
hasUnread = combinedReadState.count > 0
|
||||
}
|
||||
switch index {
|
||||
switch peerEntry.index {
|
||||
case let .chatList(index):
|
||||
preloadItems.append(ChatHistoryPreloadItem(index: index, threadId: nil, isMuted: isMuted, hasUnread: hasUnread))
|
||||
preloadItems.append(ChatHistoryPreloadItem(index: index, threadId: nil, isMuted: peerEntry.isRemovedFromTotalUnreadCount, hasUnread: hasUnread))
|
||||
case .forum:
|
||||
break
|
||||
}
|
||||
@ -2271,7 +2345,7 @@ public final class ChatListNode: ListView {
|
||||
case .GroupReferenceEntry, .HoleEntry, .PeerEntry:
|
||||
containsChats = true
|
||||
if case .forum = strongSelf.location {
|
||||
if case let .PeerEntry(_, _, _, _, _, _, _, threadInfo, _, _, _, _, _, _, _, _, _, _, _, _, _) = entry, let threadInfo {
|
||||
if case let .PeerEntry(peerEntry) = entry, let threadInfo = peerEntry.threadInfo {
|
||||
if threadInfo.id == 1 {
|
||||
hasGeneral = true
|
||||
}
|
||||
@ -2527,9 +2601,9 @@ public final class ChatListNode: ListView {
|
||||
continue
|
||||
}
|
||||
switch chatListView.filteredEntries[entryCount - i - 1] {
|
||||
case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if interaction.highlightedChatLocation?.location == ChatLocation.peer(id: peer.peerId) {
|
||||
current = (index, peer.peer!, entryCount - i - 1)
|
||||
case let .PeerEntry(peerEntry):
|
||||
if interaction.highlightedChatLocation?.location == ChatLocation.peer(id: peerEntry.peer.peerId) {
|
||||
current = (peerEntry.index, peerEntry.peer.peer!, entryCount - i - 1)
|
||||
break outer
|
||||
}
|
||||
default:
|
||||
@ -2574,11 +2648,11 @@ public final class ChatListNode: ListView {
|
||||
case .previous(unread: false), .next(unread: false):
|
||||
var target: (EngineChatList.Item.Index, EnginePeer)? = nil
|
||||
if let current = current, entryCount > 1 {
|
||||
if current.2 > 0, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 - 1] {
|
||||
next = (index, peer.peer!)
|
||||
if current.2 > 0, case let .PeerEntry(peerEntry) = chatListView.filteredEntries[current.2 - 1] {
|
||||
next = (peerEntry.index, peerEntry.peer.peer!)
|
||||
}
|
||||
if current.2 <= entryCount - 2, case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[current.2 + 1] {
|
||||
previous = (index, peer.peer!)
|
||||
if current.2 <= entryCount - 2, case let .PeerEntry(peerEntry) = chatListView.filteredEntries[current.2 + 1] {
|
||||
previous = (peerEntry.index, peerEntry.peer.peer!)
|
||||
}
|
||||
if case .previous = option {
|
||||
target = previous
|
||||
@ -2586,8 +2660,8 @@ public final class ChatListNode: ListView {
|
||||
target = next
|
||||
}
|
||||
} else if entryCount > 0 {
|
||||
if case let .PeerEntry(index, _, _, _, _, _, peer, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = chatListView.filteredEntries[entryCount - 1] {
|
||||
target = (index, peer.peer!)
|
||||
if case let .PeerEntry(peerEntry) = chatListView.filteredEntries[entryCount - 1] {
|
||||
target = (peerEntry.index, peerEntry.peer.peer!)
|
||||
}
|
||||
}
|
||||
if let target = target {
|
||||
@ -2664,8 +2738,8 @@ public final class ChatListNode: ListView {
|
||||
continue
|
||||
}
|
||||
switch chatListView.filteredEntries[entryCount - i - 1] {
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return index
|
||||
case let .PeerEntry(peerEntry):
|
||||
return peerEntry.index
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -2889,11 +2963,21 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
}
|
||||
|
||||
private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: PresentationStrings, peer: EnginePeer, isMuted: Bool, isUnread: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?) -> (String, Bool)? {
|
||||
private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: PresentationStrings, peer: EnginePeer, isMuted: Bool, isUnread: Bool, isContact: Bool, hasUnseenMentions: Bool, chatListFilters: [ChatListFilter]?, displayAutoremoveTimeout: Bool, autoremoveTimeout: Int32?) -> (String, Bool, Bool, ContactsPeerItemStatus.Icon?)? {
|
||||
if accountPeerId == peer.id {
|
||||
return nil
|
||||
}
|
||||
|
||||
if displayAutoremoveTimeout {
|
||||
if let autoremoveTimeout = autoremoveTimeout {
|
||||
//TODO:localize
|
||||
return ("auto-delete after \(timeIntervalString(strings: strings, value: autoremoveTimeout))", false, true, .autoremove)
|
||||
} else {
|
||||
//TODO:localize
|
||||
return ("auto-deletion disabled", false, false, .autoremove)
|
||||
}
|
||||
}
|
||||
|
||||
if let chatListFilters = chatListFilters {
|
||||
var result = ""
|
||||
for case let .filter(_, title, _, data) in chatListFilters {
|
||||
@ -2909,7 +2993,7 @@ private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: Pres
|
||||
if result.isEmpty {
|
||||
return nil
|
||||
} else {
|
||||
return (result, true)
|
||||
return (result, true, false, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2917,28 +3001,28 @@ private func statusStringForPeerType(accountPeerId: EnginePeer.Id, strings: Pres
|
||||
return nil
|
||||
} else if case let .user(user) = peer {
|
||||
if user.botInfo != nil || user.flags.contains(.isSupport) {
|
||||
return (strings.ChatList_PeerTypeBot, false)
|
||||
return (strings.ChatList_PeerTypeBot, false, false, nil)
|
||||
} else if isContact {
|
||||
return (strings.ChatList_PeerTypeContact, false)
|
||||
return (strings.ChatList_PeerTypeContact, false, false, nil)
|
||||
} else {
|
||||
return (strings.ChatList_PeerTypeNonContact, false)
|
||||
return (strings.ChatList_PeerTypeNonContact, false, false, nil)
|
||||
}
|
||||
} else if case .secretChat = peer {
|
||||
if isContact {
|
||||
return (strings.ChatList_PeerTypeContact, false)
|
||||
return (strings.ChatList_PeerTypeContact, false, false, nil)
|
||||
} else {
|
||||
return (strings.ChatList_PeerTypeNonContact, false)
|
||||
return (strings.ChatList_PeerTypeNonContact, false, false, nil)
|
||||
}
|
||||
} else if case .legacyGroup = peer {
|
||||
return (strings.ChatList_PeerTypeGroup, false)
|
||||
return (strings.ChatList_PeerTypeGroup, false, false, nil)
|
||||
} else if case let .channel(channel) = peer {
|
||||
if case .group = channel.info {
|
||||
return (strings.ChatList_PeerTypeGroup, false)
|
||||
return (strings.ChatList_PeerTypeGroup, false, false, nil)
|
||||
} else {
|
||||
return (strings.ChatList_PeerTypeChannel, false)
|
||||
return (strings.ChatList_PeerTypeChannel, false, false, nil)
|
||||
}
|
||||
}
|
||||
return (strings.ChatList_PeerTypeNonContact, false)
|
||||
return (strings.ChatList_PeerTypeNonContact, false, false, nil)
|
||||
}
|
||||
|
||||
public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
|
||||
|
@ -46,30 +46,191 @@ public enum ChatListNodeEntryPromoInfo: Equatable {
|
||||
}
|
||||
|
||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
struct PeerEntryData: Equatable {
|
||||
var index: EngineChatList.Item.Index
|
||||
var presentationData: ChatListPresentationData
|
||||
var messages: [EngineMessage]
|
||||
var readState: EnginePeerReadCounters?
|
||||
var isRemovedFromTotalUnreadCount: Bool
|
||||
var draftState: ChatListItemContent.DraftState?
|
||||
var peer: EngineRenderedPeer
|
||||
var threadInfo: ChatListItemContent.ThreadInfo?
|
||||
var presence: EnginePeer.Presence?
|
||||
var hasUnseenMentions: Bool
|
||||
var hasUnseenReactions: Bool
|
||||
var editing: Bool
|
||||
var hasActiveRevealControls: Bool
|
||||
var selected: Bool
|
||||
var inputActivities: [(EnginePeer, PeerInputActivity)]?
|
||||
var promoInfo: ChatListNodeEntryPromoInfo?
|
||||
var hasFailedMessages: Bool
|
||||
var isContact: Bool
|
||||
var autoremoveTimeout: Int32?
|
||||
var forumTopicData: EngineChatList.ForumTopicData?
|
||||
var topForumTopicItems: [EngineChatList.ForumTopicData]
|
||||
var revealed: Bool
|
||||
|
||||
init(
|
||||
index: EngineChatList.Item.Index,
|
||||
presentationData: ChatListPresentationData,
|
||||
messages: [EngineMessage],
|
||||
readState: EnginePeerReadCounters?,
|
||||
isRemovedFromTotalUnreadCount: Bool,
|
||||
draftState: ChatListItemContent.DraftState?,
|
||||
peer: EngineRenderedPeer,
|
||||
threadInfo: ChatListItemContent.ThreadInfo?,
|
||||
presence: EnginePeer.Presence?,
|
||||
hasUnseenMentions: Bool,
|
||||
hasUnseenReactions: Bool,
|
||||
editing: Bool,
|
||||
hasActiveRevealControls: Bool,
|
||||
selected: Bool,
|
||||
inputActivities: [(EnginePeer, PeerInputActivity)]?,
|
||||
promoInfo: ChatListNodeEntryPromoInfo?,
|
||||
hasFailedMessages: Bool,
|
||||
isContact: Bool,
|
||||
autoremoveTimeout: Int32?,
|
||||
forumTopicData: EngineChatList.ForumTopicData?,
|
||||
topForumTopicItems: [EngineChatList.ForumTopicData],
|
||||
revealed: Bool
|
||||
) {
|
||||
self.index = index
|
||||
self.presentationData = presentationData
|
||||
self.messages = messages
|
||||
self.readState = readState
|
||||
self.isRemovedFromTotalUnreadCount = isRemovedFromTotalUnreadCount
|
||||
self.draftState = draftState
|
||||
self.peer = peer
|
||||
self.threadInfo = threadInfo
|
||||
self.presence = presence
|
||||
self.hasUnseenMentions = hasUnseenMentions
|
||||
self.hasUnseenReactions = hasUnseenReactions
|
||||
self.editing = editing
|
||||
self.hasActiveRevealControls = hasActiveRevealControls
|
||||
self.selected = selected
|
||||
self.inputActivities = inputActivities
|
||||
self.promoInfo = promoInfo
|
||||
self.hasFailedMessages = hasFailedMessages
|
||||
self.isContact = isContact
|
||||
self.autoremoveTimeout = autoremoveTimeout
|
||||
self.forumTopicData = forumTopicData
|
||||
self.topForumTopicItems = topForumTopicItems
|
||||
self.revealed = revealed
|
||||
}
|
||||
|
||||
static func ==(lhs: PeerEntryData, rhs: PeerEntryData) -> Bool {
|
||||
if lhs.index != rhs.index {
|
||||
return false
|
||||
}
|
||||
if lhs.presentationData !== rhs.presentationData {
|
||||
return false
|
||||
}
|
||||
if lhs.readState != rhs.readState {
|
||||
return false
|
||||
}
|
||||
if lhs.messages.count != rhs.messages.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhs.messages.count {
|
||||
if lhs.messages[i].stableVersion != rhs.messages[i].stableVersion {
|
||||
return false
|
||||
}
|
||||
if lhs.messages[i].id != rhs.messages[i].id {
|
||||
return false
|
||||
}
|
||||
if lhs.messages[i].associatedMessages.count != rhs.messages[i].associatedMessages.count {
|
||||
return false
|
||||
}
|
||||
for (id, message) in lhs.messages[i].associatedMessages {
|
||||
if let otherMessage = rhs.messages[i].associatedMessages[id] {
|
||||
if message.stableVersion != otherMessage.stableVersion {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if lhs.isRemovedFromTotalUnreadCount != rhs.isRemovedFromTotalUnreadCount {
|
||||
return false
|
||||
}
|
||||
if let lhsPeerPresence = lhs.presence, let rhsPeerPresence = rhs.presence {
|
||||
if lhsPeerPresence != rhsPeerPresence {
|
||||
return false
|
||||
}
|
||||
} else if (lhs.presence != nil) != (rhs.presence != nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsEmbeddedState = lhs.draftState, let rhsEmbeddedState = rhs.draftState {
|
||||
if lhsEmbeddedState != rhsEmbeddedState {
|
||||
return false
|
||||
}
|
||||
} else if (lhs.draftState != nil) != (rhs.draftState != nil) {
|
||||
return false
|
||||
}
|
||||
if lhs.editing != rhs.editing {
|
||||
return false
|
||||
}
|
||||
if lhs.hasActiveRevealControls != rhs.hasActiveRevealControls {
|
||||
return false
|
||||
}
|
||||
if lhs.selected != rhs.selected {
|
||||
return false
|
||||
}
|
||||
if lhs.peer != rhs.peer {
|
||||
return false
|
||||
}
|
||||
if lhs.threadInfo != rhs.threadInfo {
|
||||
return false
|
||||
}
|
||||
if lhs.hasUnseenMentions != rhs.hasUnseenMentions {
|
||||
return false
|
||||
}
|
||||
if lhs.hasUnseenReactions != rhs.hasUnseenReactions {
|
||||
return false
|
||||
}
|
||||
if let lhsInputActivities = lhs.inputActivities, let rhsInputActivities = rhs.inputActivities {
|
||||
if lhsInputActivities.count != rhsInputActivities.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhsInputActivities.count {
|
||||
if lhsInputActivities[i].0 != rhsInputActivities[i].0 {
|
||||
return false
|
||||
}
|
||||
if lhsInputActivities[i].1 != rhsInputActivities[i].1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else if (lhs.inputActivities != nil) != (rhs.inputActivities != nil) {
|
||||
return false
|
||||
}
|
||||
if lhs.promoInfo != rhs.promoInfo {
|
||||
return false
|
||||
}
|
||||
if lhs.hasFailedMessages != rhs.hasFailedMessages {
|
||||
return false
|
||||
}
|
||||
if lhs.isContact != rhs.isContact {
|
||||
return false
|
||||
}
|
||||
if lhs.autoremoveTimeout != rhs.autoremoveTimeout {
|
||||
return false
|
||||
}
|
||||
if lhs.forumTopicData != rhs.forumTopicData {
|
||||
return false
|
||||
}
|
||||
if lhs.topForumTopicItems != rhs.topForumTopicItems {
|
||||
return false
|
||||
}
|
||||
if lhs.revealed != rhs.revealed {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
case HeaderEntry
|
||||
case PeerEntry(
|
||||
index: EngineChatList.Item.Index,
|
||||
presentationData: ChatListPresentationData,
|
||||
messages: [EngineMessage],
|
||||
readState: EnginePeerReadCounters?,
|
||||
isRemovedFromTotalUnreadCount: Bool,
|
||||
draftState: ChatListItemContent.DraftState?,
|
||||
peer: EngineRenderedPeer,
|
||||
threadInfo: ChatListItemContent.ThreadInfo?,
|
||||
presence: EnginePeer.Presence?,
|
||||
hasUnseenMentions: Bool,
|
||||
hasUnseenReactions: Bool,
|
||||
editing: Bool,
|
||||
hasActiveRevealControls: Bool,
|
||||
selected: Bool,
|
||||
inputActivities: [(EnginePeer, PeerInputActivity)]?,
|
||||
promoInfo: ChatListNodeEntryPromoInfo?,
|
||||
hasFailedMessages: Bool,
|
||||
isContact: Bool,
|
||||
forumTopicData: EngineChatList.ForumTopicData?,
|
||||
topForumTopicItems: [EngineChatList.ForumTopicData],
|
||||
revealed: Bool
|
||||
)
|
||||
case PeerEntry(PeerEntryData)
|
||||
case HoleEntry(EngineMessage.Index, theme: PresentationTheme)
|
||||
case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool)
|
||||
case ArchiveIntro(presentationData: ChatListPresentationData)
|
||||
@ -79,8 +240,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
switch self {
|
||||
case .HeaderEntry:
|
||||
return .index(.chatList(.absoluteUpperBound))
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return .index(index)
|
||||
case let .PeerEntry(peerEntry):
|
||||
return .index(peerEntry.index)
|
||||
case let .HoleEntry(holeIndex, _):
|
||||
return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex)))
|
||||
case let .GroupReferenceEntry(index, _, _, _, _, _, _, _, _):
|
||||
@ -96,8 +257,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
switch self {
|
||||
case .HeaderEntry:
|
||||
return .Header
|
||||
case let .PeerEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
switch index {
|
||||
case let .PeerEntry(peerEntry):
|
||||
switch peerEntry.index {
|
||||
case let .chatList(index):
|
||||
return .PeerId(index.messageIndex.id.peerId.toInt64())
|
||||
case let .forum(_, _, threadId, _, _):
|
||||
@ -126,115 +287,11 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .PeerEntry(lhsIndex, lhsPresentationData, lhsMessages, lhsUnreadCount, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsThreadInfo, lhsPresence, lhsHasUnseenMentions, lhsHasUnseenReactions, lhsEditing, lhsHasRevealControls, lhsSelected, lhsInputActivities, lhsAd, lhsHasFailedMessages, lhsIsContact, lhsForumThreadTitle, lhsTopForumTopicItems, lhsRevealed):
|
||||
switch rhs {
|
||||
case let .PeerEntry(rhsIndex, rhsPresentationData, rhsMessages, rhsUnreadCount, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsThreadInfo, rhsPresence, rhsHasUnseenMentions, rhsHasUnseenReactions, rhsEditing, rhsHasRevealControls, rhsSelected, rhsInputActivities, rhsAd, rhsHasFailedMessages, rhsIsContact, rhsForumThreadTitle, rhsTopForumTopicItems, rhsRevealed):
|
||||
if lhsIndex != rhsIndex {
|
||||
return false
|
||||
}
|
||||
if lhsPresentationData !== rhsPresentationData {
|
||||
return false
|
||||
}
|
||||
if lhsUnreadCount != rhsUnreadCount {
|
||||
return false
|
||||
}
|
||||
if lhsMessages.count != rhsMessages.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhsMessages.count {
|
||||
if lhsMessages[i].stableVersion != rhsMessages[i].stableVersion {
|
||||
return false
|
||||
}
|
||||
if lhsMessages[i].id != rhsMessages[i].id {
|
||||
return false
|
||||
}
|
||||
if lhsMessages[i].associatedMessages.count != rhsMessages[i].associatedMessages.count {
|
||||
return false
|
||||
}
|
||||
for (id, message) in lhsMessages[i].associatedMessages {
|
||||
if let otherMessage = rhsMessages[i].associatedMessages[id] {
|
||||
if message.stableVersion != otherMessage.stableVersion {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if lhsIsRemovedFromTotalUnreadCount != rhsIsRemovedFromTotalUnreadCount {
|
||||
return false
|
||||
}
|
||||
if let lhsPeerPresence = lhsPresence, let rhsPeerPresence = rhsPresence {
|
||||
if lhsPeerPresence != rhsPeerPresence {
|
||||
return false
|
||||
}
|
||||
} else if (lhsPresence != nil) != (rhsPresence != nil) {
|
||||
return false
|
||||
}
|
||||
if let lhsEmbeddedState = lhsEmbeddedState, let rhsEmbeddedState = rhsEmbeddedState {
|
||||
if lhsEmbeddedState != rhsEmbeddedState {
|
||||
return false
|
||||
}
|
||||
} else if (lhsEmbeddedState != nil) != (rhsEmbeddedState != nil) {
|
||||
return false
|
||||
}
|
||||
if lhsEditing != rhsEditing {
|
||||
return false
|
||||
}
|
||||
if lhsHasRevealControls != rhsHasRevealControls {
|
||||
return false
|
||||
}
|
||||
if lhsSelected != rhsSelected {
|
||||
return false
|
||||
}
|
||||
if lhsPeer != rhsPeer {
|
||||
return false
|
||||
}
|
||||
if lhsThreadInfo != rhsThreadInfo {
|
||||
return false
|
||||
}
|
||||
if lhsHasUnseenMentions != rhsHasUnseenMentions {
|
||||
return false
|
||||
}
|
||||
if lhsHasUnseenReactions != rhsHasUnseenReactions {
|
||||
return false
|
||||
}
|
||||
if let lhsInputActivities = lhsInputActivities, let rhsInputActivities = rhsInputActivities {
|
||||
if lhsInputActivities.count != rhsInputActivities.count {
|
||||
return false
|
||||
}
|
||||
for i in 0 ..< lhsInputActivities.count {
|
||||
if lhsInputActivities[i].0 != rhsInputActivities[i].0 {
|
||||
return false
|
||||
}
|
||||
if lhsInputActivities[i].1 != rhsInputActivities[i].1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else if (lhsInputActivities != nil) != (rhsInputActivities != nil) {
|
||||
return false
|
||||
}
|
||||
if lhsAd != rhsAd {
|
||||
return false
|
||||
}
|
||||
if lhsHasFailedMessages != rhsHasFailedMessages {
|
||||
return false
|
||||
}
|
||||
if lhsIsContact != rhsIsContact {
|
||||
return false
|
||||
}
|
||||
if lhsForumThreadTitle != rhsForumThreadTitle {
|
||||
return false
|
||||
}
|
||||
if lhsTopForumTopicItems != rhsTopForumTopicItems {
|
||||
return false
|
||||
}
|
||||
if lhsRevealed != rhsRevealed {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
case let .PeerEntry(peerEntry):
|
||||
if case .PeerEntry(peerEntry) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .HoleEntry(lhsHole, lhsTheme):
|
||||
switch rhs {
|
||||
@ -401,7 +458,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
threadInfo = ChatListItemContent.ThreadInfo(id: threadId, info: threadData.info, isOwnedByMe: threadData.isOwnedByMe, isClosed: threadData.isClosed, isHidden: threadData.isHidden)
|
||||
}
|
||||
|
||||
result.append(.PeerEntry(
|
||||
result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData(
|
||||
index: offsetPinnedIndex(entry.index, offset: pinnedIndexOffset),
|
||||
presentationData: state.presentationData,
|
||||
messages: updatedMessages,
|
||||
@ -420,10 +477,11 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
promoInfo: nil,
|
||||
hasFailedMessages: entry.hasFailed,
|
||||
isContact: entry.isContact,
|
||||
autoremoveTimeout: entry.autoremoveTimeout,
|
||||
forumTopicData: entry.forumTopicData,
|
||||
topForumTopicItems: entry.topForumTopicItems,
|
||||
revealed: threadId == 1 && (state.hiddenItemShouldBeTemporaryRevealed || state.editing)
|
||||
))
|
||||
)))
|
||||
}
|
||||
if !view.hasLater {
|
||||
var pinningIndex: UInt16 = UInt16(pinnedIndexOffset == 0 ? 0 : (pinnedIndexOffset - 1))
|
||||
@ -438,7 +496,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
}
|
||||
|
||||
let messageIndex = EngineMessage.Index(id: EngineMessage.Id(peerId: peer.0.id, namespace: 0, id: 0), timestamp: 1)
|
||||
result.append(.PeerEntry(
|
||||
result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData(
|
||||
index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: foundPinningIndex, messageIndex: messageIndex)),
|
||||
presentationData: state.presentationData,
|
||||
messages: [],
|
||||
@ -457,17 +515,18 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
promoInfo: nil,
|
||||
hasFailedMessages: false,
|
||||
isContact: false,
|
||||
autoremoveTimeout: nil,
|
||||
forumTopicData: nil,
|
||||
topForumTopicItems: [],
|
||||
revealed: false
|
||||
))
|
||||
)))
|
||||
if foundPinningIndex != 0 {
|
||||
foundPinningIndex -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.append(.PeerEntry(
|
||||
result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData(
|
||||
index: .chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.predecessor),
|
||||
presentationData: state.presentationData,
|
||||
messages: [],
|
||||
@ -486,10 +545,11 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
promoInfo: nil,
|
||||
hasFailedMessages: false,
|
||||
isContact: false,
|
||||
autoremoveTimeout: nil,
|
||||
forumTopicData: nil,
|
||||
topForumTopicItems: [],
|
||||
revealed: false
|
||||
))
|
||||
)))
|
||||
} else {
|
||||
if !filteredAdditionalItemEntries.isEmpty {
|
||||
for item in filteredAdditionalItemEntries.reversed() {
|
||||
@ -516,8 +576,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
result.append(.PeerEntry(
|
||||
result.append(.PeerEntry(ChatListNodeEntry.PeerEntryData(
|
||||
index: .chatList(EngineChatList.Item.Index.ChatList(pinningIndex: pinningIndex, messageIndex: index.messageIndex)),
|
||||
presentationData: state.presentationData,
|
||||
messages: item.item.messages,
|
||||
@ -536,10 +595,11 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
promoInfo: promoInfo,
|
||||
hasFailedMessages: item.item.hasFailed,
|
||||
isContact: item.item.isContact,
|
||||
autoremoveTimeout: item.item.autoremoveTimeout,
|
||||
forumTopicData: item.item.forumTopicData,
|
||||
topForumTopicItems: item.item.topForumTopicItems,
|
||||
revealed: threadId == 1 && (state.hiddenItemShouldBeTemporaryRevealed || state.editing)
|
||||
))
|
||||
revealed: threadId == 1 && (state.hiddenItemShouldBeTemporaryRevealed || state.editing)
|
||||
)))
|
||||
if pinningIndex != 0 {
|
||||
pinningIndex -= 1
|
||||
}
|
||||
@ -573,7 +633,7 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
result.append(.HeaderEntry)
|
||||
}
|
||||
|
||||
if !view.hasLater, case let .peers(_, _, additionalCategories, _) = mode {
|
||||
if !view.hasLater, case let .peers(_, _, additionalCategories, _, _) = mode {
|
||||
var index = 0
|
||||
for category in additionalCategories.reversed(){
|
||||
result.append(.AdditionalCategory(index: index, id: category.id, title: category.title, image: category.icon, appearance: category.appearance, selected: state.selectedAdditionalCategoryIds.contains(category.id), presentationData: state.presentationData))
|
||||
|
@ -287,7 +287,8 @@ func chatListViewForLocation(chatListLocation: ChatListControllerLocation, locat
|
||||
forumTopicData: nil,
|
||||
topForumTopicItems: [],
|
||||
hasFailed: false,
|
||||
isContact: false
|
||||
isContact: false,
|
||||
autoremoveTimeout: nil
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -184,19 +184,19 @@ private enum ContactListNodeEntry: Comparable, Identifiable {
|
||||
if let _ = peer as? TelegramUser {
|
||||
status = .presence(presence ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0), dateTimeFormat)
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
status = .custom(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), multiline: false)
|
||||
status = .custom(string: strings.Conversation_StatusMembers(Int32(group.participantCount)), multiline: false, isActive: false, icon: nil)
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if case .group = channel.info {
|
||||
if let participantCount = participantCount, participantCount != 0 {
|
||||
status = .custom(string: strings.Conversation_StatusMembers(participantCount), multiline: false)
|
||||
status = .custom(string: strings.Conversation_StatusMembers(participantCount), multiline: false, isActive: false, icon: nil)
|
||||
} else {
|
||||
status = .custom(string: strings.Group_Status, multiline: false)
|
||||
status = .custom(string: strings.Group_Status, multiline: false, isActive: false, icon: nil)
|
||||
}
|
||||
} else {
|
||||
if let participantCount = participantCount, participantCount != 0 {
|
||||
status = .custom(string: strings.Conversation_StatusSubscribers(participantCount), multiline: false)
|
||||
status = .custom(string: strings.Conversation_StatusSubscribers(participantCount), multiline: false, isActive: false, icon: nil)
|
||||
} else {
|
||||
status = .custom(string: strings.Channel_Status, multiline: false)
|
||||
status = .custom(string: strings.Channel_Status, multiline: false, isActive: false, icon: nil)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -1267,7 +1267,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
return context.engine.data.get(EngineDataMap(
|
||||
view.entries.compactMap { entry -> EnginePeer.Id? in
|
||||
switch entry {
|
||||
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _):
|
||||
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _, _):
|
||||
if let peer = renderedPeer.peer {
|
||||
if let channel = peer as? TelegramChannel, case .group = channel.info {
|
||||
return peer.id
|
||||
@ -1283,7 +1283,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
var peers: [(EnginePeer, Int32)] = []
|
||||
for entry in view.entries {
|
||||
switch entry {
|
||||
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _):
|
||||
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _, _):
|
||||
if let peer = renderedPeer.peer {
|
||||
if peer is TelegramGroup {
|
||||
peers.append((EnginePeer(peer), 0))
|
||||
|
@ -52,7 +52,7 @@ private enum InviteContactsEntry: Comparable, Identifiable {
|
||||
case let .peer(_, id, contact, count, selection, theme, strings, nameSortOrder, nameDisplayOrder):
|
||||
let status: ContactsPeerItemStatus
|
||||
if count != 0 {
|
||||
status = .custom(string: strings.Contacts_ImportersCount(count), multiline: false)
|
||||
status = .custom(string: strings.Contacts_ImportersCount(count), multiline: false, isActive: false, icon: nil)
|
||||
} else {
|
||||
status = .none
|
||||
}
|
||||
|
@ -31,10 +31,14 @@ public final class ContactItemHighlighting {
|
||||
}
|
||||
|
||||
public enum ContactsPeerItemStatus {
|
||||
public enum Icon {
|
||||
case autoremove
|
||||
}
|
||||
|
||||
case none
|
||||
case presence(EnginePeer.Presence, PresentationDateTimeFormat)
|
||||
case addressName(String)
|
||||
case custom(string: String, multiline: Bool)
|
||||
case custom(string: String, multiline: Bool, isActive: Bool, icon: Icon?)
|
||||
}
|
||||
|
||||
public enum ContactsPeerItemSelection: Equatable {
|
||||
@ -395,6 +399,7 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
private var credibilityIconView: ComponentHostView<Empty>?
|
||||
private var credibilityIconComponent: EmojiStatusComponent?
|
||||
private let statusNode: TextNode
|
||||
private var statusIconNode: ASImageNode?
|
||||
private var badgeBackgroundNode: ASImageNode?
|
||||
private var badgeTextNode: TextNode?
|
||||
private var selectionNode: CheckNode?
|
||||
@ -733,6 +738,8 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
var titleAttributedString: NSAttributedString?
|
||||
var statusAttributedString: NSAttributedString?
|
||||
var statusIcon: ContactsPeerItemStatus.Icon?
|
||||
var statusIsActive: Bool = false
|
||||
var multilineStatus: Bool = false
|
||||
var userPresence: EnginePeer.Presence?
|
||||
|
||||
@ -801,8 +808,10 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
} else if !suffix.isEmpty {
|
||||
statusAttributedString = NSAttributedString(string: suffix, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
}
|
||||
case let .custom(text, multiline):
|
||||
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
case let .custom(text, multiline, isActive, icon):
|
||||
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
statusIcon = icon
|
||||
statusIsActive = isActive
|
||||
multilineStatus = multiline
|
||||
}
|
||||
}
|
||||
@ -824,9 +833,11 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
switch item.status {
|
||||
case let .custom(text, multiline):
|
||||
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
case let .custom(text, multiline, isActive, icon):
|
||||
statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: isActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
multilineStatus = multiline
|
||||
statusIsActive = isActive
|
||||
statusIcon = icon
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -882,7 +893,20 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - additionalTitleInset), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: multilineStatus ? 3 : 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, params.width - leftInset - rightInset - badgeSize), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
var maxStatusWidth: CGFloat = params.width - leftInset - rightInset - badgeSize
|
||||
if let _ = statusIcon {
|
||||
maxStatusWidth -= 10.0
|
||||
}
|
||||
|
||||
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: multilineStatus ? 3 : 1, truncationType: .end, constrainedSize: CGSize(width: max(0.0, maxStatusWidth), height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var statusIconImage: UIImage?
|
||||
if let statusIcon = statusIcon {
|
||||
switch statusIcon {
|
||||
case .autoremove:
|
||||
statusIconImage = PresentationResourcesChatList.statusAutoremoveIcon(item.presentationData.theme, isActive: statusIsActive)
|
||||
}
|
||||
}
|
||||
|
||||
let titleVerticalInset: CGFloat = statusAttributedString == nil ? 13.0 : 6.0
|
||||
let verticalInset: CGFloat = statusAttributedString == nil ? 13.0 : 6.0
|
||||
@ -1097,12 +1121,32 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.statusNode.alpha = item.enabled ? 1.0 : 1.0
|
||||
|
||||
let _ = statusApply()
|
||||
let statusFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: strongSelf.titleNode.frame.maxY - 1.0), size: statusLayout.size)
|
||||
var statusFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset, y: strongSelf.titleNode.frame.maxY - 1.0), size: statusLayout.size)
|
||||
if let statusIconImage {
|
||||
statusFrame.origin.x += statusIconImage.size.width + 1.0
|
||||
}
|
||||
let previousStatusFrame = strongSelf.statusNode.frame
|
||||
|
||||
strongSelf.statusNode.frame = statusFrame
|
||||
transition.animatePositionAdditive(node: strongSelf.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
|
||||
|
||||
if let statusIconImage {
|
||||
let statusIconNode: ASImageNode
|
||||
if let current = strongSelf.statusIconNode {
|
||||
statusIconNode = current
|
||||
} else {
|
||||
statusIconNode = ASImageNode()
|
||||
strongSelf.statusNode.addSubnode(statusIconNode)
|
||||
}
|
||||
statusIconNode.image = statusIconImage
|
||||
statusIconNode.frame = CGRect(origin: CGPoint(x: -statusIconImage.size.width - 1.0, y: floor((statusFrame.height - statusIconImage.size.height) / 2.0) + 1.0), size: statusIconImage.size)
|
||||
} else {
|
||||
if let statusIconNode = strongSelf.statusIconNode {
|
||||
strongSelf.statusIconNode = nil
|
||||
statusIconNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
if let credibilityIcon = credibilityIcon {
|
||||
let animationCache = item.context.animationCache
|
||||
let animationRenderer = item.context.animationRenderer
|
||||
@ -1334,6 +1378,9 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
|
||||
var statusFrame = self.statusNode.frame
|
||||
let previousStatusFrame = statusFrame
|
||||
statusFrame.origin.x = leftInset + offset
|
||||
if let statusIconImage = self.statusIconNode?.image {
|
||||
statusFrame.origin.x += statusIconImage.size.width + 1.0
|
||||
}
|
||||
self.statusNode.frame = statusFrame
|
||||
transition.animatePositionAdditive(node: self.statusNode, offset: CGPoint(x: previousStatusFrame.minX - statusFrame.minX, y: 0))
|
||||
|
||||
|
@ -103,6 +103,7 @@ public final class ContextMenuActionItem {
|
||||
public let icon: (PresentationTheme) -> UIImage?
|
||||
public let iconSource: ContextMenuActionItemIconSource?
|
||||
public let textIcon: (PresentationTheme) -> UIImage?
|
||||
public let textLinkAction: () -> Void
|
||||
public let action: ((Action) -> Void)?
|
||||
|
||||
convenience public init(
|
||||
@ -116,6 +117,7 @@ public final class ContextMenuActionItem {
|
||||
icon: @escaping (PresentationTheme) -> UIImage?,
|
||||
iconSource: ContextMenuActionItemIconSource? = nil,
|
||||
textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil },
|
||||
textLinkAction: @escaping () -> Void = {},
|
||||
action: ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?
|
||||
) {
|
||||
self.init(
|
||||
@ -129,6 +131,7 @@ public final class ContextMenuActionItem {
|
||||
icon: icon,
|
||||
iconSource: iconSource,
|
||||
textIcon: textIcon,
|
||||
textLinkAction: textLinkAction,
|
||||
action: action.flatMap { action in
|
||||
return { impl in
|
||||
action(impl.controller, impl.dismissWithResult)
|
||||
@ -148,6 +151,7 @@ public final class ContextMenuActionItem {
|
||||
icon: @escaping (PresentationTheme) -> UIImage?,
|
||||
iconSource: ContextMenuActionItemIconSource? = nil,
|
||||
textIcon: @escaping (PresentationTheme) -> UIImage? = { _ in return nil },
|
||||
textLinkAction: @escaping () -> Void = {},
|
||||
action: ((Action) -> Void)?
|
||||
) {
|
||||
self.id = id
|
||||
@ -160,6 +164,7 @@ public final class ContextMenuActionItem {
|
||||
self.icon = icon
|
||||
self.iconSource = iconSource
|
||||
self.textIcon = textIcon
|
||||
self.textLinkAction = textLinkAction
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,6 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
||||
self.titleLabelNode = ImmediateTextNode()
|
||||
self.titleLabelNode.isAccessibilityElement = false
|
||||
self.titleLabelNode.displaysAsynchronously = false
|
||||
self.titleLabelNode.isUserInteractionEnabled = false
|
||||
|
||||
self.subtitleNode = ImmediateTextNode()
|
||||
self.subtitleNode.isAccessibilityElement = false
|
||||
@ -160,6 +159,16 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
||||
self.pressed()
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.titleLabelNode.tapAttributeAction != nil {
|
||||
if let result = self.titleLabelNode.hitTest(self.view.convert(point, to: self.titleLabelNode.view), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let verticalInset: CGFloat = 11.0
|
||||
@ -219,10 +228,23 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
||||
body: MarkdownAttributeSet(font: titleFont, textColor: titleColor),
|
||||
bold: MarkdownAttributeSet(font: titleBoldFont, textColor: titleColor),
|
||||
link: MarkdownAttributeSet(font: titleBoldFont, textColor: presentationData.theme.list.itemAccentColor),
|
||||
linkAttribute: { _ in return nil }
|
||||
linkAttribute: { value in return ("URL", value) }
|
||||
)
|
||||
)
|
||||
self.titleLabelNode.attributedText = attributedText
|
||||
self.titleLabelNode.linkHighlightColor = presentationData.theme.list.itemAccentColor.withMultipliedAlpha(0.5)
|
||||
self.titleLabelNode.highlightAttributeAction = { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
||||
return NSAttributedString.Key(rawValue: "URL")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
self.titleLabelNode.tapAttributeAction = { [weak item] attributes, _ in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
||||
item?.textLinkAction()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.titleLabelNode.attributedText = NSAttributedString(
|
||||
string: self.item.text,
|
||||
@ -230,6 +252,8 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
||||
textColor: titleColor)
|
||||
}
|
||||
|
||||
self.titleLabelNode.isUserInteractionEnabled = self.titleLabelNode.tapAttributeAction != nil
|
||||
|
||||
self.subtitleNode.attributedText = subtitle.flatMap { subtitle in
|
||||
return NSAttributedString(
|
||||
string: subtitle,
|
||||
|
@ -626,7 +626,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
}
|
||||
case let .reference(reference):
|
||||
if let transitionInfo = reference.transitionInfo() {
|
||||
contentRect = convertFrame(transitionInfo.referenceView.bounds, from: transitionInfo.referenceView, to: self.view).insetBy(dx: -2.0, dy: 0.0)
|
||||
contentRect = convertFrame(transitionInfo.referenceView.bounds.inset(by: transitionInfo.insets), from: transitionInfo.referenceView, to: self.view).insetBy(dx: -2.0, dy: 0.0)
|
||||
contentRect.size.width += 5.0
|
||||
contentParentGlobalFrame = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minX), size: CGSize(width: layout.size.width, height: contentRect.height))
|
||||
} else {
|
||||
@ -1040,7 +1040,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
self.clippingNode.layer.animateFrame(from: CGRect(origin: CGPoint(), size: layout.size), to: CGRect(origin: CGPoint(x: 0.0, y: putBackInfo.contentAreaInScreenSpace.minY), size: CGSize(width: layout.size.width, height: putBackInfo.contentAreaInScreenSpace.height)), duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
||||
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: putBackInfo.contentAreaInScreenSpace.minY, duration: duration, timingFunction: timingFunction, removeOnCompletion: false)
|
||||
|
||||
currentContentScreenFrame = convertFrame(putBackInfo.referenceView.bounds, from: putBackInfo.referenceView, to: self.view)
|
||||
currentContentScreenFrame = convertFrame(putBackInfo.referenceView.bounds.inset(by: putBackInfo.insets), from: putBackInfo.referenceView, to: self.view)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ public enum ItemListDisclosureItemTitleColor {
|
||||
|
||||
public enum ItemListDisclosureStyle {
|
||||
case arrow
|
||||
case optionArrows
|
||||
case none
|
||||
}
|
||||
|
||||
@ -116,7 +117,7 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
|
||||
let iconNode: ASImageNode
|
||||
let titleNode: TextNode
|
||||
let labelNode: TextNode
|
||||
public let labelNode: TextNode
|
||||
let arrowNode: ASImageNode
|
||||
let labelBadgeNode: ASImageNode
|
||||
let labelImageNode: ASImageNode
|
||||
@ -198,6 +199,17 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateHasContextMenu(hasContextMenu: Bool) {
|
||||
let transition: ContainedViewLayoutTransition
|
||||
if hasContextMenu {
|
||||
transition = .immediate
|
||||
} else {
|
||||
transition = .animated(duration: 0.3, curve: .easeInOut)
|
||||
}
|
||||
transition.updateAlpha(node: self.labelNode, alpha: hasContextMenu ? 0.5 : 1.0)
|
||||
transition.updateAlpha(node: self.arrowNode, alpha: hasContextMenu ? 0.5 : 1.0)
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ item: ItemListDisclosureItem, _ params: ListViewItemLayoutParams, _ insets: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
@ -213,6 +225,8 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
rightInset = 16.0 + params.rightInset
|
||||
case .arrow:
|
||||
rightInset = 34.0 + params.rightInset
|
||||
case .optionArrows:
|
||||
rightInset = 34.0 + params.rightInset
|
||||
}
|
||||
|
||||
var updateArrowImage: UIImage?
|
||||
@ -245,7 +259,12 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
let badgeDiameter: CGFloat = 20.0
|
||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||
updatedTheme = item.presentationData.theme
|
||||
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
||||
switch item.disclosureStyle {
|
||||
case .none, .arrow:
|
||||
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
||||
case .optionArrows:
|
||||
updateArrowImage = PresentationResourcesItemList.disclosureOptionArrowsImage(item.presentationData.theme)
|
||||
}
|
||||
if let badgeColor = badgeColor {
|
||||
updatedLabelBadgeImage = generateStretchableFilledCircleImage(diameter: badgeDiameter, color: badgeColor)
|
||||
}
|
||||
@ -512,14 +531,21 @@ public class ItemListDisclosureItemNode: ListViewItemNode, ItemListItemNode {
|
||||
}
|
||||
|
||||
if let arrowImage = strongSelf.arrowNode.image {
|
||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 7.0 - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
let arrowRightOffset: CGFloat
|
||||
switch item.disclosureStyle {
|
||||
case .optionArrows:
|
||||
arrowRightOffset = 18.0
|
||||
case .none, .arrow:
|
||||
arrowRightOffset = 7.0
|
||||
}
|
||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - arrowRightOffset - arrowImage.size.width, y: floorToScreenPixels((height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||
}
|
||||
|
||||
switch item.disclosureStyle {
|
||||
case .none:
|
||||
strongSelf.arrowNode.isHidden = true
|
||||
case .arrow:
|
||||
strongSelf.arrowNode.isHidden = false
|
||||
case .none:
|
||||
strongSelf.arrowNode.isHidden = true
|
||||
case .arrow, .optionArrows:
|
||||
strongSelf.arrowNode.isHidden = false
|
||||
}
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: height + UIScreenPixel))
|
||||
|
@ -659,10 +659,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
hasTimer = true
|
||||
}
|
||||
|
||||
var hasSchedule = true
|
||||
if controller.chatLocation?.threadId != nil {
|
||||
hasSchedule = false
|
||||
}
|
||||
let hasSchedule = true
|
||||
|
||||
self.openingMedia = true
|
||||
|
||||
|
@ -159,7 +159,7 @@ private final class ChannelMembersSearchEntry: Comparable, Identifiable {
|
||||
case let .participant(participant, label, revealActions, revealed, enabled):
|
||||
let status: ContactsPeerItemStatus
|
||||
if let label = label {
|
||||
status = .custom(string: label, multiline: false)
|
||||
status = .custom(string: label, multiline: false, isActive: false, icon: nil)
|
||||
} else if let presence = participant.presences[participant.peer.id], self.addIcon {
|
||||
status = .presence(EnginePeer.Presence(presence), dateTimeFormat)
|
||||
} else {
|
||||
|
@ -130,7 +130,7 @@ private enum ChannelMembersSearchEntry: Comparable, Identifiable {
|
||||
case let .peer(_, participant, editing, label, enabled, isChannel, isContact):
|
||||
let status: ContactsPeerItemStatus
|
||||
if let label = label {
|
||||
status = .custom(string: label, multiline: false)
|
||||
status = .custom(string: label, multiline: false, isActive: false, icon: nil)
|
||||
} else if participant.peer.id != context.account.peerId {
|
||||
let presence = participant.presences[participant.peer.id] ?? TelegramUserPresence(status: .none, lastActivity: 0)
|
||||
status = .presence(EnginePeer.Presence(presence), presentationData.dateTimeFormat)
|
||||
|
@ -138,10 +138,10 @@ class ItemListSecretChatKeyItemNode: ListViewItemNode {
|
||||
return { item, params, neighbors in
|
||||
let rightInset: CGFloat
|
||||
switch item.disclosureStyle {
|
||||
case .none:
|
||||
rightInset = 16.0 + params.rightInset
|
||||
case .arrow:
|
||||
rightInset = 34.0 + params.rightInset
|
||||
case .none:
|
||||
rightInset = 16.0 + params.rightInset
|
||||
case .arrow, .optionArrows:
|
||||
rightInset = 34.0 + params.rightInset
|
||||
}
|
||||
|
||||
var updateArrowImage: UIImage?
|
||||
@ -279,7 +279,7 @@ class ItemListSecretChatKeyItemNode: ListViewItemNode {
|
||||
switch item.disclosureStyle {
|
||||
case .none:
|
||||
strongSelf.arrowNode.isHidden = true
|
||||
case .arrow:
|
||||
case .arrow, .optionArrows:
|
||||
strongSelf.arrowNode.isHidden = false
|
||||
}
|
||||
|
||||
|
@ -173,7 +173,7 @@ private enum OldChannelsEntry: ItemListNodeEntry {
|
||||
case let .peersHeader(title):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section)
|
||||
case let .peer(_, peer, selected):
|
||||
return ContactsPeerItem(presentationData: presentationData, style: .blocks, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
|
||||
return ContactsPeerItem(presentationData: presentationData, style: .blocks, sectionId: self.section, sortOrder: .firstLast, displayOrder: .firstLast, context: arguments.context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
|
||||
arguments.togglePeer(peer.peer.id, true)
|
||||
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ private enum OldChannelsSearchEntry: Comparable, Identifiable {
|
||||
func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: OldChannelsSearchInteraction) -> ListViewItem {
|
||||
switch self {
|
||||
case let .peer(_, peer, selected):
|
||||
return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
|
||||
return ContactsPeerItem(presentationData: presentationData, style: .plain, sortOrder: .firstLast, displayOrder: .firstLast, context: context, peerMode: .peer, peer: .peer(peer: EnginePeer(peer.peer), chatPeer: EnginePeer(peer.peer)), status: .custom(string: localizedOldChannelDate(peer: peer, strings: presentationData.strings), multiline: false, isActive: false, icon: nil), badge: nil, enabled: true, selection: ContactsPeerItemSelection.selectable(selected: selected), editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), options: [], actionIcon: .none, index: nil, header: nil, action: { _ in
|
||||
interaction.togglePeer(peer.peer.id)
|
||||
}, setPeerIdWithRevealedOptions: nil, deletePeer: nil, itemHighlighting: nil, contextAction: nil)
|
||||
}
|
||||
|
@ -111,12 +111,12 @@ public struct ChatListForumTopicData: Equatable {
|
||||
}
|
||||
|
||||
public enum ChatListEntry: Comparable {
|
||||
case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailed: Bool, isContact: Bool)
|
||||
case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, summaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailed: Bool, isContact: Bool, autoremoveTimeout: Int32?)
|
||||
case HoleEntry(ChatListHole)
|
||||
|
||||
public var index: ChatListIndex {
|
||||
switch self {
|
||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return index
|
||||
case let .HoleEntry(hole):
|
||||
return ChatListIndex(pinningIndex: nil, messageIndex: hole.index)
|
||||
@ -125,9 +125,9 @@ public enum ChatListEntry: Comparable {
|
||||
|
||||
public static func ==(lhs: ChatListEntry, rhs: ChatListEntry) -> Bool {
|
||||
switch lhs {
|
||||
case let .MessageEntry(lhsIndex, lhsMessages, lhsReadState, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsInfo, lhsForumTopicData, lhsTopForumTopics, lhsHasFailed, lhsIsContact):
|
||||
case let .MessageEntry(lhsIndex, lhsMessages, lhsReadState, lhsIsRemovedFromTotalUnreadCount, lhsEmbeddedState, lhsPeer, lhsPresence, lhsInfo, lhsForumTopicData, lhsTopForumTopics, lhsHasFailed, lhsIsContact, lhsAutoremoveTimeout):
|
||||
switch rhs {
|
||||
case let .MessageEntry(rhsIndex, rhsMessages, rhsReadState, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsInfo, rhsForumTopicData, rhsTopForumTopics, rhsHasFailed, rhsIsContact):
|
||||
case let .MessageEntry(rhsIndex, rhsMessages, rhsReadState, rhsIsRemovedFromTotalUnreadCount, rhsEmbeddedState, rhsPeer, rhsPresence, rhsInfo, rhsForumTopicData, rhsTopForumTopics, rhsHasFailed, rhsIsContact, rhsAutoremoveTimeout):
|
||||
if lhsIndex != rhsIndex {
|
||||
return false
|
||||
}
|
||||
@ -177,6 +177,9 @@ public enum ChatListEntry: Comparable {
|
||||
if lhsIsContact != rhsIsContact {
|
||||
return false
|
||||
}
|
||||
if lhsAutoremoveTimeout != rhsAutoremoveTimeout {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
@ -197,7 +200,7 @@ public enum ChatListEntry: Comparable {
|
||||
|
||||
enum MutableChatListEntry: Equatable {
|
||||
case IntermediateMessageEntry(index: ChatListIndex, messageIndex: MessageIndex?)
|
||||
case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailedMessages: Bool, isContact: Bool)
|
||||
case MessageEntry(index: ChatListIndex, messages: [Message], readState: ChatListViewReadState?, notificationSettings: PeerNotificationSettings?, isRemovedFromTotalUnreadCount: Bool, embeddedInterfaceState: StoredPeerChatInterfaceState?, renderedPeer: RenderedPeer, presence: PeerPresence?, tagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo], forumTopicData: ChatListForumTopicData?, topForumTopics: [ChatListForumTopicData], hasFailedMessages: Bool, isContact: Bool, autoremoveTimeout: Int32?)
|
||||
case HoleEntry(ChatListHole)
|
||||
|
||||
init(_ intermediateEntry: ChatListIntermediateEntry, cachedDataTable: CachedPeerDataTable, readStateTable: MessageHistoryReadStateTable, messageHistoryTable: MessageHistoryTable) {
|
||||
@ -213,7 +216,7 @@ enum MutableChatListEntry: Equatable {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(index, _):
|
||||
return index
|
||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return index
|
||||
case let .HoleEntry(hole):
|
||||
return ChatListIndex(pinningIndex: nil, messageIndex: hole.index)
|
||||
@ -713,7 +716,12 @@ final class MutableChatListView {
|
||||
}
|
||||
}
|
||||
|
||||
return .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: [:], forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact)
|
||||
var autoremoveTimeout: Int32?
|
||||
if let cachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId) {
|
||||
autoremoveTimeout = postbox.seedConfiguration.decodeAutoremoveTimeout(cachedData)
|
||||
}
|
||||
|
||||
return .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: false, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: [:], forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId), isContact: isContact, autoremoveTimeout: autoremoveTimeout)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@ -742,8 +750,8 @@ public final class ChatListView {
|
||||
var entries: [ChatListEntry] = []
|
||||
for entry in mutableView.sampledState.entries {
|
||||
switch entry {
|
||||
case let .MessageEntry(index, messages, combinedReadState, _, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact):
|
||||
entries.append(.MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact))
|
||||
case let .MessageEntry(index, messages, combinedReadState, _, isRemovedFromTotalUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact, autoremoveTimeout):
|
||||
entries.append(.MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact, autoremoveTimeout: autoremoveTimeout))
|
||||
case let .HoleEntry(hole):
|
||||
entries.append(.HoleEntry(hole))
|
||||
case .IntermediateMessageEntry:
|
||||
@ -760,9 +768,9 @@ public final class ChatListView {
|
||||
var additionalItemEntries: [ChatListAdditionalItemEntry] = []
|
||||
for entry in mutableView.additionalItemEntries {
|
||||
switch entry.entry {
|
||||
case let .MessageEntry(index, messages, combinedReadState, _, isExcludedFromUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact):
|
||||
case let .MessageEntry(index, messages, combinedReadState, _, isExcludedFromUnreadCount, embeddedState, peer, peerPresence, summaryInfo, forumTopicData, topForumTopics, hasFailed, isContact, autoremoveTimeout):
|
||||
additionalItemEntries.append(ChatListAdditionalItemEntry(
|
||||
entry: .MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isExcludedFromUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact),
|
||||
entry: .MessageEntry(index: index, messages: messages, readState: combinedReadState, isRemovedFromTotalUnreadCount: isExcludedFromUnreadCount, embeddedInterfaceState: embeddedState, renderedPeer: peer, presence: peerPresence, summaryInfo: summaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailed: hasFailed, isContact: isContact, autoremoveTimeout: autoremoveTimeout),
|
||||
info: entry.info
|
||||
))
|
||||
case .HoleEntry:
|
||||
|
@ -515,7 +515,7 @@ private final class ChatListViewSpaceState {
|
||||
let entryPeer: Peer
|
||||
let entryNotificationsPeerId: PeerId
|
||||
switch entry {
|
||||
case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _, _, _):
|
||||
case let .MessageEntry(_, _, _, _, _, _, renderedPeer, _, _, _, _, _, _, _):
|
||||
if let peer = renderedPeer.peer {
|
||||
entryPeer = peer
|
||||
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
||||
@ -630,13 +630,13 @@ private final class ChatListViewSpaceState {
|
||||
|
||||
if self.orderedEntries.mutableScan({ entry in
|
||||
switch entry {
|
||||
case let .MessageEntry(index, messages, readState, _, _, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact):
|
||||
case let .MessageEntry(index, messages, readState, _, _, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact, autoremoveTimeout):
|
||||
if let peer = renderedPeer.peer {
|
||||
let notificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
||||
if let (_, updated) = transaction.currentUpdatedPeerNotificationSettings[notificationsPeerId] {
|
||||
let isRemovedFromTotalUnreadCount = resolvedIsRemovedFromTotalUnreadCount(globalSettings: globalNotificationSettings, peer: peer, peerSettings: updated)
|
||||
|
||||
return .MessageEntry(index: index, messages: messages, readState: readState, notificationSettings: updated, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedInterfaceState, renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, isContact: isContact)
|
||||
return .MessageEntry(index: index, messages: messages, readState: readState, notificationSettings: updated, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: embeddedInterfaceState, renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: hasFailedMessages, isContact: isContact, autoremoveTimeout: autoremoveTimeout)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -654,7 +654,7 @@ private final class ChatListViewSpaceState {
|
||||
if !transaction.updatedFailedMessagePeerIds.isEmpty {
|
||||
if self.orderedEntries.mutableScan({ entry in
|
||||
switch entry {
|
||||
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, _, isContact):
|
||||
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, _, isContact, autoremoveTimeout):
|
||||
if transaction.updatedFailedMessagePeerIds.contains(index.messageIndex.id.peerId) {
|
||||
return .MessageEntry(
|
||||
index: index,
|
||||
@ -669,7 +669,8 @@ private final class ChatListViewSpaceState {
|
||||
forumTopicData: forumTopicData,
|
||||
topForumTopics: topForumTopics,
|
||||
hasFailedMessages: postbox.messageHistoryFailedTable.contains(peerId: index.messageIndex.id.peerId),
|
||||
isContact: isContact
|
||||
isContact: isContact,
|
||||
autoremoveTimeout: autoremoveTimeout
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
@ -685,7 +686,7 @@ private final class ChatListViewSpaceState {
|
||||
if !transaction.currentUpdatedPeers.isEmpty {
|
||||
if self.orderedEntries.mutableScan({ entry in
|
||||
switch entry {
|
||||
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact):
|
||||
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact, autoremoveTimeout):
|
||||
var updatedMessages: [Message] = messages
|
||||
var hasUpdatedMessages = false
|
||||
for i in 0 ..< updatedMessages.count {
|
||||
@ -710,7 +711,9 @@ private final class ChatListViewSpaceState {
|
||||
forumTopicData: forumTopicData,
|
||||
topForumTopics: topForumTopics,
|
||||
hasFailedMessages: hasFailedMessages,
|
||||
isContact: isContact)
|
||||
isContact: isContact,
|
||||
autoremoveTimeout: autoremoveTimeout
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -725,7 +728,7 @@ private final class ChatListViewSpaceState {
|
||||
if !transaction.currentUpdatedPeerPresences.isEmpty {
|
||||
if self.orderedEntries.mutableScan({ entry in
|
||||
switch entry {
|
||||
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, _, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact):
|
||||
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, _, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact, autoremoveTimeout):
|
||||
var presencePeerId = entryRenderedPeer.peerId
|
||||
if let peer = entryRenderedPeer.peers[entryRenderedPeer.peerId], let associatedPeerId = peer.associatedPeerId {
|
||||
presencePeerId = associatedPeerId
|
||||
@ -744,7 +747,8 @@ private final class ChatListViewSpaceState {
|
||||
forumTopicData: forumTopicData,
|
||||
topForumTopics: topForumTopics,
|
||||
hasFailedMessages: hasFailedMessages,
|
||||
isContact: isContact
|
||||
isContact: isContact,
|
||||
autoremoveTimeout: autoremoveTimeout
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
@ -763,7 +767,7 @@ private final class ChatListViewSpaceState {
|
||||
let entryPeer: Peer
|
||||
let entryNotificationsPeerId: PeerId
|
||||
switch entry {
|
||||
case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _, _, _):
|
||||
case let .MessageEntry(_, _, _, _, _, _, entryRenderedPeer, _, _, _, _, _, _, _):
|
||||
if let peer = entryRenderedPeer.peer {
|
||||
entryPeer = peer
|
||||
entryNotificationsPeerId = peer.notificationSettingsPeerId ?? peer.id
|
||||
@ -881,10 +885,20 @@ private final class ChatListViewSpaceState {
|
||||
}
|
||||
}
|
||||
|
||||
if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty || !transaction.updatedPeerThreadsSummaries.isEmpty {
|
||||
var cachedPeerDataUpdated = false
|
||||
if !transaction.currentUpdatedCachedPeerData.isEmpty {
|
||||
let _ = self.orderedEntries.mutableScan { entry in
|
||||
if transaction.currentUpdatedCachedPeerData[entry.index.messageIndex.id.peerId] != nil {
|
||||
cachedPeerDataUpdated = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !transaction.currentUpdatedMessageTagSummaries.isEmpty || !transaction.currentUpdatedMessageActionsSummaries.isEmpty || !transaction.updatedPeerThreadsSummaries.isEmpty || cachedPeerDataUpdated {
|
||||
if self.orderedEntries.mutableScan({ entry in
|
||||
switch entry {
|
||||
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact):
|
||||
case let .MessageEntry(index, messages, readState, notificationSettings, isRemovedFromTotalUnreadCount, embeddedInterfaceState, entryRenderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailedMessages, isContact, autoremoveTimeout):
|
||||
var updatedChatListMessageTagSummaryInfo: [ChatListEntryMessageTagSummaryKey: ChatListMessageTagSummaryInfo] = tagSummaryInfo
|
||||
var didUpdateSummaryInfo = false
|
||||
|
||||
@ -938,6 +952,14 @@ private final class ChatListViewSpaceState {
|
||||
didUpdateSummaryInfo = true
|
||||
}
|
||||
|
||||
var updatedAutoremoveTimeout: Int32?
|
||||
if let cachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId) {
|
||||
updatedAutoremoveTimeout = postbox.seedConfiguration.decodeAutoremoveTimeout(cachedData)
|
||||
if updatedAutoremoveTimeout != autoremoveTimeout {
|
||||
didUpdateSummaryInfo = true
|
||||
}
|
||||
}
|
||||
|
||||
if didUpdateSummaryInfo {
|
||||
return .MessageEntry(
|
||||
index: index,
|
||||
@ -952,7 +974,8 @@ private final class ChatListViewSpaceState {
|
||||
forumTopicData: forumTopicData,
|
||||
topForumTopics: topForumTopics,
|
||||
hasFailedMessages: hasFailedMessages,
|
||||
isContact: isContact
|
||||
isContact: isContact,
|
||||
autoremoveTimeout: updatedAutoremoveTimeout
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
@ -1086,7 +1109,7 @@ private extension MutableChatListEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(index, _):
|
||||
return index.messageIndex.id.peerId
|
||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return index.messageIndex.id.peerId
|
||||
case .HoleEntry:
|
||||
return nil
|
||||
@ -1097,7 +1120,7 @@ private extension MutableChatListEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(index, _):
|
||||
return MutableChatListEntryIndex(index: index, isMessage: true)
|
||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return MutableChatListEntryIndex(index: index, isMessage: true)
|
||||
case let .HoleEntry(hole):
|
||||
return MutableChatListEntryIndex(index: ChatListIndex(pinningIndex: nil, messageIndex: hole.index), isMessage: false)
|
||||
@ -1108,7 +1131,7 @@ private extension MutableChatListEntry {
|
||||
switch self {
|
||||
case let .IntermediateMessageEntry(index, _):
|
||||
return .peer(index.messageIndex.id.peerId)
|
||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .MessageEntry(index, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return .peer(index.messageIndex.id.peerId)
|
||||
case let .HoleEntry(hole):
|
||||
return .hole(hole.index)
|
||||
@ -1558,7 +1581,12 @@ struct ChatListViewState {
|
||||
}
|
||||
}
|
||||
|
||||
let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId))
|
||||
var autoremoveTimeout: Int32?
|
||||
if let cachedData = postbox.cachedPeerDataTable.get(index.messageIndex.id.peerId) {
|
||||
autoremoveTimeout = postbox.seedConfiguration.decodeAutoremoveTimeout(cachedData)
|
||||
}
|
||||
|
||||
let updatedEntry: MutableChatListEntry = .MessageEntry(index: index, messages: renderedMessages, readState: readState, notificationSettings: notificationSettings, isRemovedFromTotalUnreadCount: isRemovedFromTotalUnreadCount, embeddedInterfaceState: postbox.peerChatInterfaceStateTable.get(index.messageIndex.id.peerId), renderedPeer: renderedPeer, presence: presence, tagSummaryInfo: tagSummaryInfo, forumTopicData: forumTopicData, topForumTopics: topForumTopics, hasFailedMessages: false, isContact: postbox.contactsTable.isContact(peerId: index.messageIndex.id.peerId), autoremoveTimeout: autoremoveTimeout)
|
||||
if directionIndex == 0 {
|
||||
self.stateBySpace[space]!.orderedEntries.setLowerOrAtAnchorAtArrayIndex(listIndex, to: updatedEntry)
|
||||
} else {
|
||||
|
@ -1,23 +1,58 @@
|
||||
|
||||
final class MutableInvalidatedMessageHistoryTagSummariesView: MutablePostboxView {
|
||||
private let peerId: PeerId?
|
||||
private let threadId: Int64?
|
||||
private let namespace: MessageId.Namespace
|
||||
private let tagMask: MessageTags
|
||||
|
||||
var entries = Set<InvalidatedMessageHistoryTagsSummaryEntry>()
|
||||
|
||||
init(postbox: PostboxImpl, tagMask: MessageTags, namespace: MessageId.Namespace) {
|
||||
init(postbox: PostboxImpl, peerId: PeerId?, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace) {
|
||||
self.peerId = peerId
|
||||
self.threadId = threadId
|
||||
self.tagMask = tagMask
|
||||
self.namespace = namespace
|
||||
|
||||
for entry in postbox.invalidatedMessageHistoryTagsSummaryTable.get(tagMask: tagMask, namespace: namespace) {
|
||||
self.entries.insert(entry)
|
||||
if let peerId = self.peerId {
|
||||
if let entry = postbox.invalidatedMessageHistoryTagsSummaryTable.get(peerId: peerId, threadId: self.threadId, tagMask: self.tagMask, namespace: self.namespace) {
|
||||
self.entries.insert(entry)
|
||||
}
|
||||
} else {
|
||||
for entry in postbox.invalidatedMessageHistoryTagsSummaryTable.get(tagMask: tagMask, namespace: namespace) {
|
||||
self.entries.insert(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func replay(postbox: PostboxImpl, transaction: PostboxTransaction) -> Bool {
|
||||
var updated = false
|
||||
for operation in transaction.currentInvalidateMessageTagSummaries {
|
||||
switch operation {
|
||||
|
||||
if let peerId = self.peerId {
|
||||
var maybeUpdated = false
|
||||
loop: for operation in transaction.currentInvalidateMessageTagSummaries {
|
||||
switch operation {
|
||||
case let .add(entry):
|
||||
if entry.key.peerId == peerId && entry.key.threadId == self.threadId {
|
||||
maybeUpdated = true
|
||||
break loop
|
||||
}
|
||||
case let .remove(key):
|
||||
if key.peerId == peerId && key.threadId == self.threadId {
|
||||
maybeUpdated = true
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
if maybeUpdated {
|
||||
self.entries.removeAll()
|
||||
if let entry = postbox.invalidatedMessageHistoryTagsSummaryTable.get(peerId: peerId, threadId: self.threadId, tagMask: self.tagMask, namespace: self.namespace) {
|
||||
self.entries.insert(entry)
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
} else {
|
||||
for operation in transaction.currentInvalidateMessageTagSummaries {
|
||||
switch operation {
|
||||
case let .add(entry):
|
||||
if entry.key.namespace == self.namespace && entry.key.tagMask == self.tagMask {
|
||||
self.entries.insert(entry)
|
||||
@ -33,8 +68,10 @@ final class MutableInvalidatedMessageHistoryTagSummariesView: MutablePostboxView
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
|
@ -74,6 +74,10 @@ final class InvalidatedMessageHistoryTagsSummaryTable: Table {
|
||||
return entries
|
||||
}
|
||||
|
||||
func get(peerId: PeerId, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace) -> InvalidatedMessageHistoryTagsSummaryEntry? {
|
||||
return self.get(InvalidatedMessageHistoryTagsSummaryKey(peerId: peerId, namespace: namespace, tagMask: tagMask, threadId: threadId))
|
||||
}
|
||||
|
||||
private func get(_ key: InvalidatedMessageHistoryTagsSummaryKey) -> InvalidatedMessageHistoryTagsSummaryEntry? {
|
||||
if let value = self.valueBox.get(self.table, key: self.key(key)) {
|
||||
var version: Int32 = 0
|
||||
|
@ -75,6 +75,7 @@ public final class SeedConfiguration {
|
||||
public let defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings
|
||||
public let mergeMessageAttributes: ([MessageAttribute], inout [MessageAttribute]) -> Void
|
||||
public let decodeMessageThreadInfo: (CodableEntry) -> Message.AssociatedThreadInfo?
|
||||
public let decodeAutoremoveTimeout: (CachedPeerData) -> Int32?
|
||||
|
||||
public init(
|
||||
globalMessageIdsPeerIdNamespaces: Set<GlobalMessageIdsNamespace>,
|
||||
@ -99,7 +100,8 @@ public final class SeedConfiguration {
|
||||
getGlobalNotificationSettings: @escaping (Transaction) -> PostboxGlobalNotificationSettings?,
|
||||
defaultGlobalNotificationSettings: PostboxGlobalNotificationSettings,
|
||||
mergeMessageAttributes: @escaping ([MessageAttribute], inout [MessageAttribute]) -> Void,
|
||||
decodeMessageThreadInfo: @escaping (CodableEntry) -> Message.AssociatedThreadInfo?
|
||||
decodeMessageThreadInfo: @escaping (CodableEntry) -> Message.AssociatedThreadInfo?,
|
||||
decodeAutoremoveTimeout: @escaping (CachedPeerData) -> Int32?
|
||||
) {
|
||||
self.globalMessageIdsPeerIdNamespaces = globalMessageIdsPeerIdNamespaces
|
||||
self.initializeChatListWithHole = initializeChatListWithHole
|
||||
@ -120,5 +122,6 @@ public final class SeedConfiguration {
|
||||
self.defaultGlobalNotificationSettings = defaultGlobalNotificationSettings
|
||||
self.mergeMessageAttributes = mergeMessageAttributes
|
||||
self.decodeMessageThreadInfo = decodeMessageThreadInfo
|
||||
self.decodeAutoremoveTimeout = decodeAutoremoveTimeout
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ public enum PostboxViewKey: Hashable {
|
||||
case globalMessageTags(globalTag: GlobalMessageTags, position: MessageIndex, count: Int, groupingPredicate: ((Message, Message) -> Bool)?)
|
||||
case peer(peerId: PeerId, components: PeerViewComponents)
|
||||
case pendingMessageActions(type: PendingMessageActionType)
|
||||
case invalidatedMessageHistoryTagSummaries(tagMask: MessageTags, namespace: MessageId.Namespace)
|
||||
case invalidatedMessageHistoryTagSummaries(peerId: PeerId?, threadId: Int64?, tagMask: MessageTags, namespace: MessageId.Namespace)
|
||||
case pendingMessageActionsSummary(type: PendingMessageActionType, peerId: PeerId, namespace: MessageId.Namespace)
|
||||
case historyTagSummaryView(tag: MessageTags, peerId: PeerId, threadId: Int64?, namespace: MessageId.Namespace)
|
||||
case cachedPeerData(peerId: PeerId)
|
||||
@ -61,7 +61,9 @@ public enum PostboxViewKey: Hashable {
|
||||
hasher.combine(peerId)
|
||||
case let .pendingMessageActions(type):
|
||||
hasher.combine(type)
|
||||
case let .invalidatedMessageHistoryTagSummaries(tagMask, namespace):
|
||||
case let .invalidatedMessageHistoryTagSummaries(peerId, threadId, tagMask, namespace):
|
||||
hasher.combine(peerId)
|
||||
hasher.combine(threadId)
|
||||
hasher.combine(tagMask)
|
||||
hasher.combine(namespace)
|
||||
case let .pendingMessageActionsSummary(type, peerId, namespace):
|
||||
@ -191,8 +193,8 @@ public enum PostboxViewKey: Hashable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .invalidatedMessageHistoryTagSummaries:
|
||||
if case .invalidatedMessageHistoryTagSummaries = rhs {
|
||||
case let .invalidatedMessageHistoryTagSummaries(peerId, threadId, tagMask, namespace):
|
||||
if case .invalidatedMessageHistoryTagSummaries(peerId, threadId, tagMask, namespace) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -395,8 +397,8 @@ func postboxViewForKey(postbox: PostboxImpl, key: PostboxViewKey) -> MutablePost
|
||||
return MutablePeerView(postbox: postbox, peerId: peerId, components: components)
|
||||
case let .pendingMessageActions(type):
|
||||
return MutablePendingMessageActionsView(postbox: postbox, type: type)
|
||||
case let .invalidatedMessageHistoryTagSummaries(tagMask, namespace):
|
||||
return MutableInvalidatedMessageHistoryTagSummariesView(postbox: postbox, tagMask: tagMask, namespace: namespace)
|
||||
case let .invalidatedMessageHistoryTagSummaries(peerId, threadId, tagMask, namespace):
|
||||
return MutableInvalidatedMessageHistoryTagSummariesView(postbox: postbox, peerId: peerId, threadId: threadId, tagMask: tagMask, namespace: namespace)
|
||||
case let .pendingMessageActionsSummary(type, peerId, namespace):
|
||||
return MutablePendingMessageActionsSummaryView(postbox: postbox, type: type, peerId: peerId, namespace: namespace)
|
||||
case let .historyTagSummaryView(tag, peerId, threadId, namespace):
|
||||
|
@ -0,0 +1,109 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import AccountContext
|
||||
|
||||
class GlobalAutoremoveHeaderItem: ListViewItem, ItemListItem {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let sectionId: ItemListSectionId
|
||||
|
||||
init(context: AccountContext, theme: PresentationTheme, sectionId: ItemListSectionId) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.sectionId = sectionId
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = GlobalAutoremoveHeaderItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
guard let nodeValue = node() as? GlobalAutoremoveHeaderItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let titleFont = Font.regular(13.0)
|
||||
|
||||
class GlobalAutoremoveHeaderItemNode: ListViewItemNode {
|
||||
private var animationNode: AnimatedStickerNode
|
||||
|
||||
private var item: GlobalAutoremoveHeaderItem?
|
||||
|
||||
init() {
|
||||
self.animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.animationNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: GlobalAutoremoveHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
return { item, params, neighbors in
|
||||
//let leftInset: CGFloat = 32.0 + params.leftInset
|
||||
let topInset: CGFloat = 92.0
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: topInset)
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.item == nil {
|
||||
strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "GlobalAutoRemove"), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
|
||||
strongSelf.animationNode.visibility = true
|
||||
}
|
||||
strongSelf.item = item
|
||||
|
||||
let iconSize = CGSize(width: 96.0, height: 96.0)
|
||||
strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: -10.0), size: iconSize)
|
||||
strongSelf.animationNode.updateLayout(size: iconSize)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
}
|
@ -0,0 +1,407 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import AuthTransferUI
|
||||
import ItemListPeerActionItem
|
||||
import DeviceAccess
|
||||
import QrCodeUI
|
||||
import ChatTimerScreen
|
||||
import UndoUI
|
||||
|
||||
private final class GlobalAutoremoveScreenArguments {
|
||||
let context: AccountContext
|
||||
let updateValue: (Int32) -> Void
|
||||
let openCustomValue: () -> Void
|
||||
let infoLinkAction: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
updateValue: @escaping (Int32) -> Void,
|
||||
openCustomValue: @escaping () -> Void,
|
||||
infoLinkAction: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.updateValue = updateValue
|
||||
self.openCustomValue = openCustomValue
|
||||
self.infoLinkAction = infoLinkAction
|
||||
}
|
||||
}
|
||||
|
||||
private enum GlobalAutoremoveSection: Int32 {
|
||||
case header
|
||||
case general
|
||||
}
|
||||
|
||||
private enum GlobalAutoremoveEntry: ItemListNodeEntry {
|
||||
case header
|
||||
case sectionHeader(String)
|
||||
case timerOption(value: Int32, text: String, isSelected: Bool)
|
||||
case customAction(String)
|
||||
case info(String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .header:
|
||||
return GlobalAutoremoveSection.header.rawValue
|
||||
case .sectionHeader, .timerOption, .customAction, .info:
|
||||
return GlobalAutoremoveSection.general.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int {
|
||||
return self.sortIndex
|
||||
}
|
||||
|
||||
var sortIndex: Int {
|
||||
switch self {
|
||||
case .header:
|
||||
return 0
|
||||
case .sectionHeader:
|
||||
return 1
|
||||
case let .timerOption(value, _, _):
|
||||
return 1000 + Int(value)
|
||||
case .customAction:
|
||||
return Int.max - 1000 + 0
|
||||
case .info:
|
||||
return Int.max - 1000 + 1
|
||||
}
|
||||
}
|
||||
|
||||
static func ==(lhs: GlobalAutoremoveEntry, rhs: GlobalAutoremoveEntry) -> Bool {
|
||||
switch lhs {
|
||||
case .header:
|
||||
if case .header = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .sectionHeader(text):
|
||||
if case .sectionHeader(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .timerOption(value, text, isSelected):
|
||||
if case .timerOption(value, text, isSelected) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .customAction(text):
|
||||
if case .customAction(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .info(text):
|
||||
if case .info(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: GlobalAutoremoveEntry, rhs: GlobalAutoremoveEntry) -> Bool {
|
||||
return lhs.sortIndex < rhs.sortIndex
|
||||
}
|
||||
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! GlobalAutoremoveScreenArguments
|
||||
switch self {
|
||||
case .header:
|
||||
return GlobalAutoremoveHeaderItem(context: arguments.context, theme: presentationData.theme, sectionId: self.section)
|
||||
case let .sectionHeader(text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .timerOption(value, text, isSelected):
|
||||
return ItemListCheckboxItem(presentationData: presentationData, title: text, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||
arguments.updateValue(value)
|
||||
})
|
||||
case let .customAction(text):
|
||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openCustomValue()
|
||||
})
|
||||
case let .info(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
|
||||
arguments.infoLinkAction()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct GlobalAutoremoveScreenState: Equatable {
|
||||
var additionalValues: Set<Int32>
|
||||
var updatedValue: Int32
|
||||
}
|
||||
|
||||
private func globalAutoremoveScreenEntries(presentationData: PresentationData, state: GlobalAutoremoveScreenState) -> [GlobalAutoremoveEntry] {
|
||||
var entries: [GlobalAutoremoveEntry] = []
|
||||
|
||||
entries.append(.header)
|
||||
|
||||
let effectiveCurrentValue = state.updatedValue
|
||||
|
||||
//TODO:localize
|
||||
entries.append(.sectionHeader("SELF-DESTRUCT TIMER"))
|
||||
|
||||
var values: [Int32] = [
|
||||
0,
|
||||
1 * 24 * 60 * 60,
|
||||
7 * 24 * 60 * 60,
|
||||
31 * 24 * 60 * 60
|
||||
]
|
||||
|
||||
if !values.contains(effectiveCurrentValue) {
|
||||
values.append(effectiveCurrentValue)
|
||||
}
|
||||
for value in state.additionalValues {
|
||||
if !values.contains(value) {
|
||||
values.append(value)
|
||||
}
|
||||
}
|
||||
|
||||
values.sort()
|
||||
|
||||
//TODO:localize
|
||||
for value in values {
|
||||
let text: String
|
||||
if value == 0 {
|
||||
text = "Off"
|
||||
} else {
|
||||
text = "After \(timeIntervalString(strings: presentationData.strings, value: value))"
|
||||
}
|
||||
entries.append(.timerOption(value: value, text: text, isSelected: effectiveCurrentValue == value))
|
||||
}
|
||||
|
||||
entries.append(.customAction("Set Custom Time..."))
|
||||
|
||||
//TODO:localize
|
||||
if effectiveCurrentValue == 0 {
|
||||
entries.append(.info("If enabled, all new messages in chats you start will be automatically deleted for everyone at some point after they have been sent. This will not affect your existing chats."))
|
||||
} else {
|
||||
entries.append(.info("All new messages in chats you started will be automatically deleted for everyone 3 weeks after they have been sent. You can also [apply this setting for your existing chats]()."))
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
public func globalAutoremoveScreen(context: AccountContext, initialValue: Int32, updated: @escaping (Int32) -> Void) -> ViewController {
|
||||
let initialState = GlobalAutoremoveScreenState(
|
||||
additionalValues: Set([initialValue]),
|
||||
updatedValue: initialValue
|
||||
)
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((GlobalAutoremoveScreenState) -> GlobalAutoremoveScreenState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var getController: (() -> ViewController?)?
|
||||
|
||||
let _ = dismissImpl
|
||||
let _ = pushControllerImpl
|
||||
let _ = presentControllerImpl
|
||||
let _ = updateState
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let updateTimeoutDisposable = MetaDisposable()
|
||||
actionsDisposable.add(updateTimeoutDisposable)
|
||||
|
||||
let updateValue: (Int32) -> Void = { timeout in
|
||||
let apply: (Int32) -> Void = { timeout in
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.updatedValue = timeout
|
||||
if timeout != 0 {
|
||||
state.additionalValues.insert(timeout)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
|
||||
|
||||
var isOn: Bool = true
|
||||
var text: String?
|
||||
if timeout != 0 {
|
||||
text = "Messages in all new chats you start will be automatically deleted after \(timeIntervalString(strings: presentationData.strings, value: timeout))."
|
||||
} else {
|
||||
isOn = false
|
||||
text = "Messages in all new chats you start will not be automatically deleted.";
|
||||
}
|
||||
if let text = text {
|
||||
var animateAsReplacement = false
|
||||
if let window = getController?()?.window {
|
||||
window.forEachController { other in
|
||||
if let other = other as? UndoOverlayController {
|
||||
animateAsReplacement = true
|
||||
other.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .autoDelete(isOn: isOn, title: nil, text: text), elevatedLayout: false, animateInAsReplacement: animateAsReplacement, action: { _ in return false }), nil)
|
||||
}
|
||||
|
||||
updateTimeoutDisposable.set((context.engine.privacy.updateGlobalMessageRemovalTimeout(timeout: timeout == 0 ? nil : timeout)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
updated(timeout)
|
||||
}))
|
||||
}
|
||||
if timeout == 0 || stateValue.with({ $0 }).updatedValue != 0 {
|
||||
apply(timeout)
|
||||
} else {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
//TODO:localize
|
||||
let valueText = timeIntervalString(strings: presentationData.strings, value: timeout)
|
||||
presentControllerImpl?(standardTextAlertController(
|
||||
theme: AlertControllerTheme(presentationData: presentationData),
|
||||
title: "Self-Destruct Timer",
|
||||
text: "Are you sure you want all messages in new chats started by you to be automatically deleted for everyone \(valueText) after they have been sent?",
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultAction, title: "Enable Auto-Deletion", action: {
|
||||
apply(timeout)
|
||||
}),
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
|
||||
],
|
||||
actionLayout: .vertical
|
||||
), nil)
|
||||
}
|
||||
}
|
||||
|
||||
let arguments = GlobalAutoremoveScreenArguments(
|
||||
context: context,
|
||||
updateValue: { value in
|
||||
updateValue(value)
|
||||
},
|
||||
openCustomValue: {
|
||||
let currentValue = stateValue.with({ $0 }).updatedValue
|
||||
let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: currentValue == 0 ? nil : currentValue, dismissByTapOutside: true, completion: { value in
|
||||
updateValue(value)
|
||||
})
|
||||
presentControllerImpl?(controller, nil)
|
||||
},
|
||||
infoLinkAction: {
|
||||
let value = stateValue.with({ $0 }).updatedValue
|
||||
if value == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let valueText = timeIntervalString(strings: presentationData.strings, value: value)
|
||||
|
||||
//TODO:localize
|
||||
let selectionController = context.sharedContext.makeContactMultiselectionController(ContactMultiselectionControllerParams(
|
||||
context: context,
|
||||
mode: .chatSelection(ContactMultiselectionControllerMode.ChatSelection(
|
||||
title: "Select Chats",
|
||||
searchPlaceholder: "Select chats to apply the \(valueText) self-destruct timer",
|
||||
selectedChats: Set(),
|
||||
additionalCategories: nil,
|
||||
chatListFilters: nil,
|
||||
displayAutoremoveTimeout: true
|
||||
)),
|
||||
options: [],
|
||||
filters: [.excludeSelf],
|
||||
isPeerEnabled: { peer in
|
||||
var canManage = false
|
||||
if case let .user(user) = peer {
|
||||
if user.botInfo == nil {
|
||||
canManage = true
|
||||
}
|
||||
} else if case .secretChat = peer {
|
||||
canManage = true
|
||||
} else if case let .legacyGroup(group) = peer {
|
||||
canManage = !group.hasBannedPermission(.banChangeInfo)
|
||||
} else if case let .channel(channel) = peer {
|
||||
canManage = channel.hasPermission(.changeInfo)
|
||||
}
|
||||
return canManage
|
||||
}
|
||||
))
|
||||
selectionController.navigationPresentation = .modal
|
||||
|
||||
let _ = (selectionController.result
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak selectionController] result in
|
||||
var contacts: [ContactListPeerId] = []
|
||||
if case let .result(peerIdsValue, _) = result {
|
||||
contacts = peerIdsValue
|
||||
}
|
||||
let peerIds = contacts.compactMap { item -> EnginePeer.Id? in
|
||||
switch item {
|
||||
case let .peer(id):
|
||||
return id
|
||||
case .deviceContact:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if peerIds.isEmpty {
|
||||
selectionController?.dismiss()
|
||||
} else {
|
||||
selectionController?.displayProgress = true
|
||||
let _ = (context.engine.peers.setChatMessageAutoremoveTimeouts(peerIds: peerIds, timeout: value)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
selectionController?.dismiss()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
pushControllerImpl?(selectionController)
|
||||
}
|
||||
)
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(),
|
||||
context.sharedContext.presentationData,
|
||||
statePromise.get()
|
||||
)
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let rightNavigationButton: ItemListNavigationButton? = nil
|
||||
|
||||
//TODO:localize
|
||||
let title: ItemListControllerTitle = .text("Auto-Delete Messages")
|
||||
|
||||
let entries: [GlobalAutoremoveEntry] = globalAutoremoveScreenEntries(presentationData: presentationData, state: state)
|
||||
|
||||
let animateChanges = false
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: entries, style: .blocks, emptyStateItem: nil, crossfadeState: false, animateChanges: animateChanges, scrollEnabled: true)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|> afterDisposed {
|
||||
actionsDisposable.dispose()
|
||||
}
|
||||
|
||||
let controller = ItemListController(context: context, state: signal)
|
||||
getController = { [weak controller] in
|
||||
return controller
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, p in
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
controller.present(c, in: .window(.root), with: p)
|
||||
}
|
||||
pushControllerImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
@ -105,7 +105,6 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
case accountHeader(PresentationTheme, String)
|
||||
case accountTimeout(PresentationTheme, String, String)
|
||||
case accountInfo(PresentationTheme, String)
|
||||
case messageAutoremoveHeader(PresentationTheme, String)
|
||||
case messageAutoremoveTimeout(PresentationTheme, String, String)
|
||||
case messageAutoremoveInfo(PresentationTheme, String)
|
||||
case dataSettings(PresentationTheme, String)
|
||||
@ -113,18 +112,16 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .blockedPeers, .activeSessions, .passcode, .twoStepVerification, .loginEmail, .loginEmailInfo:
|
||||
return PrivacyAndSecuritySection.general.rawValue
|
||||
case .privacyHeader, .phoneNumberPrivacy, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .voiceCallPrivacy, .voiceMessagePrivacy, .selectivePrivacyInfo:
|
||||
return PrivacyAndSecuritySection.privacy.rawValue
|
||||
case .autoArchiveHeader, .autoArchive, .autoArchiveInfo:
|
||||
return PrivacyAndSecuritySection.autoArchive.rawValue
|
||||
case .accountHeader, .accountTimeout, .accountInfo:
|
||||
return PrivacyAndSecuritySection.account.rawValue
|
||||
case .messageAutoremoveHeader, .messageAutoremoveTimeout, .messageAutoremoveInfo:
|
||||
return PrivacyAndSecuritySection.messageAutoremove.rawValue
|
||||
case .dataSettings, .dataSettingsInfo:
|
||||
return PrivacyAndSecuritySection.dataSettings.rawValue
|
||||
case .blockedPeers, .activeSessions, .passcode, .twoStepVerification, .loginEmail, .loginEmailInfo, .messageAutoremoveTimeout, .messageAutoremoveInfo:
|
||||
return PrivacyAndSecuritySection.general.rawValue
|
||||
case .privacyHeader, .phoneNumberPrivacy, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .voiceCallPrivacy, .voiceMessagePrivacy, .selectivePrivacyInfo:
|
||||
return PrivacyAndSecuritySection.privacy.rawValue
|
||||
case .autoArchiveHeader, .autoArchive, .autoArchiveInfo:
|
||||
return PrivacyAndSecuritySection.autoArchive.rawValue
|
||||
case .accountHeader, .accountTimeout, .accountInfo:
|
||||
return PrivacyAndSecuritySection.account.rawValue
|
||||
case .dataSettings, .dataSettingsInfo:
|
||||
return PrivacyAndSecuritySection.dataSettings.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,50 +135,48 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
return 3
|
||||
case .twoStepVerification:
|
||||
return 4
|
||||
case .loginEmail:
|
||||
return 5
|
||||
case .loginEmailInfo:
|
||||
return 6
|
||||
case .privacyHeader:
|
||||
return 7
|
||||
case .phoneNumberPrivacy:
|
||||
return 8
|
||||
case .lastSeenPrivacy:
|
||||
return 9
|
||||
case .profilePhotoPrivacy:
|
||||
return 10
|
||||
case .voiceCallPrivacy:
|
||||
return 11
|
||||
case .voiceMessagePrivacy:
|
||||
return 12
|
||||
case .forwardPrivacy:
|
||||
return 13
|
||||
case .groupPrivacy:
|
||||
return 14
|
||||
case .selectivePrivacyInfo:
|
||||
return 15
|
||||
case .autoArchiveHeader:
|
||||
return 16
|
||||
case .autoArchive:
|
||||
return 17
|
||||
case .autoArchiveInfo:
|
||||
return 18
|
||||
case .accountHeader:
|
||||
return 19
|
||||
case .accountTimeout:
|
||||
return 20
|
||||
case .accountInfo:
|
||||
return 21
|
||||
case .messageAutoremoveHeader:
|
||||
return 22
|
||||
case .messageAutoremoveTimeout:
|
||||
return 23
|
||||
return 5
|
||||
case .messageAutoremoveInfo:
|
||||
return 24
|
||||
return 6
|
||||
case .loginEmail:
|
||||
return 7
|
||||
case .loginEmailInfo:
|
||||
return 8
|
||||
case .privacyHeader:
|
||||
return 9
|
||||
case .phoneNumberPrivacy:
|
||||
return 10
|
||||
case .lastSeenPrivacy:
|
||||
return 11
|
||||
case .profilePhotoPrivacy:
|
||||
return 12
|
||||
case .voiceCallPrivacy:
|
||||
return 13
|
||||
case .voiceMessagePrivacy:
|
||||
return 14
|
||||
case .forwardPrivacy:
|
||||
return 15
|
||||
case .groupPrivacy:
|
||||
return 16
|
||||
case .selectivePrivacyInfo:
|
||||
return 17
|
||||
case .autoArchiveHeader:
|
||||
return 18
|
||||
case .autoArchive:
|
||||
return 19
|
||||
case .autoArchiveInfo:
|
||||
return 20
|
||||
case .accountHeader:
|
||||
return 21
|
||||
case .accountTimeout:
|
||||
return 22
|
||||
case .accountInfo:
|
||||
return 23
|
||||
case .dataSettings:
|
||||
return 25
|
||||
return 24
|
||||
case .dataSettingsInfo:
|
||||
return 26
|
||||
return 25
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,12 +308,6 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .messageAutoremoveHeader(lhsTheme, lhsText):
|
||||
if case let .messageAutoremoveHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .messageAutoremoveTimeout(lhsTheme, lhsText, lhsValue):
|
||||
if case let .messageAutoremoveTimeout(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
@ -397,6 +386,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openTwoStepVerification(data)
|
||||
})
|
||||
case let .messageAutoremoveTimeout(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Timer")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.setupMessageAutoremove()
|
||||
}, tag: PrivacyAndSecurityEntryTag.messageAutoremoveTimeout)
|
||||
case let .messageAutoremoveInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .loginEmail(_, text, emailPattern):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/LoginEmail")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openEmailSettings(emailPattern)
|
||||
@ -423,14 +418,6 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
}, tag: PrivacyAndSecurityEntryTag.accountTimeout)
|
||||
case let .accountInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .messageAutoremoveHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .messageAutoremoveTimeout(_, text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.setupMessageAutoremove()
|
||||
}, tag: PrivacyAndSecurityEntryTag.messageAutoremoveTimeout)
|
||||
case let .messageAutoremoveInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .dataSettings(_, text):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: "", sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openDataSettings()
|
||||
@ -527,6 +514,26 @@ private func privacyAndSecurityControllerEntries(
|
||||
}
|
||||
entries.append(.twoStepVerification(presentationData.theme, presentationData.strings.PrivacySettings_TwoStepAuth, twoStepAuthString, twoStepAuthData))
|
||||
|
||||
//TODO:localize
|
||||
if let privacySettings = privacySettings {
|
||||
let value: Int32?
|
||||
if let updatingMessageAutoremoveTimeoutValue = state.updatingMessageAutoremoveTimeoutValue {
|
||||
value = updatingMessageAutoremoveTimeoutValue
|
||||
} else {
|
||||
value = privacySettings.messageAutoremoveTimeout
|
||||
}
|
||||
let valueText: String
|
||||
if let value {
|
||||
valueText = timeIntervalString(strings: presentationData.strings, value: value)
|
||||
} else {
|
||||
valueText = "Off"
|
||||
}
|
||||
entries.append(.messageAutoremoveTimeout(presentationData.theme, "Auto-Delete Messages", valueText))
|
||||
} else {
|
||||
entries.append(.messageAutoremoveTimeout(presentationData.theme, "Auto-Delete Messages", presentationData.strings.Channel_NotificationLoading))
|
||||
}
|
||||
entries.append(.messageAutoremoveInfo(presentationData.theme, "Automatically delete messages for everyone after a period of time in all new chats you start."))
|
||||
|
||||
if loginEmail != nil {
|
||||
entries.append(.loginEmail(presentationData.theme, presentationData.strings.PrivacySettings_LoginEmail, loginEmail))
|
||||
entries.append(.loginEmailInfo(presentationData.theme, presentationData.strings.PrivacySettings_LoginEmailInfo))
|
||||
@ -588,27 +595,6 @@ private func privacyAndSecurityControllerEntries(
|
||||
}
|
||||
entries.append(.accountInfo(presentationData.theme, presentationData.strings.PrivacySettings_DeleteAccountHelp))
|
||||
|
||||
//TODO:localize
|
||||
entries.append(.messageAutoremoveHeader(presentationData.theme, "MESSAGE TIMER"))
|
||||
if let privacySettings = privacySettings {
|
||||
let value: Int32?
|
||||
if let updatingMessageAutoremoveTimeoutValue = state.updatingMessageAutoremoveTimeoutValue {
|
||||
value = updatingMessageAutoremoveTimeoutValue
|
||||
} else {
|
||||
value = privacySettings.messageAutoremoveTimeout
|
||||
}
|
||||
let valueText: String
|
||||
if let value {
|
||||
valueText = timeIntervalString(strings: presentationData.strings, value: value)
|
||||
} else {
|
||||
valueText = "Never"
|
||||
}
|
||||
entries.append(.messageAutoremoveTimeout(presentationData.theme, "Auto-Delete Messages", valueText))
|
||||
} else {
|
||||
entries.append(.messageAutoremoveTimeout(presentationData.theme, "Auto-Delete Messages", presentationData.strings.Channel_NotificationLoading))
|
||||
}
|
||||
entries.append(.messageAutoremoveInfo(presentationData.theme, "Set a global message timer."))
|
||||
|
||||
entries.append(.dataSettings(presentationData.theme, presentationData.strings.PrivacySettings_DataSettings))
|
||||
entries.append(.dataSettingsInfo(presentationData.theme, presentationData.strings.PrivacySettings_DataSettingsHelp))
|
||||
|
||||
@ -1067,79 +1053,21 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
let signal = privacySettingsPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
updateAccountTimeoutDisposable.set(signal.start(next: { [weak updateAccountTimeoutDisposable] privacySettingsValue in
|
||||
if let privacySettingsValue = privacySettingsValue {
|
||||
let timeoutAction: (Int32?) -> Void = { timeout in
|
||||
if let updateAccountTimeoutDisposable = updateAccountTimeoutDisposable {
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.updatingMessageAutoremoveTimeoutValue = timeout
|
||||
return state
|
||||
}
|
||||
let applyTimeout: Signal<Void, NoError> = privacySettingsPromise.get()
|
||||
|> filter { $0 != nil }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { value -> Signal<Void, NoError> in
|
||||
if let value = value {
|
||||
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: timeout)))
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
updateAccountTimeoutDisposable.set((context.engine.privacy.updateGlobalMessageRemovalTimeout(timeout: timeout)
|
||||
|> then(applyTimeout)
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.updatingMessageAutoremoveTimeoutValue = nil
|
||||
return state
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: privacySettingsValue.messageAutoremoveTimeout, dismissByTapOutside: true, completion: { value in
|
||||
timeoutAction(value)
|
||||
})
|
||||
presentControllerImpl?(controller)
|
||||
|
||||
/*
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = ActionSheetController(presentationData: presentationData)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
let timeoutValues: [Int32] = [
|
||||
1 * 30 * 24 * 60 * 60,
|
||||
3 * 30 * 24 * 60 * 60,
|
||||
6 * 30 * 24 * 60 * 60,
|
||||
365 * 24 * 60 * 60
|
||||
]
|
||||
var timeoutItems: [ActionSheetItem] = timeoutValues.map { value in
|
||||
return ActionSheetButtonItem(title: timeIntervalString(strings: presentationData.strings, value: value), action: {
|
||||
dismissAction()
|
||||
timeoutAction(value)
|
||||
})
|
||||
}
|
||||
timeoutItems.append(ActionSheetButtonItem(title: presentationData.strings.PrivacySettings_DeleteAccountNow, color: .destructive, action: {
|
||||
dismissAction()
|
||||
|
||||
guard let navigationController = getNavigationControllerImpl?() else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (combineLatest(twoStepAuth.get(), twoStepAuthDataValue.get())
|
||||
updateAccountTimeoutDisposable.set(signal.start(next: { privacySettingsValue in
|
||||
if let privacySettingsValue {
|
||||
pushControllerImpl?(globalAutoremoveScreen(context: context, initialValue: privacySettingsValue.messageAutoremoveTimeout ?? 0, updated: { updatedValue in
|
||||
let applyTimeout: Signal<Void, NoError> = privacySettingsPromise.get()
|
||||
|> filter { $0 != nil }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { hasTwoStepAuth, twoStepAuthData in
|
||||
let optionsController = deleteAccountOptionsController(context: context, navigationController: navigationController, hasTwoStepAuth: hasTwoStepAuth ?? false, twoStepAuthData: twoStepAuthData)
|
||||
pushControllerImpl?(optionsController, true)
|
||||
})
|
||||
}))
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: timeoutItems),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
presentControllerImpl?(controller)*/
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { value -> Signal<Void, NoError> in
|
||||
if let value = value {
|
||||
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: value.voiceMessages, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout, messageAutoremoveTimeout: updatedValue == 0 ? nil : updatedValue)))
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
let _ = applyTimeout.start()
|
||||
}), true)
|
||||
}
|
||||
}))
|
||||
}, openDataSettings: {
|
||||
|
@ -1028,7 +1028,7 @@ public final class ShareController: ViewController {
|
||||
var peers: [EngineRenderedPeer] = []
|
||||
for entry in view.0.entries.reversed() {
|
||||
switch entry {
|
||||
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _):
|
||||
case let .MessageEntry(_, _, _, _, _, renderedPeer, _, _, _, _, _, _, _):
|
||||
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(peer) {
|
||||
peers.append(EngineRenderedPeer(renderedPeer))
|
||||
}
|
||||
|
@ -1315,7 +1315,8 @@ private func threadList(context: AccountContext, peerId: EnginePeer.Id) -> Signa
|
||||
forumTopicData: nil,
|
||||
topForumTopicItems: [],
|
||||
hasFailed: false,
|
||||
isContact: false
|
||||
isContact: false,
|
||||
autoremoveTimeout: nil
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -596,6 +596,53 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
}
|
||||
strongSelf.hasVideo = true
|
||||
strongSelf.updated(transition: .immediate)
|
||||
|
||||
/*let engine = strongSelf.call.accountContext.engine
|
||||
guard let info = strongSelf.call.initialCall else {
|
||||
return
|
||||
}
|
||||
let _ = (engine.calls.getAudioBroadcastDataSource(callId: info.id, accessHash: info.accessHash)
|
||||
|> mapToSignal { source -> Signal<Data?, NoError> in
|
||||
guard let source else {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
let time = engine.calls.requestStreamState(dataSource: source, callId: info.id, accessHash: info.accessHash)
|
||||
|> map { state -> Int64? in
|
||||
guard let state else {
|
||||
return nil
|
||||
}
|
||||
return state.channels.first?.latestTimestamp
|
||||
}
|
||||
|
||||
return time
|
||||
|> mapToSignal { latestTimestamp -> Signal<Data?, NoError> in
|
||||
guard let latestTimestamp else {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
let durationMilliseconds: Int64 = 32000
|
||||
let bufferOffset: Int64 = 1 * durationMilliseconds
|
||||
let timestampId = latestTimestamp - bufferOffset
|
||||
|
||||
return engine.calls.getVideoBroadcastPart(dataSource: source, callId: info.id, accessHash: info.accessHash, timestampIdMilliseconds: timestampId, durationMilliseconds: durationMilliseconds, channelId: 2, quality: 0)
|
||||
|> mapToSignal { result -> Signal<Data?, NoError> in
|
||||
switch result.status {
|
||||
case let .data(data):
|
||||
return .single(data)
|
||||
case .notReady, .resyncNeeded, .rejoinNeeded:
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] data in
|
||||
guard let self, let data else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
let _ = data
|
||||
})*/
|
||||
})
|
||||
|
||||
let callPeer = call.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: call.peerId))
|
||||
@ -1116,13 +1163,19 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
state.updateDismissOffset(value: offset.y, interactive: true)
|
||||
case let .ended(velocity):
|
||||
if abs(velocity.y) > 200.0 {
|
||||
activatePictureInPicture.invoke(Action { [weak state] in
|
||||
guard let state = state, let controller = controller() as? MediaStreamComponentController else {
|
||||
return
|
||||
if state.isPictureInPictureSupported {
|
||||
activatePictureInPicture.invoke(Action { [weak state] in
|
||||
guard let state = state, let controller = controller() as? MediaStreamComponentController else {
|
||||
return
|
||||
}
|
||||
state.updateDismissOffset(value: velocity.y < 0 ? -height : height, interactive: false)
|
||||
controller.dismiss(closing: false, manual: true)
|
||||
})
|
||||
} else {
|
||||
if let controller = controller() as? MediaStreamComponentController {
|
||||
controller.dismiss(closing: false, manual: true)
|
||||
}
|
||||
state.updateDismissOffset(value: velocity.y < 0 ? -height : height, interactive: false)
|
||||
controller.dismiss(closing: false, manual: true)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
state.updateDismissOffset(value: 0.0, interactive: false)
|
||||
}
|
||||
|
@ -609,7 +609,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
|
||||
|
||||
private var initialCall: EngineGroupCallDescription?
|
||||
private(set) var initialCall: EngineGroupCallDescription?
|
||||
public let internalId: CallSessionInternalId
|
||||
public let peerId: PeerId
|
||||
private let isChannel: Bool
|
||||
|
@ -1091,7 +1091,6 @@ public class Account {
|
||||
self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedConsumePersonalMessagesActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedReadReactionActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedSynchronizeMessageHistoryTagSummaries(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenPersonalMessagesOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedSynchronizeMarkAllUnseenReactionsOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
self.managedOperationsDisposable.add(managedApplyPendingMessageReactionsActions(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start())
|
||||
|
@ -72,8 +72,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
||||
PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId))
|
||||
}))
|
||||
}
|
||||
case let .messageActionSetMessagesTTL(period, _, _):
|
||||
return TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period))
|
||||
case let .messageActionSetMessagesTTL(_, period, autoSettingFrom):
|
||||
return TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: period, autoSettingSource: autoSettingFrom.flatMap { PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value($0)) }))
|
||||
case let .messageActionGroupCallScheduled(call, scheduleDate):
|
||||
switch call {
|
||||
case let .inputGroupCall(id, accessHash):
|
||||
|
@ -4,19 +4,23 @@ import SwiftSignalKit
|
||||
|
||||
func _internal_setSecretChatMessageAutoremoveTimeoutInteractively(account: Account, peerId: PeerId, timeout: Int32?) -> Signal<Void, NoError> {
|
||||
return account.postbox.transaction { transaction -> Void in
|
||||
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat, let state = transaction.getPeerChatState(peerId) as? SecretChatState {
|
||||
if state.messageAutoremoveTimeout != timeout {
|
||||
let updatedPeer = peer.withUpdatedMessageAutoremoveTimeout(timeout)
|
||||
let updatedState = state.withUpdatedMessageAutoremoveTimeout(timeout)
|
||||
if !updatedPeer.isEqual(peer) {
|
||||
updatePeers(transaction: transaction, peers: [updatedPeer], update: { $1 })
|
||||
}
|
||||
if updatedState != state {
|
||||
transaction.setPeerChatState(peerId, state: updatedState)
|
||||
}
|
||||
|
||||
let _ = enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: [(true, .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaAction(action: TelegramMediaActionType.messageAutoremoveTimeoutUpdated(timeout == nil ? 0 : timeout!))), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))])
|
||||
_internal_setSecretChatMessageAutoremoveTimeoutInteractively(transaction: transaction, account: account, peerId: peerId, timeout: timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_setSecretChatMessageAutoremoveTimeoutInteractively(transaction: Transaction, account: Account, peerId: PeerId, timeout: Int32?) {
|
||||
if let peer = transaction.getPeer(peerId) as? TelegramSecretChat, let state = transaction.getPeerChatState(peerId) as? SecretChatState {
|
||||
if state.messageAutoremoveTimeout != timeout {
|
||||
let updatedPeer = peer.withUpdatedMessageAutoremoveTimeout(timeout)
|
||||
let updatedState = state.withUpdatedMessageAutoremoveTimeout(timeout)
|
||||
if !updatedPeer.isEqual(peer) {
|
||||
updatePeers(transaction: transaction, peers: [updatedPeer], update: { $1 })
|
||||
}
|
||||
if updatedState != state {
|
||||
transaction.setPeerChatState(peerId, state: updatedState)
|
||||
}
|
||||
|
||||
let _ = enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: [(true, .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaAction(action: TelegramMediaActionType.messageAutoremoveTimeoutUpdated(period: timeout == nil ? 0 : timeout!, autoSettingSource: nil))), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2211,7 +2211,7 @@ private func resolveMissingPeerChatInfos(network: Network, state: AccountMutable
|
||||
|
||||
for dialog in dialogs {
|
||||
switch dialog {
|
||||
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, notifySettings, pts, _, folderId, _):
|
||||
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, notifySettings, pts, _, folderId, ttlPeriod):
|
||||
let peerId = peer.peerId
|
||||
|
||||
updatedState.setNeedsHoleFromPreviousState(peerId: peerId, namespace: Namespaces.Message.Cloud, validateChannelPts: pts)
|
||||
@ -2251,6 +2251,8 @@ private func resolveMissingPeerChatInfos(network: Network, state: AccountMutable
|
||||
if !isExcludedFromChatList {
|
||||
updatedState.updatePeerChatInclusion(peerId: peerId, groupId: PeerGroupId(rawValue: folderId ?? 0), changedGroup: false)
|
||||
}
|
||||
|
||||
updatedState.updateAutoremoveTimeout(peer: peer, value: ttlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:)))
|
||||
|
||||
let notificationSettings = TelegramPeerNotificationSettings(apiSettings: notifySettings)
|
||||
updatedState.updateNotificationSettings(.peer(peerId: peer.peerId, threadId: nil), notificationSettings: notificationSettings)
|
||||
@ -2452,8 +2454,9 @@ private func resetChannels(accountPeerId: PeerId, postbox: Postbox, network: Net
|
||||
let apiNotificationSettings: Api.PeerNotifySettings
|
||||
let apiMarkedUnread: Bool
|
||||
let groupId: PeerGroupId
|
||||
let apiTtlPeriod: Int32?
|
||||
switch dialog {
|
||||
case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, peerNotificationSettings, pts, _, folderId, _):
|
||||
case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, peerNotificationSettings, pts, _, folderId, ttlPeriod):
|
||||
apiPeer = peer
|
||||
apiTopMessage = topMessage
|
||||
apiReadInboxMaxId = readInboxMaxId
|
||||
@ -2465,6 +2468,7 @@ private func resetChannels(accountPeerId: PeerId, postbox: Postbox, network: Net
|
||||
apiNotificationSettings = peerNotificationSettings
|
||||
apiChannelPts = pts
|
||||
groupId = PeerGroupId(rawValue: folderId ?? 0)
|
||||
apiTtlPeriod = ttlPeriod
|
||||
case .dialogFolder:
|
||||
assertionFailure()
|
||||
continue loop
|
||||
@ -2491,6 +2495,8 @@ private func resetChannels(accountPeerId: PeerId, postbox: Postbox, network: Net
|
||||
|
||||
updatedState.updatePeerChatInclusion(peerId: peerId, groupId: groupId, changedGroup: false)
|
||||
|
||||
updatedState.updateAutoremoveTimeout(peer: apiPeer, value: apiTtlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:)))
|
||||
|
||||
resetForumTopics.insert(peerId)
|
||||
}
|
||||
|
||||
@ -2747,12 +2753,12 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo
|
||||
|
||||
apiTimeout = timeout
|
||||
|
||||
var parameters: (peer: Api.Peer, pts: Int32, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32)?
|
||||
var parameters: (peer: Api.Peer, pts: Int32, topMessage: Int32, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, unreadMentionsCount: Int32, unreadReactionsCount: Int32, ttlPeriod: Int32?)?
|
||||
|
||||
switch dialog {
|
||||
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, _, pts, _, _, _):
|
||||
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, _, pts, _, _, ttlPeriod):
|
||||
if let pts = pts {
|
||||
parameters = (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount)
|
||||
parameters = (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, ttlPeriod)
|
||||
}
|
||||
case .dialogFolder:
|
||||
break
|
||||
@ -2760,10 +2766,12 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo
|
||||
|
||||
var resetForumTopics = Set<PeerId>()
|
||||
|
||||
if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount) = parameters {
|
||||
if let (peer, pts, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, ttlPeriod) = parameters {
|
||||
updatedState.updateChannelState(peer.peerId, pts: pts)
|
||||
updatedState.updateChannelInvalidationPts(peer.peerId, invalidationPts: pts)
|
||||
|
||||
updatedState.updateAutoremoveTimeout(peer: peer, value: ttlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:)))
|
||||
|
||||
updatedState.mergeChats(chats)
|
||||
updatedState.mergeUsers(users)
|
||||
|
||||
|
@ -22,6 +22,7 @@ struct ParsedDialogs {
|
||||
let channelStates: [PeerId: Int32]
|
||||
let topMessageIds: [PeerId: MessageId]
|
||||
let storeMessages: [StoreMessage]
|
||||
let ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout]
|
||||
|
||||
let lowerNonPinnedIndex: MessageIndex?
|
||||
let referencedFolders: [PeerGroupId: PeerGroupUnreadCountersSummary]
|
||||
@ -53,6 +54,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
|
||||
var reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:]
|
||||
var channelStates: [PeerId: Int32] = [:]
|
||||
var topMessageIds: [PeerId: MessageId] = [:]
|
||||
var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] = [:]
|
||||
|
||||
var storeMessages: [StoreMessage] = []
|
||||
var nonPinnedDialogsTopMessageIds = Set<MessageId>()
|
||||
@ -85,7 +87,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
|
||||
var apiChannelPts: Int32?
|
||||
let apiNotificationSettings: Api.PeerNotifySettings
|
||||
switch dialog {
|
||||
case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, peerNotificationSettings, pts, _, _, _):
|
||||
case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, peerNotificationSettings, pts, _, _, ttlPeriod):
|
||||
if let peer = peers[peer.peerId] {
|
||||
var isExluded = false
|
||||
if let group = peer as? TelegramGroup {
|
||||
@ -107,6 +109,9 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
|
||||
apiUnreadReactionsCount = unreadReactionsCount
|
||||
apiNotificationSettings = peerNotificationSettings
|
||||
apiChannelPts = pts
|
||||
|
||||
ttlPeriods[peer.peerId] = .known(ttlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:)))
|
||||
|
||||
let isPinned = (flags & (1 << 2)) != 0
|
||||
if !isPinned {
|
||||
nonPinnedDialogsTopMessageIds.insert(MessageId(peerId: peer.peerId, namespace: Namespaces.Message.Cloud, id: topMessage))
|
||||
@ -180,6 +185,7 @@ private func parseDialogs(apiDialogs: [Api.Dialog], apiMessages: [Api.Message],
|
||||
channelStates: channelStates,
|
||||
topMessageIds: topMessageIds,
|
||||
storeMessages: storeMessages,
|
||||
ttlPeriods: ttlPeriods,
|
||||
|
||||
lowerNonPinnedIndex: lowerNonPinnedIndex,
|
||||
referencedFolders: referencedFolders
|
||||
@ -191,6 +197,7 @@ struct FetchedChatList {
|
||||
var peers: [Peer]
|
||||
var peerPresences: [PeerId: Api.User]
|
||||
var notificationSettings: [PeerId: PeerNotificationSettings]
|
||||
var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout]
|
||||
var readStates: [PeerId: [MessageId.Namespace: PeerReadState]]
|
||||
var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
|
||||
var reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary]
|
||||
@ -298,6 +305,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: Api.User] = [:]
|
||||
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
||||
var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] = [:]
|
||||
var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:]
|
||||
var mentionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:]
|
||||
var reactionTagSummaries: [PeerId: MessageHistoryTagNamespaceSummary] = [:]
|
||||
@ -308,6 +316,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
peers.append(contentsOf: parsedRemoteChats.peers)
|
||||
peerPresences.merge(parsedRemoteChats.peerPresences, uniquingKeysWith: { _, updated in updated })
|
||||
notificationSettings.merge(parsedRemoteChats.notificationSettings, uniquingKeysWith: { _, updated in updated })
|
||||
ttlPeriods.merge(parsedRemoteChats.ttlPeriods, uniquingKeysWith: { _, updated in updated })
|
||||
readStates.merge(parsedRemoteChats.readStates, uniquingKeysWith: { _, updated in updated })
|
||||
mentionTagSummaries.merge(parsedRemoteChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated })
|
||||
reactionTagSummaries.merge(parsedRemoteChats.reactionTagSummaries, uniquingKeysWith: { _, updated in updated })
|
||||
@ -319,6 +328,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
peers.append(contentsOf: parsedPinnedChats.peers)
|
||||
peerPresences.merge(parsedPinnedChats.peerPresences, uniquingKeysWith: { _, updated in updated })
|
||||
notificationSettings.merge(parsedPinnedChats.notificationSettings, uniquingKeysWith: { _, updated in updated })
|
||||
ttlPeriods.merge(parsedPinnedChats.ttlPeriods, uniquingKeysWith: { _, updated in updated })
|
||||
readStates.merge(parsedPinnedChats.readStates, uniquingKeysWith: { _, updated in updated })
|
||||
mentionTagSummaries.merge(parsedPinnedChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated })
|
||||
reactionTagSummaries.merge(parsedPinnedChats.reactionTagSummaries, uniquingKeysWith: { _, updated in updated })
|
||||
@ -342,6 +352,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
peers.append(contentsOf: folderChats.peers)
|
||||
peerPresences.merge(folderChats.peerPresences, uniquingKeysWith: { _, updated in updated })
|
||||
notificationSettings.merge(folderChats.notificationSettings, uniquingKeysWith: { _, updated in updated })
|
||||
ttlPeriods.merge(folderChats.ttlPeriods, uniquingKeysWith: { _, updated in updated })
|
||||
readStates.merge(folderChats.readStates, uniquingKeysWith: { _, updated in updated })
|
||||
mentionTagSummaries.merge(folderChats.mentionTagSummaries, uniquingKeysWith: { _, updated in updated })
|
||||
reactionTagSummaries.merge(folderChats.reactionTagSummaries, uniquingKeysWith: { _, updated in updated })
|
||||
@ -376,6 +387,7 @@ func fetchChatList(postbox: Postbox, network: Network, location: FetchChatListLo
|
||||
peers: peers,
|
||||
peerPresences: peerPresences,
|
||||
notificationSettings: notificationSettings,
|
||||
ttlPeriods: ttlPeriods,
|
||||
readStates: readStates,
|
||||
mentionTagSummaries: mentionTagSummaries,
|
||||
reactionTagSummaries: reactionTagSummaries,
|
||||
|
@ -815,6 +815,20 @@ func fetchChatListHole(postbox: Postbox, network: Network, accountPeerId: PeerId
|
||||
let _ = transaction.addMessages(additionalMessages, location: .Random)
|
||||
transaction.resetIncomingReadStates(fetchedChats.readStates)
|
||||
|
||||
for (peerId, autoremoveValue) in fetchedChats.ttlPeriods {
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedUserData {
|
||||
return current.withUpdatedAutoremoveTimeout(autoremoveValue)
|
||||
} else if let current = current as? CachedGroupData {
|
||||
return current.withUpdatedAutoremoveTimeout(autoremoveValue)
|
||||
} else if let current = current as? CachedChannelData {
|
||||
return current.withUpdatedAutoremoveTimeout(autoremoveValue)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
transaction.replaceChatListHole(groupId: groupId, index: hole.index, hole: fetchedChats.lowerNonPinnedIndex.flatMap(ChatListHole.init))
|
||||
|
||||
for peerId in fetchedChats.chatPeerIds {
|
||||
|
@ -93,7 +93,7 @@ func managedConsumePersonalMessagesActions(postbox: Postbox, network: Network, s
|
||||
let helper = Atomic<ManagedConsumePersonalMessagesActionsHelper>(value: ManagedConsumePersonalMessagesActionsHelper())
|
||||
|
||||
let actionsKey = PostboxViewKey.pendingMessageActions(type: .consumeUnseenPersonalMessage)
|
||||
let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud)
|
||||
let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(peerId: nil, threadId: nil, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud)
|
||||
let disposable = postbox.combinedView(keys: [actionsKey, invalidateKey]).start(next: { view in
|
||||
var entries: [PendingMessageActionsEntry] = []
|
||||
var invalidateEntries = Set<InvalidatedMessageHistoryTagsSummaryEntry>()
|
||||
@ -156,7 +156,7 @@ func managedReadReactionActions(postbox: Postbox, network: Network, stateManager
|
||||
let helper = Atomic<ManagedConsumePersonalMessagesActionsHelper>(value: ManagedConsumePersonalMessagesActionsHelper())
|
||||
|
||||
let actionsKey = PostboxViewKey.pendingMessageActions(type: .readReaction)
|
||||
let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud)
|
||||
let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(peerId: nil, threadId: nil, tagMask: .unseenReaction, namespace: Namespaces.Message.Cloud)
|
||||
let disposable = postbox.combinedView(keys: [actionsKey, invalidateKey]).start(next: { view in
|
||||
var entries: [PendingMessageActionsEntry] = []
|
||||
var invalidateEntries = Set<InvalidatedMessageHistoryTagsSummaryEntry>()
|
||||
@ -435,11 +435,11 @@ private func synchronizeUnseenReactionsTag(postbox: Postbox, network: Network, e
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
func managedSynchronizeMessageHistoryTagSummaries(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
|
||||
func managedSynchronizeMessageHistoryTagSummaries(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, threadId: Int64) -> Signal<Void, NoError> {
|
||||
return Signal { _ in
|
||||
let helper = Atomic<ManagedConsumePersonalMessagesActionsHelper>(value: ManagedConsumePersonalMessagesActionsHelper())
|
||||
|
||||
let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(tagMask: MessageTags(rawValue: 0), namespace: Namespaces.Message.Cloud)
|
||||
let invalidateKey = PostboxViewKey.invalidatedMessageHistoryTagSummaries(peerId: peerId, threadId: threadId, tagMask: MessageTags(rawValue: 0), namespace: Namespaces.Message.Cloud)
|
||||
let disposable = postbox.combinedView(keys: [invalidateKey]).start(next: { view in
|
||||
var invalidateEntries = Set<InvalidatedMessageHistoryTagsSummaryEntry>()
|
||||
if let v = view.views[invalidateKey] as? InvalidatedMessageHistoryTagSummariesView {
|
||||
@ -480,7 +480,7 @@ private func synchronizeMessageHistoryTagSummary(postbox: Postbox, network: Netw
|
||||
guard let threadId = entry.key.threadId else {
|
||||
return .complete()
|
||||
}
|
||||
if let peer = transaction.getPeer(entry.key.peerId), let inputPeer = apiInputPeer(peer) {
|
||||
if let peer = transaction.getPeer(entry.key.peerId) as? TelegramChannel, peer.flags.contains(.isForum), let inputPeer = apiInputPeer(peer) {
|
||||
return network.request(Api.functions.messages.getReplies(peer: inputPeer, msgId: Int32(clamping: threadId), offsetId: 0, offsetDate: 0, addOffset: 0, limit: 1, maxId: 0, minId: 0, hash: 0))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.messages.Messages?, NoError> in
|
||||
|
@ -132,6 +132,7 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox,
|
||||
var readStates: [PeerId: [MessageId.Namespace: PeerReadState]] = [:]
|
||||
var channelStates: [PeerId: Int32] = [:]
|
||||
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
||||
var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] = [:]
|
||||
|
||||
var remoteItemIds: [PinnedItemId] = []
|
||||
|
||||
@ -159,9 +160,10 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox,
|
||||
let apiUnreadCount: Int32
|
||||
let apiMarkedUnread: Bool
|
||||
var apiChannelPts: Int32?
|
||||
let apiTtlPeriod: Int32?
|
||||
let apiNotificationSettings: Api.PeerNotifySettings
|
||||
switch dialog {
|
||||
case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, peerNotificationSettings, pts, _, _, _):
|
||||
case let .dialog(flags, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, peerNotificationSettings, pts, _, _, ttlPeriod):
|
||||
apiPeer = peer
|
||||
apiTopMessage = topMessage
|
||||
apiReadInboxMaxId = readInboxMaxId
|
||||
@ -170,6 +172,7 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox,
|
||||
apiMarkedUnread = (flags & (1 << 3)) != 0
|
||||
apiNotificationSettings = peerNotificationSettings
|
||||
apiChannelPts = pts
|
||||
apiTtlPeriod = ttlPeriod
|
||||
case .dialogFolder:
|
||||
//assertionFailure()
|
||||
continue loop
|
||||
@ -189,6 +192,8 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox,
|
||||
}
|
||||
|
||||
notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings)
|
||||
|
||||
ttlPeriods[peerId] = .known(apiTtlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:)))
|
||||
}
|
||||
|
||||
for message in messages {
|
||||
@ -220,6 +225,20 @@ private func synchronizePinnedChats(transaction: Transaction, postbox: Postbox,
|
||||
|
||||
transaction.updateCurrentPeerNotificationSettings(notificationSettings)
|
||||
|
||||
for (peerId, autoremoveValue) in ttlPeriods {
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedUserData {
|
||||
return current.withUpdatedAutoremoveTimeout(autoremoveValue)
|
||||
} else if let current = current as? CachedGroupData {
|
||||
return current.withUpdatedAutoremoveTimeout(autoremoveValue)
|
||||
} else if let current = current as? CachedChannelData {
|
||||
return current.withUpdatedAutoremoveTimeout(autoremoveValue)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var allPeersWithMessages = Set<PeerId>()
|
||||
for message in storeMessages {
|
||||
if !allPeersWithMessages.contains(message.id.peerId) {
|
||||
|
@ -950,7 +950,7 @@ public final class PendingMessageManager {
|
||||
var sentAsAction = false
|
||||
for media in message.media {
|
||||
if let media = media as? TelegramMediaAction {
|
||||
if case let .messageAutoremoveTimeoutUpdated(value) = media.action {
|
||||
if case let .messageAutoremoveTimeoutUpdated(value, _) = media.action {
|
||||
sentAsAction = true
|
||||
let updatedState = addSecretChatOutgoingOperation(transaction: transaction, peerId: message.id.peerId, operation: .setMessageAutoremoveTimeout(layer: layer, actionGloballyUniqueId: message.globallyUniqueId!, timeout: value, messageId: message.id), state: state)
|
||||
if updatedState != state {
|
||||
|
@ -581,7 +581,7 @@ extension StoreMessage {
|
||||
case .decryptedMessageActionScreenshotMessages:
|
||||
self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)])
|
||||
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
|
||||
self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))])
|
||||
self.init(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: ttlSeconds, autoSettingSource: nil))])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -908,7 +908,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
||||
case .decryptedMessageActionScreenshotMessages:
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), [])
|
||||
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), [])
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: ttlSeconds, autoSettingSource: nil))]), [])
|
||||
case .decryptedMessageActionResend:
|
||||
return nil
|
||||
case .decryptedMessageActionRequestKey:
|
||||
@ -1140,7 +1140,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
||||
case .decryptedMessageActionScreenshotMessages:
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), [])
|
||||
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), [])
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: ttlSeconds, autoSettingSource: nil))]), [])
|
||||
case .decryptedMessageActionResend:
|
||||
return nil
|
||||
case .decryptedMessageActionRequestKey:
|
||||
@ -1419,7 +1419,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
||||
case .decryptedMessageActionScreenshotMessages:
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), [])
|
||||
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), [])
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: ttlSeconds, autoSettingSource: nil))]), [])
|
||||
case .decryptedMessageActionResend:
|
||||
return nil
|
||||
case .decryptedMessageActionRequestKey:
|
||||
@ -1620,7 +1620,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
||||
case .decryptedMessageActionScreenshotMessages:
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), [])
|
||||
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), [])
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(period: ttlSeconds, autoSettingSource: nil))]), [])
|
||||
case .decryptedMessageActionResend:
|
||||
return nil
|
||||
case .decryptedMessageActionRequestKey:
|
||||
|
@ -133,6 +133,22 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
|
||||
return nil
|
||||
}
|
||||
return Message.AssociatedThreadInfo(title: data.info.title, icon: data.info.icon, iconColor: data.info.iconColor)
|
||||
},
|
||||
decodeAutoremoveTimeout: { cachedData in
|
||||
if let cachedData = cachedData as? CachedUserData {
|
||||
if case let .known(value) = cachedData.autoremoveTimeout {
|
||||
return value?.effectiveValue
|
||||
}
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
if case let .known(value) = cachedData.autoremoveTimeout {
|
||||
return value?.effectiveValue
|
||||
}
|
||||
} else if let cachedData = cachedData as? CachedChannelData {
|
||||
if case let .known(value) = cachedData.autoremoveTimeout {
|
||||
return value?.effectiveValue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
}()
|
||||
|
@ -80,7 +80,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
case groupMigratedToChannel(channelId: PeerId)
|
||||
case historyCleared
|
||||
case historyScreenshot
|
||||
case messageAutoremoveTimeoutUpdated(Int32)
|
||||
case messageAutoremoveTimeoutUpdated(period: Int32, autoSettingSource: PeerId?)
|
||||
case gameScore(gameId: Int64, score: Int32)
|
||||
case phoneCall(callId: Int64, discardReason: PhoneCallDiscardReason?, duration: Int32?, isVideo: Bool)
|
||||
case paymentSent(currency: String, totalAmount: Int64, invoiceSlug: String?, isRecurringInit: Bool, isRecurringUsed: Bool)
|
||||
@ -125,7 +125,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
case 11:
|
||||
self = .historyScreenshot
|
||||
case 12:
|
||||
self = .messageAutoremoveTimeoutUpdated(decoder.decodeInt32ForKey("t", orElse: 0))
|
||||
self = .messageAutoremoveTimeoutUpdated(period: decoder.decodeInt32ForKey("t", orElse: 0), autoSettingSource: decoder.decodeOptionalInt64ForKey("src").flatMap(PeerId.init))
|
||||
case 13:
|
||||
self = .gameScore(gameId: decoder.decodeInt64ForKey("i", orElse: 0), score: decoder.decodeInt32ForKey("s", orElse: 0))
|
||||
case 14:
|
||||
@ -218,9 +218,14 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
||||
encoder.encodeInt32(10, forKey: "_rawValue")
|
||||
case .historyScreenshot:
|
||||
encoder.encodeInt32(11, forKey: "_rawValue")
|
||||
case let .messageAutoremoveTimeoutUpdated(timeout):
|
||||
case let .messageAutoremoveTimeoutUpdated(timeout, autoSettingSource):
|
||||
encoder.encodeInt32(12, forKey: "_rawValue")
|
||||
encoder.encodeInt32(timeout, forKey: "t")
|
||||
if let autoSettingSource = autoSettingSource {
|
||||
encoder.encodeInt64(autoSettingSource.toInt64(), forKey: "src")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "src")
|
||||
}
|
||||
case let .gameScore(gameId, score):
|
||||
encoder.encodeInt32(13, forKey: "_rawValue")
|
||||
encoder.encodeInt64(gameId, forKey: "i")
|
||||
|
@ -2532,6 +2532,8 @@ func _internal_getVideoBroadcastPart(dataSource: AudioBroadcastDataSource, callI
|
||||
scale = 0
|
||||
case 500:
|
||||
scale = 1
|
||||
case 32000:
|
||||
scale = -5
|
||||
default:
|
||||
return .single(GetAudioBroadcastPartResult(status: .notReady, responseTimestamp: Double(timestampIdMilliseconds) / 1000.0))
|
||||
}
|
||||
|
@ -126,6 +126,7 @@ public final class EngineChatList: Equatable {
|
||||
public let topForumTopicItems: [EngineChatList.ForumTopicData]
|
||||
public let hasFailed: Bool
|
||||
public let isContact: Bool
|
||||
public let autoremoveTimeout: Int32?
|
||||
|
||||
public init(
|
||||
id: Id,
|
||||
@ -142,7 +143,8 @@ public final class EngineChatList: Equatable {
|
||||
forumTopicData: ForumTopicData?,
|
||||
topForumTopicItems: [EngineChatList.ForumTopicData],
|
||||
hasFailed: Bool,
|
||||
isContact: Bool
|
||||
isContact: Bool,
|
||||
autoremoveTimeout: Int32?
|
||||
) {
|
||||
self.id = id
|
||||
self.index = index
|
||||
@ -159,6 +161,7 @@ public final class EngineChatList: Equatable {
|
||||
self.topForumTopicItems = topForumTopicItems
|
||||
self.hasFailed = hasFailed
|
||||
self.isContact = isContact
|
||||
self.autoremoveTimeout = autoremoveTimeout
|
||||
}
|
||||
|
||||
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||
@ -207,6 +210,9 @@ public final class EngineChatList: Equatable {
|
||||
if lhs.isContact != rhs.isContact {
|
||||
return false
|
||||
}
|
||||
if lhs.autoremoveTimeout != rhs.autoremoveTimeout {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -408,7 +414,7 @@ public extension EngineChatList.RelativePosition {
|
||||
extension EngineChatList.Item {
|
||||
convenience init?(_ entry: ChatListEntry) {
|
||||
switch entry {
|
||||
case let .MessageEntry(index, messages, readState, isRemovedFromTotalUnreadCount, embeddedState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailed, isContact):
|
||||
case let .MessageEntry(index, messages, readState, isRemovedFromTotalUnreadCount, embeddedState, renderedPeer, presence, tagSummaryInfo, forumTopicData, topForumTopics, hasFailed, isContact, autoremoveTimeout):
|
||||
var draft: EngineChatList.Draft?
|
||||
if let embeddedState = embeddedState, let _ = embeddedState.overrideChatTimestamp {
|
||||
if let opaqueState = _internal_decodeStoredChatInterfaceState(state: embeddedState) {
|
||||
@ -470,7 +476,8 @@ extension EngineChatList.Item {
|
||||
forumTopicData: forumTopicDataValue,
|
||||
topForumTopicItems: topForumTopicItems,
|
||||
hasFailed: hasFailed,
|
||||
isContact: isContact
|
||||
isContact: isContact,
|
||||
autoremoveTimeout: autoremoveTimeout
|
||||
)
|
||||
case .HoleEntry:
|
||||
return nil
|
||||
|
@ -590,6 +590,14 @@ public struct ChatReplyThreadMessage: Equatable {
|
||||
self.initialAnchor = initialAnchor
|
||||
self.isNotAvailable = isNotAvailable
|
||||
}
|
||||
|
||||
public var normalized: ChatReplyThreadMessage {
|
||||
if self.isForumPost {
|
||||
return ChatReplyThreadMessage(messageId: self.messageId, channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false)
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum FetchChannelReplyThreadMessageError {
|
||||
|
@ -494,5 +494,10 @@ public extension TelegramEngine {
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func keepMessageCountersSyncrhonized(peerId: EnginePeer.Id, threadId: Int64) -> Signal<Never, NoError> {
|
||||
return managedSynchronizeMessageHistoryTagSummaries(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, threadId: threadId)
|
||||
|> ignoreValues
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -635,6 +635,7 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
|
||||
var peers: [Peer] = []
|
||||
var peerPresences: [PeerId: Api.User] = [:]
|
||||
var notificationSettings: [PeerId: PeerNotificationSettings] = [:]
|
||||
var ttlPeriods: [PeerId: CachedPeerAutoremoveTimeout] = [:]
|
||||
var channelStates: [PeerId: Int32] = [:]
|
||||
|
||||
switch result {
|
||||
@ -654,7 +655,7 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
|
||||
|
||||
for dialog in dialogs {
|
||||
switch dialog {
|
||||
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, notifySettings, pts, _, folderId, _):
|
||||
case let .dialog(_, peer, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, unreadMentionsCount, unreadReactionsCount, notifySettings, pts, _, folderId, ttlPeriod):
|
||||
let peerId = peer.peerId
|
||||
|
||||
if topMessage != 0 {
|
||||
@ -708,6 +709,8 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
|
||||
|
||||
notificationSettings[peer.peerId] = TelegramPeerNotificationSettings(apiSettings: notifySettings)
|
||||
|
||||
ttlPeriods[peer.peerId] = .known(ttlPeriod.flatMap(CachedPeerAutoremoveTimeout.Value.init(peerValue:)))
|
||||
|
||||
transaction.resetIncomingReadStates([peerId: [Namespaces.Message.Cloud: .idBased(maxIncomingReadId: readInboxMaxId, maxOutgoingReadId: readOutboxMaxId, maxKnownId: topMessage, count: unreadCount, markedUnread: false)]])
|
||||
|
||||
transaction.replaceMessageTagSummary(peerId: peerId, threadId: nil, tagMask: .unseenPersonalMessage, namespace: Namespaces.Message.Cloud, count: unreadMentionsCount, maxId: topMessage)
|
||||
@ -754,6 +757,20 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
|
||||
updatePeerPresences(transaction: transaction, accountPeerId: accountPeerId, peerPresences: peerPresences)
|
||||
|
||||
transaction.updateCurrentPeerNotificationSettings(notificationSettings)
|
||||
|
||||
for (peerId, autoremoveValue) in ttlPeriods {
|
||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||
if let current = current as? CachedUserData {
|
||||
return current.withUpdatedAutoremoveTimeout(autoremoveValue)
|
||||
} else if let current = current as? CachedGroupData {
|
||||
return current.withUpdatedAutoremoveTimeout(autoremoveValue)
|
||||
} else if let current = current as? CachedChannelData {
|
||||
return current.withUpdatedAutoremoveTimeout(autoremoveValue)
|
||||
} else {
|
||||
return current
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
@ -154,6 +154,56 @@ public extension TelegramEngine {
|
||||
return _internal_setChatMessageAutoremoveTimeoutInteractively(account: self.account, peerId: peerId, timeout: timeout)
|
||||
}
|
||||
}
|
||||
|
||||
public func setChatMessageAutoremoveTimeouts(peerIds: [EnginePeer.Id], timeout: Int32?) -> Signal<Never, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> Void in
|
||||
for peerId in peerIds {
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
_internal_setSecretChatMessageAutoremoveTimeoutInteractively(transaction: transaction, account: self.account, peerId: peerId, timeout: timeout)
|
||||
} else {
|
||||
var canManage = false
|
||||
guard let peer = transaction.getPeer(peerId) else {
|
||||
continue
|
||||
}
|
||||
if let user = peer as? TelegramUser {
|
||||
if user.botInfo == nil {
|
||||
canManage = true
|
||||
}
|
||||
} else if let _ = peer as? TelegramSecretChat {
|
||||
canManage = true
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
canManage = !group.hasBannedPermission(.banChangeInfo)
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
canManage = channel.hasPermission(.changeInfo)
|
||||
}
|
||||
|
||||
if !canManage {
|
||||
continue
|
||||
}
|
||||
|
||||
let cachedData = transaction.getPeerCachedData(peerId: peerId)
|
||||
var currentValue: Int32?
|
||||
if let cachedData = cachedData as? CachedUserData {
|
||||
if case let .known(value) = cachedData.autoremoveTimeout {
|
||||
currentValue = value?.effectiveValue
|
||||
}
|
||||
} else if let cachedData = cachedData as? CachedGroupData {
|
||||
if case let .known(value) = cachedData.autoremoveTimeout {
|
||||
currentValue = value?.effectiveValue
|
||||
}
|
||||
} else if let cachedData = cachedData as? CachedChannelData {
|
||||
if case let .known(value) = cachedData.autoremoveTimeout {
|
||||
currentValue = value?.effectiveValue
|
||||
}
|
||||
}
|
||||
if currentValue != timeout {
|
||||
let _ = _internal_setChatMessageAutoremoveTimeoutInteractively(account: self.account, peerId: peerId, timeout: timeout).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func updateChannelSlowModeInteractively(peerId: PeerId, timeout: Int32?) -> Signal<Void, UpdateChannelSlowModeError> {
|
||||
return _internal_updateChannelSlowModeInteractively(postbox: self.account.postbox, network: self.account.network, accountStateManager: self.account.stateManager, peerId: peerId, timeout: timeout)
|
||||
|
@ -38,6 +38,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
|
||||
case itemListDownArrow
|
||||
case itemListDisclosureArrow
|
||||
case disclosureOptionArrowsImage
|
||||
case itemListDisclosureLocked
|
||||
case itemListCheckIcon
|
||||
case itemListSecondaryCheckIcon
|
||||
@ -331,4 +332,6 @@ public enum PresentationResourceParameterKey: Hashable {
|
||||
|
||||
case chatEntityKeyboardLock(color: UInt32)
|
||||
case chatInputMediaPanelGridDismissImage(color: UInt32)
|
||||
|
||||
case statusAutoremoveIcon(isActive: Bool)
|
||||
}
|
||||
|
@ -408,4 +408,10 @@ public struct PresentationResourcesChatList {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: theme.chatList.unreadBadgeInactiveBackgroundColor)
|
||||
})
|
||||
}
|
||||
|
||||
public static func statusAutoremoveIcon(_ theme: PresentationTheme, isActive: Bool) -> UIImage? {
|
||||
return theme.image(PresentationResourceParameterKey.statusAutoremoveIcon(isActive: isActive), { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: isActive ? "Chat List/StatusIconAutoremoveOn" : "Chat List/StatusIconAutoremoveOff"), color: isActive ? theme.list.itemAccentColor : theme.list.itemSecondaryTextColor)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,12 @@ public struct PresentationResourcesItemList {
|
||||
})
|
||||
}
|
||||
|
||||
public static func disclosureOptionArrowsImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.disclosureOptionArrowsImage.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Item List/ContextDisclosureArrow"), color: theme.list.disclosureArrowColor)
|
||||
})
|
||||
}
|
||||
|
||||
public static func disclosureLockedImage(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.itemListDisclosureLocked.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: theme.list.disclosureArrowColor)
|
||||
|
@ -9,6 +9,8 @@ public func stringForFullAuthorName(message: EngineMessage, strings: Presentatio
|
||||
var authorName = ""
|
||||
if author.id == accountPeerId {
|
||||
authorName = strings.DialogList_You
|
||||
} else if author.isDeleted {
|
||||
authorName = strings.User_DeletedAccount
|
||||
} else {
|
||||
authorName = author.compactDisplayTitle
|
||||
}
|
||||
|
@ -336,7 +336,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
}
|
||||
case .channelMigratedFromGroup, .groupMigratedToChannel:
|
||||
attributedString = NSAttributedString(string: "", font: titleFont, textColor: primaryTextColor)
|
||||
case let .messageAutoremoveTimeoutUpdated(timeout):
|
||||
case let .messageAutoremoveTimeoutUpdated(timeout, _):
|
||||
let authorString: String
|
||||
if let author = messageMainPeer(message) {
|
||||
authorString = author.compactDisplayTitle
|
||||
|
@ -0,0 +1,5 @@
|
||||
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.5953 11.5986C4.12087 11.4918 2.82405 10.6403 2.17149 8.85384C1.77098 7.7574 2.07201 6.26373 3.0746 4.37282C3.16459 4.20303 3.68939 4.39974 3.71944 4.44691L4.50543 5.68903C4.55963 5.77404 4.79721 5.69973 4.8111 5.67179C5.03783 5.21551 5.22636 4.6273 5.37671 3.90715C5.52201 3.21113 5.59278 2.56825 5.5953 1.81662C5.59594 1.62274 5.95245 1.32054 6 1.34613C7.14662 1.96317 8.0944 2.72803 8.66171 3.48285C9.63301 4.7752 9.88777 5.77431 9.96174 6.98816C10.1296 8.28857 9.7451 9.42338 8.80824 10.3926C7.87138 11.3618 6.8004 11.7638 5.5953 11.5986Z" fill="#8E8E93"/>
|
||||
<path d="M3 11L11 3" stroke="white" stroke-width="1.66" stroke-linecap="round"/>
|
||||
<path d="M2 11L10 3" stroke="#8E8E93" stroke-width="1.33" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 879 B |
12
submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOff.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOff.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AutoremoveIconOff.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.5953 11.0986C4.12087 10.9918 2.82405 10.1403 2.17149 8.35384C1.77098 7.2574 2.07201 5.76373 3.0746 3.87282C3.16459 3.70303 3.68939 3.89974 3.71944 3.94691L4.50543 5.18903C4.55963 5.27404 4.79721 5.19973 4.8111 5.17179C5.03783 4.71551 5.22636 4.1273 5.37671 3.40715C5.52201 2.71113 5.59278 2.06825 5.5953 1.31662C5.59594 1.12274 5.95245 0.820539 6 0.84613C7.14662 1.46317 8.0944 2.22803 8.66171 2.98285C9.63301 4.2752 9.88777 5.27431 9.96174 6.48816C10.1296 7.78857 9.7451 8.92338 8.80824 9.8926C7.87138 10.8618 6.8004 11.2638 5.5953 11.0986Z" fill="#007AFE"/>
|
||||
</svg>
|
After Width: | Height: | Size: 715 B |
12
submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOn.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat List/StatusIconAutoremoveOn.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "AutoremoveIconOn.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
12
submodules/TelegramUI/Images.xcassets/Item List/ContextDisclosureArrow.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Item List/ContextDisclosureArrow.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ContextDisclosureArrow.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
<svg width="12" height="16" viewBox="0 0 12 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 5.5L6 2L10 5.5" stroke="#C4C4C6" stroke-width="1.66" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 10.5L6 14L2 10.5" stroke="#C4C4C6" stroke-width="1.66" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 334 B |
12
submodules/TelegramUI/Images.xcassets/Settings/Menu/Timer.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/Menu/Timer.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "TimerIcon.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
13
submodules/TelegramUI/Images.xcassets/Settings/Menu/Timer.imageset/TimerIcon.svg
vendored
Normal file
13
submodules/TelegramUI/Images.xcassets/Settings/Menu/Timer.imageset/TimerIcon.svg
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_23)">
|
||||
<rect width="30" height="30" rx="7" fill="#AF52DE"/>
|
||||
<path d="M17 13L22.5 7.5" stroke="white" stroke-width="1.66" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15 6.99995V4.49995" stroke="white" stroke-width="1.66" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 24C19.9706 24 24 19.9706 24 15C24 10.0294 19.9706 6 15 6C10.0294 6 6 10.0294 6 15C6 19.9706 10.0294 24 15 24ZM15 8.67C15.4584 8.67 15.83 9.0416 15.83 9.5V13.7504C16.2339 14.0191 16.5 14.4785 16.5 15C16.5 15.8284 15.8284 16.5 15 16.5C14.1716 16.5 13.5 15.8284 13.5 15C13.5 14.4785 13.7661 14.0191 14.17 13.7504V9.5C14.17 9.0416 14.5416 8.67 15 8.67Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_23">
|
||||
<rect width="30" height="30" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 934 B |
BIN
submodules/TelegramUI/Resources/Animations/GlobalAutoRemove.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/GlobalAutoRemove.tgs
Normal file
Binary file not shown.
@ -317,7 +317,7 @@ final class AuthorizedApplicationContext {
|
||||
if let _ = threadData, let threadId = firstMessage.threadId {
|
||||
chatLocation = .replyThread(ChatReplyThreadMessage(
|
||||
messageId: MessageId(peerId: firstMessage.id.peerId, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false
|
||||
))
|
||||
).normalized)
|
||||
} else {
|
||||
guard let peer = firstMessage.peers[firstMessage.id.peerId] else {
|
||||
return
|
||||
|
@ -295,6 +295,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
private var hasActiveGroupCallDisposable: Disposable?
|
||||
private var sendAsPeersDisposable: Disposable?
|
||||
private var preloadAttachBotIconsDisposables: DisposableSet?
|
||||
private var keepMessageCountersSyncrhonizedDisposable: Disposable?
|
||||
|
||||
private let editingMessage = ValuePromise<Float?>(nil, ignoreRepeated: true)
|
||||
private let startingBot = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
@ -785,19 +786,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let _ = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramSecretChat {
|
||||
canSetupAutoremoveTimeout = false
|
||||
} else if let group = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramGroup {
|
||||
if case .creator = group.role {
|
||||
if !group.hasBannedPermission(.banChangeInfo) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
} else if case let .admin(rights, _) = group.role {
|
||||
if rights.rights.contains(.canDeleteMessages) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
}
|
||||
} else if let user = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramUser {
|
||||
if user.id != strongSelf.context.account.peerId && user.botInfo == nil {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
} else if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||
if channel.hasPermission(.deleteAllMessages) {
|
||||
if channel.hasPermission(.changeInfo) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
}
|
||||
@ -5670,6 +5667,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.inviteRequestsDisposable.dispose()
|
||||
self.sendAsPeersDisposable?.dispose()
|
||||
self.preloadAttachBotIconsDisposables?.dispose()
|
||||
self.keepMessageCountersSyncrhonizedDisposable?.dispose()
|
||||
}
|
||||
deallocate()
|
||||
}
|
||||
@ -8543,19 +8541,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
currentAutoremoveTimeout = secretChat.messageAutoremoveTimeout
|
||||
canSetupAutoremoveTimeout = true
|
||||
} else if let group = peer as? TelegramGroup {
|
||||
if case .creator = group.role {
|
||||
if !group.hasBannedPermission(.banChangeInfo) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
} else if case let .admin(rights, _) = group.role {
|
||||
if rights.rights.contains(.canDeleteMessages) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
}
|
||||
} else if let user = peer as? TelegramUser {
|
||||
if user.id != strongSelf.context.account.peerId && user.botInfo == nil {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if channel.hasPermission(.deleteAllMessages) {
|
||||
if channel.hasPermission(.changeInfo) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
}
|
||||
@ -9360,7 +9354,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard case let .peer(peerId) = strongSelf.chatLocation else {
|
||||
guard let peerId = strongSelf.chatLocation.peerId else {
|
||||
return
|
||||
}
|
||||
guard let pinnedMessage = strongSelf.presentationInterfaceState.pinnedMessage else {
|
||||
@ -9404,7 +9398,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})))
|
||||
}
|
||||
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: .peer(id: peerId), subject: .pinnedMessages(id: pinnedMessage.message.id), botStart: nil, mode: .standard(previewing: true))
|
||||
let chatLocation: ChatLocation
|
||||
if let _ = strongSelf.chatLocation.threadId {
|
||||
chatLocation = strongSelf.chatLocation
|
||||
} else {
|
||||
chatLocation = .peer(id: peerId)
|
||||
}
|
||||
|
||||
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: chatLocation, subject: .pinnedMessages(id: pinnedMessage.message.id), botStart: nil, mode: .standard(previewing: true))
|
||||
chatController.canReadHistory.set(false)
|
||||
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
|
||||
@ -10020,6 +10021,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
if case let .replyThread(message) = self.chatLocation, message.isForumPost {
|
||||
if self.keepMessageCountersSyncrhonizedDisposable == nil {
|
||||
self.keepMessageCountersSyncrhonizedDisposable = self.context.engine.messages.keepMessageCountersSyncrhonized(peerId: message.messageId.peerId, threadId: Int64(message.messageId.id)).start()
|
||||
}
|
||||
}
|
||||
|
||||
if let scheduledActivateInput = scheduledActivateInput, case .text = scheduledActivateInput {
|
||||
self.scheduledActivateInput = nil
|
||||
|
||||
@ -14998,16 +15005,27 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
if isPinnedMessages, let messageId = messageLocation.messageId {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
let _ = (combineLatest(
|
||||
self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId)),
|
||||
self.context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .local)
|
||||
)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer, messages in
|
||||
guard let self, let peer = peer else {
|
||||
return
|
||||
}
|
||||
|
||||
if let navigationController = self.effectiveNavigationController {
|
||||
self.dismiss()
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: true, timecode: nil), keepStack: .always))
|
||||
guard let navigationController = self.effectiveNavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
self.dismiss()
|
||||
|
||||
let navigateToLocation: NavigateToChatControllerParams.Location
|
||||
if let threadId = messages.first?.threadId {
|
||||
navigateToLocation = .replyThread(ChatReplyThreadMessage(messageId: MessageId(peerId: peer.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false))
|
||||
} else {
|
||||
navigateToLocation = .peer(peer)
|
||||
}
|
||||
self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: navigateToLocation, subject: .message(id: .id(messageId), highlight: true, timecode: nil), keepStack: .always))
|
||||
})
|
||||
} else if case let .peer(peerId) = self.chatLocation, let messageId = messageLocation.messageId, (messageId.peerId != peerId && !forceInCurrentChat) || (isScheduledMessages && messageId.id != 0 && !Namespaces.Message.allScheduled.contains(messageId.namespace)) {
|
||||
let _ = (self.context.engine.data.get(
|
||||
|
@ -2368,24 +2368,57 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
})
|
||||
|
||||
if let loaded = displayedRange.visibleRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last {
|
||||
if loaded.firstIndex < 5 && historyView.originalView.laterId != nil {
|
||||
if !"".isEmpty {
|
||||
print("load next")
|
||||
return
|
||||
|
||||
var mathesFirst = false
|
||||
if loaded.firstIndex <= 5 {
|
||||
var firstHasGroups = false
|
||||
for index in (max(0, historyView.filteredEntries.count - 5) ..< historyView.filteredEntries.count).reversed() {
|
||||
switch historyView.filteredEntries[index] {
|
||||
case .MessageEntry:
|
||||
break
|
||||
case .MessageGroupEntry:
|
||||
firstHasGroups = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if firstHasGroups {
|
||||
mathesFirst = loaded.firstIndex <= 1
|
||||
} else {
|
||||
mathesFirst = loaded.firstIndex <= 5
|
||||
}
|
||||
}
|
||||
|
||||
var mathesLast = false
|
||||
if loaded.lastIndex >= historyView.filteredEntries.count - 5 {
|
||||
var lastHasGroups = false
|
||||
for index in 0 ..< min(5, historyView.filteredEntries.count) {
|
||||
switch historyView.filteredEntries[index] {
|
||||
case .MessageEntry:
|
||||
break
|
||||
case .MessageGroupEntry:
|
||||
lastHasGroups = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
if lastHasGroups {
|
||||
mathesLast = loaded.lastIndex >= historyView.filteredEntries.count - 1
|
||||
} else {
|
||||
mathesLast = loaded.lastIndex >= historyView.filteredEntries.count - 5
|
||||
}
|
||||
}
|
||||
|
||||
if mathesFirst && historyView.originalView.laterId != nil {
|
||||
let locationInput: ChatHistoryLocation = .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount, highlight: false)
|
||||
if self.chatHistoryLocationValue?.content != locationInput {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId())
|
||||
}
|
||||
} else if loaded.firstIndex < 5, historyView.originalView.laterId == nil, !historyView.originalView.holeLater, let chatHistoryLocationValue = self.chatHistoryLocationValue, !chatHistoryLocationValue.isAtUpperBound, historyView.originalView.anchorIndex != .upperBound {
|
||||
} else if mathesFirst, historyView.originalView.laterId == nil, !historyView.originalView.holeLater, let chatHistoryLocationValue = self.chatHistoryLocationValue, !chatHistoryLocationValue.isAtUpperBound, historyView.originalView.anchorIndex != .upperBound {
|
||||
if self.chatHistoryLocationValue == historyView.locationInput {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount, highlight: false), id: self.takeNextHistoryLocationId())
|
||||
}
|
||||
} else if loaded.lastIndex >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil {
|
||||
if !"".isEmpty {
|
||||
print("load previous")
|
||||
return
|
||||
}
|
||||
} else if mathesLast && historyView.originalView.earlierId != nil {
|
||||
let locationInput: ChatHistoryLocation = .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount, highlight: false)
|
||||
if self.chatHistoryLocationValue?.content != locationInput {
|
||||
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: locationInput, id: self.takeNextHistoryLocationId())
|
||||
|
@ -295,19 +295,15 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
} else if let group = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramGroup {
|
||||
if case .creator = group.role {
|
||||
if !group.hasBannedPermission(.banChangeInfo) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
} else if case let .admin(rights, _) = group.role {
|
||||
if rights.rights.contains(.canDeleteMessages) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
}
|
||||
} else if let user = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser {
|
||||
if user.botInfo == nil {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
} else if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel {
|
||||
if channel.hasPermission(.deleteAllMessages) {
|
||||
if channel.hasPermission(.changeInfo) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,10 @@ func rightNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Ch
|
||||
if let channel = presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isForum), let moreInfoNavigationButton = moreInfoNavigationButton {
|
||||
if case .replyThread = presentationInterfaceState.chatLocation {
|
||||
} else {
|
||||
return moreInfoNavigationButton
|
||||
if case .pinnedMessages = presentationInterfaceState.subject {
|
||||
} else {
|
||||
return moreInfoNavigationButton
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
private let params: ContactMultiselectionControllerParams
|
||||
private let context: AccountContext
|
||||
private let mode: ContactMultiselectionControllerMode
|
||||
private let isPeerEnabled: ((EnginePeer) -> Bool)?
|
||||
|
||||
private let titleView: CounterContollerTitleView
|
||||
|
||||
@ -83,6 +84,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
self.params = params
|
||||
self.context = params.context
|
||||
self.mode = params.mode
|
||||
self.isPeerEnabled = params.isPeerEnabled
|
||||
self.options = params.options
|
||||
self.filters = params.filters
|
||||
self.limit = params.limit
|
||||
@ -127,7 +129,9 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
})
|
||||
|
||||
switch self.mode {
|
||||
case let .chatSelection(_, selectedChats, additionalCategories, _):
|
||||
case let .chatSelection(chatSelection):
|
||||
let selectedChats = chatSelection.selectedChats
|
||||
let additionalCategories = chatSelection.additionalCategories
|
||||
let _ = (self.context.engine.data.get(
|
||||
EngineDataList(
|
||||
selectedChats.map(TelegramEngine.EngineData.Item.Peer.Peer.init)
|
||||
@ -207,8 +211,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed))
|
||||
self.navigationItem.rightBarButtonItem = self.rightNavigationButton
|
||||
rightNavigationButton.isEnabled = false
|
||||
case let .chatSelection(title, _, _, _):
|
||||
self.titleView.title = CounterContollerTitle(title: title, counter: "")
|
||||
case let .chatSelection(chatSelection):
|
||||
self.titleView.title = CounterContollerTitle(title: chatSelection.title, counter: "")
|
||||
let rightNavigationButton = UIBarButtonItem(title: self.presentationData.strings.Common_Done, style: .done, target: self, action: #selector(self.rightNavigationButtonPressed))
|
||||
self.rightNavigationButton = rightNavigationButton
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(cancelPressed))
|
||||
@ -218,7 +222,7 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
}
|
||||
|
||||
override func loadDisplayNode() {
|
||||
self.displayNode = ContactMultiselectionControllerNode(navigationBar: self.navigationBar, context: self.context, presentationData: self.presentationData, mode: self.mode, options: self.options, filters: self.filters, limit: self.limit, reachedSelectionLimit: self.params.reachedLimit)
|
||||
self.displayNode = ContactMultiselectionControllerNode(navigationBar: self.navigationBar, context: self.context, presentationData: self.presentationData, mode: self.mode, isPeerEnabled: self.isPeerEnabled, options: self.options, filters: self.filters, limit: self.limit, reachedSelectionLimit: self.params.reachedLimit)
|
||||
switch self.contactsNode.contentNode {
|
||||
case let .contacts(contactsNode):
|
||||
self._listReady.set(contactsNode.ready)
|
||||
@ -440,8 +444,8 @@ class ContactMultiselectionControllerImpl: ViewController, ContactMultiselection
|
||||
break
|
||||
case let .chats(chatsNode):
|
||||
var categoryToken: EditableTokenListToken?
|
||||
if case let .chatSelection(_, _, additionalCategories, _) = strongSelf.mode {
|
||||
if let additionalCategories = additionalCategories {
|
||||
if case let .chatSelection(chatSelection) = strongSelf.mode {
|
||||
if let additionalCategories = chatSelection.additionalCategories {
|
||||
for i in 0 ..< additionalCategories.categories.count {
|
||||
if additionalCategories.categories[i].id == id {
|
||||
categoryToken = EditableTokenListToken(id: id, title: additionalCategories.categories[i].title, fixedPosition: i)
|
||||
|
@ -71,7 +71,7 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
private let animationCache: AnimationCache
|
||||
private let animationRenderer: MultiAnimationRenderer
|
||||
|
||||
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, options: [ContactListAdditionalOption], filters: [ContactListFilter], limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?) {
|
||||
init(navigationBar: NavigationBar?, context: AccountContext, presentationData: PresentationData, mode: ContactMultiselectionControllerMode, isPeerEnabled: ((EnginePeer) -> Bool)?, options: [ContactListAdditionalOption], filters: [ContactListFilter], limit: Int32?, reachedSelectionLimit: ((Int32) -> Void)?) {
|
||||
self.navigationBar = navigationBar
|
||||
|
||||
self.context = context
|
||||
@ -94,9 +94,14 @@ final class ContactMultiselectionControllerNode: ASDisplayNode {
|
||||
placeholder = self.presentationData.strings.Compose_TokenListPlaceholder
|
||||
}
|
||||
|
||||
if case let .chatSelection(_, selectedChats, additionalCategories, chatListFilters) = mode {
|
||||
placeholder = self.presentationData.strings.ChatListFilter_AddChatsTitle
|
||||
let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters), theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false)
|
||||
if case let .chatSelection(chatSelection) = mode {
|
||||
let placeholderValue = chatSelection.searchPlaceholder
|
||||
let selectedChats = chatSelection.selectedChats
|
||||
let additionalCategories = chatSelection.additionalCategories
|
||||
let chatListFilters = chatSelection.chatListFilters
|
||||
|
||||
placeholder = placeholderValue
|
||||
let chatListNode = ChatListNode(context: context, location: .chatList(groupId: .root), previewing: false, fillPreloadItems: false, mode: .peers(filter: [.excludeSecretChats], isSelecting: true, additionalCategories: additionalCategories?.categories ?? [], chatListFilters: chatListFilters, displayAutoremoveTimeout: chatSelection.displayAutoremoveTimeout), isPeerEnabled: isPeerEnabled, theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false)
|
||||
if let limit = limit {
|
||||
chatListNode.selectionLimit = limit
|
||||
chatListNode.reachedSelectionLimit = reachedSelectionLimit
|
||||
|
@ -26,6 +26,9 @@ import MapResourceToAvatarSizes
|
||||
import ItemListAddressItem
|
||||
import ItemListVenueItem
|
||||
import LegacyMediaPickerUI
|
||||
import ContextUI
|
||||
import ChatTimerScreen
|
||||
import AsyncDisplayKit
|
||||
|
||||
private struct CreateGroupArguments {
|
||||
let context: AccountContext
|
||||
@ -35,10 +38,12 @@ private struct CreateGroupArguments {
|
||||
let changeProfilePhoto: () -> Void
|
||||
let changeLocation: () -> Void
|
||||
let updateWithVenue: (TelegramMediaMap) -> Void
|
||||
let updateAutoDelete: () -> Void
|
||||
}
|
||||
|
||||
private enum CreateGroupSection: Int32 {
|
||||
case info
|
||||
case autoDelete
|
||||
case members
|
||||
case location
|
||||
case venues
|
||||
@ -46,17 +51,11 @@ private enum CreateGroupSection: Int32 {
|
||||
|
||||
private enum CreateGroupEntryTag: ItemListItemTag {
|
||||
case info
|
||||
case autoDelete
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? CreateGroupEntryTag {
|
||||
switch self {
|
||||
case .info:
|
||||
if case .info = other {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return self == other
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -66,6 +65,8 @@ private enum CreateGroupEntryTag: ItemListItemTag {
|
||||
private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
case groupInfo(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, Peer?, ItemListAvatarAndNameInfoItemState, ItemListAvatarAndNameInfoItemUpdatingAvatar?)
|
||||
case setProfilePhoto(PresentationTheme, String)
|
||||
case autoDelete(title: String, value: String)
|
||||
case autoDeleteInfo(String)
|
||||
case member(Int32, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationPersonNameOrder, Peer, PeerPresence?)
|
||||
case locationHeader(PresentationTheme, String)
|
||||
case location(PresentationTheme, PeerGeoLocation)
|
||||
@ -78,6 +79,8 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
switch self {
|
||||
case .groupInfo, .setProfilePhoto:
|
||||
return CreateGroupSection.info.rawValue
|
||||
case .autoDelete, .autoDeleteInfo:
|
||||
return CreateGroupSection.autoDelete.rawValue
|
||||
case .member:
|
||||
return CreateGroupSection.members.rawValue
|
||||
case .locationHeader, .location, .changeLocation, .locationInfo:
|
||||
@ -93,8 +96,12 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
return 0
|
||||
case .setProfilePhoto:
|
||||
return 1
|
||||
case .autoDelete:
|
||||
return 2
|
||||
case .autoDeleteInfo:
|
||||
return 3
|
||||
case let .member(index, _, _, _, _, _, _):
|
||||
return 2 + index
|
||||
return 4 + index
|
||||
case .locationHeader:
|
||||
return 10000
|
||||
case .location:
|
||||
@ -146,6 +153,18 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .autoDelete(title, value):
|
||||
if case .autoDelete(title, value) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .autoDeleteInfo(text):
|
||||
if case .autoDeleteInfo(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .member(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameDisplayOrder, lhsPeer, lhsPresence):
|
||||
if case let .member(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameDisplayOrder, rhsPeer, rhsPresence) = rhs {
|
||||
if lhsIndex != rhsIndex {
|
||||
@ -244,6 +263,12 @@ private enum CreateGroupEntry: ItemListNodeEntry {
|
||||
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
|
||||
arguments.changeProfilePhoto()
|
||||
})
|
||||
case let .autoDelete(text, value):
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: text, label: value, sectionId: self.section, style: .blocks, disclosureStyle: .optionArrows, action: {
|
||||
arguments.updateAutoDelete()
|
||||
}, tag: CreateGroupEntryTag.autoDelete)
|
||||
case let .autoDeleteInfo(text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .member(_, _, _, dateTimeFormat, nameDisplayOrder, peer, presence):
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: EnginePeer(peer), presence: presence.flatMap(EnginePeer.Presence.init), text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
||||
case let .locationHeader(_, title):
|
||||
@ -273,25 +298,7 @@ private struct CreateGroupState: Equatable {
|
||||
var nameSetFromVenue: Bool
|
||||
var avatar: ItemListAvatarAndNameInfoItemUpdatingAvatar?
|
||||
var location: PeerGeoLocation?
|
||||
|
||||
static func ==(lhs: CreateGroupState, rhs: CreateGroupState) -> Bool {
|
||||
if lhs.creating != rhs.creating {
|
||||
return false
|
||||
}
|
||||
if lhs.editingName != rhs.editingName {
|
||||
return false
|
||||
}
|
||||
if lhs.nameSetFromVenue != rhs.nameSetFromVenue {
|
||||
return false
|
||||
}
|
||||
if lhs.avatar != rhs.avatar {
|
||||
return false
|
||||
}
|
||||
if lhs.location != rhs.location {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
var autoremoveTimeout: Int32
|
||||
}
|
||||
|
||||
private func createGroupEntries(presentationData: PresentationData, state: CreateGroupState, peerIds: [PeerId], view: MultiplePeersView, venues: [TelegramMediaMap]?) -> [CreateGroupEntry] {
|
||||
@ -303,6 +310,16 @@ private func createGroupEntries(presentationData: PresentationData, state: Creat
|
||||
|
||||
entries.append(.groupInfo(presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, peer, groupInfoState, state.avatar))
|
||||
|
||||
//TODO:localize
|
||||
let autoRemoveText: String
|
||||
if state.autoremoveTimeout == 0 {
|
||||
autoRemoveText = "Off"
|
||||
} else {
|
||||
autoRemoveText = timeIntervalString(strings: presentationData.strings, value: state.autoremoveTimeout)
|
||||
}
|
||||
entries.append(.autoDelete(title: "Auto-Delete Messages", value: autoRemoveText))
|
||||
entries.append(.autoDeleteInfo("Automatically delete messages sent in this group for everyone after a period of time."))
|
||||
|
||||
var peers: [Peer] = []
|
||||
for peerId in peerIds {
|
||||
if let peer = view.peers[peerId] {
|
||||
@ -365,7 +382,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
location = PeerGeoLocation(latitude: latitude, longitude: longitude, address: address ?? "")
|
||||
}
|
||||
|
||||
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), nameSetFromVenue: false, avatar: nil, location: location)
|
||||
let initialState = CreateGroupState(creating: false, editingName: .title(title: initialTitle ?? "", type: .group), nameSetFromVenue: false, avatar: nil, location: location, autoremoveTimeout: 0)
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((CreateGroupState) -> CreateGroupState) -> Void = { f in
|
||||
@ -375,9 +392,11 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
var replaceControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var presentInGlobalOverlay: ((ViewController) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var endEditingImpl: (() -> Void)?
|
||||
var ensureItemVisibleImpl: ((CreateGroupEntryTag, Bool) -> Void)?
|
||||
var findAutoremoveReferenceNode: (() -> ItemListDisclosureItemNode?)?
|
||||
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
@ -422,29 +441,13 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
}
|
||||
endEditingImpl?()
|
||||
|
||||
let autoremoveTimeout = stateValue.with({ $0 }).autoremoveTimeout
|
||||
let ttlPeriod: Int32? = autoremoveTimeout == 0 ? nil : autoremoveTimeout
|
||||
|
||||
let createSignal: Signal<PeerId?, CreateGroupError>
|
||||
switch mode {
|
||||
case .generic:
|
||||
if title.contains("*forum") {
|
||||
createSignal = context.engine.peers.createSupergroup(title: title, description: nil)
|
||||
|> map(Optional.init)
|
||||
|> mapError { error -> CreateGroupError in
|
||||
switch error {
|
||||
case .generic:
|
||||
return .generic
|
||||
case .restricted:
|
||||
return .restricted
|
||||
case .tooMuchJoined:
|
||||
return .tooMuchJoined
|
||||
case .tooMuchLocationBasedGroups:
|
||||
return .tooMuchLocationBasedGroups
|
||||
case let .serverProvided(error):
|
||||
return .serverProvided(error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: nil)
|
||||
}
|
||||
createSignal = context.engine.peers.createGroup(title: title, peerIds: peerIds, ttlPeriod: ttlPeriod)
|
||||
case .supergroup:
|
||||
createSignal = context.engine.peers.createSupergroup(title: title, description: nil)
|
||||
|> map(Optional.init)
|
||||
@ -821,6 +824,86 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
}
|
||||
})
|
||||
ensureItemVisibleImpl?(.info, true)
|
||||
}, updateAutoDelete: {
|
||||
var subItems: [ContextMenuItem] = []
|
||||
|
||||
let currentValue = stateValue.with({ $0 }).autoremoveTimeout
|
||||
|
||||
let applyValue: (Int32) -> Void = { value in
|
||||
updateState { state in
|
||||
var state = state
|
||||
state.autoremoveTimeout = value
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
//TODO:localize
|
||||
subItems.append(.action(ContextMenuActionItem(text: "Off", icon: { theme in
|
||||
if currentValue == 0 {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, action: { _, f in
|
||||
applyValue(0)
|
||||
f(.default)
|
||||
})))
|
||||
subItems.append(.separator)
|
||||
|
||||
var presetValues: [Int32] = [
|
||||
1 * 24 * 60 * 60,
|
||||
7 * 24 * 60 * 60,
|
||||
31 * 24 * 60 * 60
|
||||
]
|
||||
if currentValue != 0 && !presetValues.contains(currentValue) {
|
||||
presetValues.append(currentValue)
|
||||
presetValues.sort()
|
||||
}
|
||||
|
||||
for value in presetValues {
|
||||
subItems.append(.action(ContextMenuActionItem(text: timeIntervalString(strings: presentationData.strings, value: value), icon: { theme in
|
||||
if currentValue == value {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, action: { _, f in
|
||||
applyValue(value)
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
subItems.append(.action(ContextMenuActionItem(text: "Set Custom Time...", icon: { _ in
|
||||
return nil
|
||||
}, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
let controller = ChatTimerScreen(context: context, updatedPresentationData: nil, style: .default, mode: .autoremove, currentTime: currentValue == 0 ? nil : currentValue, dismissByTapOutside: true, completion: { value in
|
||||
applyValue(value)
|
||||
})
|
||||
endEditingImpl?()
|
||||
presentControllerImpl?(controller, nil)
|
||||
})))
|
||||
|
||||
if let sourceNode = findAutoremoveReferenceNode?() {
|
||||
let items: Signal<ContextController.Items, NoError> = .single(ContextController.Items(content: .list(subItems)))
|
||||
let source: ContextContentSource = .reference(CreateGroupContextReferenceContentSource(sourceView: sourceNode.labelNode.view))
|
||||
|
||||
let contextController = ContextController(
|
||||
account: context.account,
|
||||
presentationData: presentationData,
|
||||
source: source,
|
||||
items: items,
|
||||
gesture: nil
|
||||
)
|
||||
sourceNode.updateHasContextMenu(hasContextMenu: true)
|
||||
contextController.dismissed = { [weak sourceNode] in
|
||||
sourceNode?.updateHasContextMenu(hasContextMenu: false)
|
||||
}
|
||||
presentInGlobalOverlay?(contextController)
|
||||
}
|
||||
})
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), context.account.postbox.multiplePeersView(peerIds), .single(nil) |> then(addressPromise.get()), .single(nil) |> then(venuesPromise.get()))
|
||||
@ -856,6 +939,9 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
presentInGlobalOverlay = { [weak controller] c in
|
||||
controller?.presentInGlobalOverlay(c, with: nil)
|
||||
}
|
||||
pushImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
@ -888,5 +974,41 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
findAutoremoveReferenceNode = { [weak controller] in
|
||||
guard let controller else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let targetTag: CreateGroupEntryTag = .autoDelete
|
||||
var resultItemNode: ItemListItemNode?
|
||||
controller.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode {
|
||||
if let tag = itemNode.tag, tag.isEqual(to: targetTag) {
|
||||
resultItemNode = itemNode
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let resultItemNode = resultItemNode as? ItemListDisclosureItemNode {
|
||||
return resultItemNode
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
||||
|
||||
private final class CreateGroupContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let sourceView: UIView
|
||||
|
||||
init(sourceView: UIView) {
|
||||
self.sourceView = sourceView
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, insets: UIEdgeInsets(top: -4.0, left: 0.0, bottom: -4.0, right: 0.0))
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,11 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
|
||||
var isFirst = true
|
||||
if params.useExisting {
|
||||
for controller in params.navigationController.viewControllers.reversed() {
|
||||
if let controller = controller as? ChatControllerImpl, controller.chatLocation == params.chatLocation.asChatLocation && (controller.subject != .scheduledMessages || controller.subject == params.subject) {
|
||||
guard let controller = controller as? ChatControllerImpl else {
|
||||
isFirst = false
|
||||
continue
|
||||
}
|
||||
if controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && controller.chatLocation.threadId == params.chatLocation.asChatLocation.threadId && (controller.subject != .scheduledMessages || controller.subject == params.subject) {
|
||||
if let updateTextInputState = params.updateTextInputState {
|
||||
controller.updateTextInputState(updateTextInputState)
|
||||
}
|
||||
|
@ -459,47 +459,55 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
case let .settings(section):
|
||||
dismissInput()
|
||||
switch section {
|
||||
case .theme:
|
||||
if let navigationController = navigationController {
|
||||
let controller = themeSettingsController(context: context)
|
||||
case .theme:
|
||||
if let navigationController = navigationController {
|
||||
let controller = themeSettingsController(context: context)
|
||||
controller.navigationPresentation = .modal
|
||||
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is ThemeSettingsController) }
|
||||
controllers.append(controller)
|
||||
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
case .devices:
|
||||
if let navigationController = navigationController {
|
||||
let activeSessions = deferred { () -> Signal<(ActiveSessionsContext, Int, WebSessionsContext), NoError> in
|
||||
let activeSessionsContext = context.engine.privacy.activeSessions()
|
||||
let webSessionsContext = context.engine.privacy.webSessions()
|
||||
let otherSessionCount = activeSessionsContext.state
|
||||
|> map { state -> Int in
|
||||
return state.sessions.filter({ !$0.isCurrent }).count
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
return otherSessionCount
|
||||
|> map { value in
|
||||
return (activeSessionsContext, value, webSessionsContext)
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (activeSessions
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { activeSessionsContext, count, webSessionsContext in
|
||||
let controller = recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false)
|
||||
controller.navigationPresentation = .modal
|
||||
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is ThemeSettingsController) }
|
||||
controllers = controllers.filter { !($0 is RecentSessionsController) }
|
||||
controllers.append(controller)
|
||||
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
case .devices:
|
||||
if let navigationController = navigationController {
|
||||
let activeSessions = deferred { () -> Signal<(ActiveSessionsContext, Int, WebSessionsContext), NoError> in
|
||||
let activeSessionsContext = context.engine.privacy.activeSessions()
|
||||
let webSessionsContext = context.engine.privacy.webSessions()
|
||||
let otherSessionCount = activeSessionsContext.state
|
||||
|> map { state -> Int in
|
||||
return state.sessions.filter({ !$0.isCurrent }).count
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|
||||
return otherSessionCount
|
||||
|> map { value in
|
||||
return (activeSessionsContext, value, webSessionsContext)
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (activeSessions
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { activeSessionsContext, count, webSessionsContext in
|
||||
let controller = recentSessionsController(context: context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false)
|
||||
controller.navigationPresentation = .modal
|
||||
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is RecentSessionsController) }
|
||||
controllers.append(controller)
|
||||
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
case .autoremoveMessages:
|
||||
let _ = (context.engine.privacy.requestAccountPrivacySettings()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { settings in
|
||||
navigationController?.pushViewController(globalAutoremoveScreen(context: context, initialValue: settings.messageAutoremoveTimeout ?? 0, updated: { _ in }), animated: true)
|
||||
})
|
||||
case .twoStepAuth:
|
||||
break
|
||||
}
|
||||
case let .premiumOffer(reference):
|
||||
dismissInput()
|
||||
|
@ -817,12 +817,14 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
||||
if let path = parsedUrl.pathComponents.last {
|
||||
var section: ResolvedUrlSettingsSection?
|
||||
switch path {
|
||||
case "themes":
|
||||
section = .theme
|
||||
case "devices":
|
||||
section = .devices
|
||||
default:
|
||||
break
|
||||
case "themes":
|
||||
section = .theme
|
||||
case "devices":
|
||||
section = .devices
|
||||
case "password":
|
||||
section = .twoStepAuth
|
||||
default:
|
||||
break
|
||||
}
|
||||
if let section = section {
|
||||
handleResolvedUrl(.settings(section))
|
||||
|
@ -2180,7 +2180,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
self.usernameNode.displaysAsynchronously = false
|
||||
|
||||
self.buttonsContainerNode = SparseNode()
|
||||
self.buttonsContainerNode.clipsToBounds = false
|
||||
self.buttonsContainerNode.clipsToBounds = true
|
||||
|
||||
self.regularContentNode = PeerInfoHeaderRegularContentNode()
|
||||
var requestUpdateLayoutImpl: (() -> Void)?
|
||||
@ -2930,10 +2930,6 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
if subtitleIsButton {
|
||||
subtitleFrame.origin.y += 11.0
|
||||
}
|
||||
|
||||
let singleTitleLockOffset: CGFloat = (peer?.id == self.context.account.peerId || subtitleSize.height.isZero) ? 8.0 : 0.0
|
||||
|
||||
let titleLockOffset: CGFloat = 7.0 + singleTitleLockOffset
|
||||
@ -3028,6 +3024,19 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
avatarOffset = apparentTitleLockOffset + 0.0 * (1.0 - titleCollapseFraction) + 10.0 * titleCollapseFraction
|
||||
}
|
||||
|
||||
if subtitleIsButton {
|
||||
subtitleFrame.origin.y += 11.0 * (1.0 - titleCollapseFraction)
|
||||
if let subtitleBackgroundButton = self.subtitleBackgroundButton {
|
||||
transition.updateAlpha(node: subtitleBackgroundButton, alpha: (1.0 - titleCollapseFraction))
|
||||
}
|
||||
if let subtitleBackgroundNode = self.subtitleBackgroundNode {
|
||||
transition.updateAlpha(node: subtitleBackgroundNode, alpha: (1.0 - titleCollapseFraction))
|
||||
}
|
||||
if let subtitleArrowNode = self.subtitleArrowNode {
|
||||
transition.updateAlpha(node: subtitleArrowNode, alpha: (1.0 - titleCollapseFraction))
|
||||
}
|
||||
}
|
||||
|
||||
let avatarCornerRadius: CGFloat
|
||||
if let channel = peer as? TelegramChannel, channel.flags.contains(.isForum) {
|
||||
avatarCornerRadius = floor(avatarSize * 0.25)
|
||||
|
@ -4436,19 +4436,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
currentAutoremoveTimeout = secretChat.messageAutoremoveTimeout
|
||||
canSetupAutoremoveTimeout = false
|
||||
} else if let group = chatPeer as? TelegramGroup {
|
||||
if case .creator = group.role {
|
||||
if !group.hasBannedPermission(.banChangeInfo) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
} else if case let .admin(rights, _) = group.role {
|
||||
if rights.rights.contains(.canDeleteMessages) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
}
|
||||
} else if let user = chatPeer as? TelegramUser {
|
||||
if user.id != strongSelf.context.account.peerId && user.botInfo == nil {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
} else if let channel = chatPeer as? TelegramChannel {
|
||||
if channel.hasPermission(.deleteAllMessages) {
|
||||
if channel.hasPermission(.changeInfo) {
|
||||
canSetupAutoremoveTimeout = true
|
||||
}
|
||||
}
|
||||
@ -4660,8 +4656,20 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
subItems.append(.separator)
|
||||
|
||||
subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo, textLayout: .multiline, textFont: .small, icon: { _ in
|
||||
subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in
|
||||
return nil
|
||||
}, textLinkAction: { [weak c] in
|
||||
c?.dismiss(completion: nil)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controller?.view.endEditing(true)
|
||||
}, contentContext: nil)
|
||||
}, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?)))
|
||||
|
||||
c.pushItems(items: .single(ContextController.Items(content: .list(subItems))))
|
||||
@ -4804,9 +4812,27 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
subItems.append(.separator)
|
||||
|
||||
subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo, textLayout: .multiline, textFont: .small, icon: { _ in
|
||||
return nil
|
||||
}, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?)))
|
||||
if case .group = channel.info {
|
||||
subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in
|
||||
return nil
|
||||
}, textLinkAction: { [weak c] in
|
||||
c?.dismiss(completion: nil)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controller?.view.endEditing(true)
|
||||
}, contentContext: nil)
|
||||
}, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?)))
|
||||
} else {
|
||||
subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo, textLayout: .multiline, textFont: .small, icon: { _ in
|
||||
return nil
|
||||
}, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?)))
|
||||
}
|
||||
|
||||
c.pushItems(items: .single(ContextController.Items(content: .list(subItems))))
|
||||
})))
|
||||
@ -4920,8 +4946,20 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
|
||||
subItems.append(.separator)
|
||||
|
||||
subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo, textLayout: .multiline, textFont: .small, icon: { _ in
|
||||
subItems.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PeerInfo_AutoDeleteInfo + "\n\n" + strongSelf.presentationData.strings.AutoremoveSetup_AdditionalGlobalSettingsInfo, textLayout: .multiline, textFont: .small, parseMarkdown: true, icon: { _ in
|
||||
return nil
|
||||
}, textLinkAction: { [weak c] in
|
||||
c?.dismiss(completion: nil)
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.openResolvedUrl(.settings(.autoremoveMessages), context: self.context, urlContext: .generic, navigationController: self.controller?.navigationController as? NavigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { _, _ in }, dismissInput: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controller?.view.endEditing(true)
|
||||
}, contentContext: nil)
|
||||
}, action: nil as ((ContextControllerProtocol, @escaping (ContextMenuActionResult) -> Void) -> Void)?)))
|
||||
|
||||
c.pushItems(items: .single(ContextController.Items(content: .list(subItems))))
|
||||
@ -6986,11 +7024,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
||||
threadId = Int64(message.messageId.id)
|
||||
}
|
||||
|
||||
var temporary = false
|
||||
if self.isSettings && self.data?.globalSettings?.privacySettings?.phoneDiscoveryEnabled == false {
|
||||
temporary = true
|
||||
}
|
||||
controller.present(ChatQrCodeScreen(context: self.context, subject: .peer(peer: peer, threadId: threadId, temporary: temporary)), in: .window(.root))
|
||||
// var temporary = false
|
||||
// if self.isSettings && self.data?.globalSettings?.privacySettings?.phoneDiscoveryEnabled == false {
|
||||
// temporary = true
|
||||
// }
|
||||
controller.present(ChatQrCodeScreen(context: self.context, subject: .peer(peer: peer, threadId: threadId, temporary: false)), in: .window(.root))
|
||||
}
|
||||
|
||||
fileprivate func openSettings(section: PeerInfoSettingsSection) {
|
||||
|
@ -137,7 +137,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
chatListLocation = .chatList(groupId: .root)
|
||||
}
|
||||
|
||||
self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false)
|
||||
self.chatListNode = ChatListNode(context: context, location: chatListLocation, previewing: false, fillPreloadItems: false, mode: .peers(filter: filter, isSelecting: false, additionalCategories: chatListCategories, chatListFilters: nil, displayAutoremoveTimeout: false), theme: self.presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, animationCache: self.animationCache, animationRenderer: self.animationRenderer, disableAnimations: true, isInlineMode: false)
|
||||
|
||||
super.init()
|
||||
|
||||
|
@ -751,6 +751,8 @@ public final class OngoingCallContext {
|
||||
|
||||
private var signalingConnectionManager: QueueLocalObject<CallSignalingConnectionManager>?
|
||||
|
||||
private let audioDevice: SharedCallAudioDevice?
|
||||
|
||||
public static func versions(includeExperimental: Bool, includeReference: Bool) -> [(version: String, supportsVideo: Bool)] {
|
||||
#if os(iOS) && DEBUG && false
|
||||
if "".isEmpty {
|
||||
@ -780,6 +782,14 @@ public final class OngoingCallContext {
|
||||
self.logPath = logName.isEmpty ? "" : callLogsPath(account: self.account) + "/" + logName + ".log"
|
||||
let logPath = self.logPath
|
||||
|
||||
let audioDevice: SharedCallAudioDevice?
|
||||
if !"".isEmpty {
|
||||
audioDevice = SharedCallAudioDevice()
|
||||
} else {
|
||||
audioDevice = nil
|
||||
}
|
||||
self.audioDevice = audioDevice
|
||||
|
||||
let _ = try? FileManager.default.createDirectory(atPath: callLogsPath(account: account), withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
self.tempStatsLogFile = EngineTempBox.shared.tempFile(fileName: "CallStats.json")
|
||||
@ -900,7 +910,7 @@ public final class OngoingCallContext {
|
||||
callSessionManager.sendSignalingData(internalId: internalId, data: data)
|
||||
}
|
||||
}
|
||||
}, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "", useManualAudioSessionControl: true)
|
||||
}, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "", audioDevice: audioDevice)
|
||||
|
||||
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
|
||||
context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState, remoteBatteryLevel, _ in
|
||||
|
@ -11,6 +11,16 @@
|
||||
#define UIView NSView
|
||||
#endif
|
||||
|
||||
@interface SharedCallAudioDevice : NSObject
|
||||
|
||||
- (instancetype _Nonnull)init;
|
||||
|
||||
+ (void)setupAudioSession;
|
||||
|
||||
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive;
|
||||
|
||||
@end
|
||||
|
||||
@interface OngoingCallConnectionDescriptionWebrtc : NSObject
|
||||
|
||||
@property (nonatomic, readonly) uint8_t reflectorId;
|
||||
@ -223,7 +233,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
|
||||
sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer
|
||||
preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec
|
||||
audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId
|
||||
useManualAudioSessionControl:(bool)useManualAudioSessionControl;
|
||||
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice;
|
||||
|
||||
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive;
|
||||
|
||||
|
@ -43,6 +43,95 @@
|
||||
#import "platform/darwin/TGRTCCVPixelBuffer.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace tgcalls {
|
||||
|
||||
class SharedAudioDeviceModule {
|
||||
public:
|
||||
virtual ~SharedAudioDeviceModule() = default;
|
||||
|
||||
public:
|
||||
virtual rtc::scoped_refptr<webrtc::AudioDeviceModule> audioDeviceModule() = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class SharedAudioDeviceModuleImpl: public tgcalls::SharedAudioDeviceModule {
|
||||
public:
|
||||
SharedAudioDeviceModuleImpl() {
|
||||
if (tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent()) {
|
||||
_audioDeviceModule = rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, false, 1);
|
||||
} else {
|
||||
tgcalls::StaticThreads::getThreads()->getWorkerThread()->BlockingCall([&]() {
|
||||
_audioDeviceModule = rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, false, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
virtual ~SharedAudioDeviceModuleImpl() override {
|
||||
if (tgcalls::StaticThreads::getThreads()->getWorkerThread()->IsCurrent()) {
|
||||
_audioDeviceModule = nullptr;
|
||||
} else {
|
||||
tgcalls::StaticThreads::getThreads()->getWorkerThread()->BlockingCall([&]() {
|
||||
_audioDeviceModule = nullptr;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
virtual rtc::scoped_refptr<webrtc::AudioDeviceModule> audioDeviceModule() override {
|
||||
return _audioDeviceModule;
|
||||
}
|
||||
|
||||
private:
|
||||
rtc::scoped_refptr<webrtc::AudioDeviceModule> _audioDeviceModule;
|
||||
};
|
||||
|
||||
@implementation SharedCallAudioDevice {
|
||||
std::shared_ptr<tgcalls::ThreadLocalObject<tgcalls::SharedAudioDeviceModule>> _audioDeviceModule;
|
||||
}
|
||||
|
||||
- (instancetype _Nonnull)init {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_audioDeviceModule.reset(new tgcalls::ThreadLocalObject<tgcalls::SharedAudioDeviceModule>(tgcalls::StaticThreads::getThreads()->getWorkerThread(), []() mutable {
|
||||
return (tgcalls::SharedAudioDeviceModule *)(new SharedAudioDeviceModuleImpl());
|
||||
}));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
_audioDeviceModule.reset();
|
||||
}
|
||||
|
||||
- (std::shared_ptr<tgcalls::ThreadLocalObject<tgcalls::SharedAudioDeviceModule>>)getAudioDeviceModule {
|
||||
return _audioDeviceModule;
|
||||
}
|
||||
|
||||
+ (void)setupAudioSession {
|
||||
RTCAudioSessionConfiguration *sharedConfiguration = [RTCAudioSessionConfiguration webRTCConfiguration];
|
||||
sharedConfiguration.mode = AVAudioSessionModeVoiceChat;
|
||||
sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionMixWithOthers;
|
||||
sharedConfiguration.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetoothA2DP;
|
||||
sharedConfiguration.outputNumberOfChannels = 1;
|
||||
[RTCAudioSessionConfiguration setWebRTCConfiguration:sharedConfiguration];
|
||||
|
||||
[[RTCAudioSession sharedInstance] lockForConfiguration];
|
||||
[[RTCAudioSession sharedInstance] setConfiguration:sharedConfiguration active:false error:nil disableRecording:false];
|
||||
[[RTCAudioSession sharedInstance] unlockForConfiguration];
|
||||
}
|
||||
|
||||
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive {
|
||||
if (isAudioSessionActive) {
|
||||
[[RTCAudioSession sharedInstance] audioSessionDidActivate:[AVAudioSession sharedInstance]];
|
||||
} else {
|
||||
[[RTCAudioSession sharedInstance] audioSessionDidDeactivate:[AVAudioSession sharedInstance]];
|
||||
}
|
||||
[RTCAudioSession sharedInstance].isAudioEnabled = isAudioSessionActive;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation OngoingCallConnectionDescriptionWebrtc
|
||||
|
||||
- (instancetype _Nonnull)initWithReflectorId:(uint8_t)reflectorId hasStun:(bool)hasStun hasTurn:(bool)hasTurn hasTcp:(bool)hasTcp ip:(NSString * _Nonnull)ip port:(int32_t)port username:(NSString * _Nonnull)username password:(NSString * _Nonnull)password {
|
||||
@ -709,6 +798,7 @@ tgcalls::VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(tgcalls:
|
||||
int32_t _contextId;
|
||||
|
||||
bool _useManualAudioSessionControl;
|
||||
SharedCallAudioDevice *_audioDevice;
|
||||
|
||||
OngoingCallNetworkTypeWebrtc _networkType;
|
||||
NSTimeInterval _callReceiveTimeout;
|
||||
@ -876,7 +966,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer
|
||||
preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec
|
||||
audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId
|
||||
useManualAudioSessionControl:(bool)useManualAudioSessionControl {
|
||||
audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_version = version;
|
||||
@ -885,7 +975,9 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
|
||||
assert([[OngoingCallThreadLocalContextWebrtc versionsWithIncludeReference:true] containsObject:version]);
|
||||
|
||||
_useManualAudioSessionControl = useManualAudioSessionControl;
|
||||
_audioDevice = audioDevice;
|
||||
|
||||
_useManualAudioSessionControl = true;
|
||||
[RTCAudioSession sharedInstance].useManualAudio = true;
|
||||
|
||||
#ifdef WEBRTC_IOS
|
||||
@ -992,6 +1084,11 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
|
||||
[OngoingCallThreadLocalContextWebrtc ensureRegisteredImplementations];
|
||||
|
||||
std::shared_ptr<tgcalls::ThreadLocalObject<tgcalls::SharedAudioDeviceModule>> audioDeviceModule;
|
||||
if (_audioDevice) {
|
||||
audioDeviceModule = [_audioDevice getAudioDeviceModule];
|
||||
}
|
||||
|
||||
__weak OngoingCallThreadLocalContextWebrtc *weakSelf = self;
|
||||
_tgVoip = tgcalls::Meta::Create([version UTF8String], (tgcalls::Descriptor){
|
||||
.version = [version UTF8String],
|
||||
@ -1116,8 +1213,12 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
|
||||
}
|
||||
}];
|
||||
},
|
||||
.createAudioDeviceModule = [](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<webrtc::AudioDeviceModule> {
|
||||
return rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, false, 1);
|
||||
.createAudioDeviceModule = [audioDeviceModule](webrtc::TaskQueueFactory *taskQueueFactory) -> rtc::scoped_refptr<webrtc::AudioDeviceModule> {
|
||||
if (audioDeviceModule) {
|
||||
return audioDeviceModule->getSyncAssumingSameThread()->audioDeviceModule();
|
||||
} else {
|
||||
return rtc::make_ref_counted<webrtc::tgcalls_ios_adm::AudioDeviceModuleIOS>(false, false, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
_state = OngoingCallStateInitializing;
|
||||
|
@ -678,12 +678,27 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(messageId.id))
|
||||
|> map { info -> ResolvedUrl? in
|
||||
if let _ = info {
|
||||
return .replyThread(messageId: messageId)
|
||||
return context.engine.messages.getMessagesLoadIfNecessary([messageId], strategy: .cloud(skipLocal: false))
|
||||
|> take(1)
|
||||
|> mapToSignal { messages -> Signal<ResolvedUrl?, NoError> in
|
||||
if let threadId = messages.first?.threadId {
|
||||
return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: threadId)
|
||||
|> map { info -> ResolvedUrl? in
|
||||
if let _ = info {
|
||||
return .replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage(messageId: MessageId(peerId: channel.id, namespace: Namespaces.Message.Cloud, id: Int32(clamping: threadId)), channelMessageId: nil, isChannelPost: false, isForumPost: true, maxMessage: nil, maxReadIncomingMessageId: nil, maxReadOutgoingMessageId: nil, unreadCount: 0, initialFilledHoles: IndexSet(), initialAnchor: .automatic, isNotAvailable: false), messageId: messageId)
|
||||
} else {
|
||||
return .peer(peer?._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))
|
||||
return context.engine.peers.fetchForumChannelTopic(id: channel.id, threadId: Int64(messageId.id))
|
||||
|> map { info -> ResolvedUrl? in
|
||||
if let _ = info {
|
||||
return .replyThread(messageId: messageId)
|
||||
} else {
|
||||
return .peer(foundPeer._asPeer(), .chat(textInputState: nil, subject: nil, peekData: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -325,7 +325,7 @@ func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: P
|
||||
}
|
||||
|
||||
func makeBridgeChat(_ entry: ChatListEntry, strings: PresentationStrings) -> (TGBridgeChat, [Int64 : TGBridgeUser])? {
|
||||
if case let .MessageEntry(index, messages, readState, _, _, renderedPeer, _, _, _, _, hasFailed, _) = entry {
|
||||
if case let .MessageEntry(index, messages, readState, _, _, renderedPeer, _, _, _, _, hasFailed, _, _) = entry {
|
||||
guard index.messageIndex.id.peerId.namespace != Namespaces.Peer.SecretChat else {
|
||||
return nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user