mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Folder updates
This commit is contained in:
parent
8ca477e686
commit
4ccd0bd804
@ -90,6 +90,7 @@ swift_library(
|
|||||||
"//submodules/AvatarVideoNode:AvatarVideoNode",
|
"//submodules/AvatarVideoNode:AvatarVideoNode",
|
||||||
"//submodules/InviteLinksUI",
|
"//submodules/InviteLinksUI",
|
||||||
"//submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen",
|
"//submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen",
|
||||||
|
"//submodules/ItemListUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -1547,8 +1547,8 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
for filter in filters {
|
for filter in filters {
|
||||||
if filter.id == filterId, case let .filter(_, title, _, data) = filter {
|
if filter.id == filterId, case let .filter(_, title, _, data) = filter {
|
||||||
if !data.includePeers.peers.isEmpty {
|
if !data.includePeers.peers.isEmpty {
|
||||||
items.append(.action(ContextMenuActionItem(text: "Share", textColor: .primary, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: "Share", textColor: .primary, badge: ContextMenuActionBadge(value: "NEW", color: .accent, style: .label), icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
|
||||||
}, action: { c, f in
|
}, action: { c, f in
|
||||||
c.dismiss(completion: {
|
c.dismiss(completion: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
@ -2701,6 +2701,13 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func shareFolder(filterId: Int32, data: ChatListFilterData, title: String) {
|
private func shareFolder(filterId: Int32, data: ChatListFilterData, title: String) {
|
||||||
|
openCreateChatListFolderLink(context: self.context, folderId: filterId, title: title, peerIds: data.includePeers.peers, pushController: { [weak self] c in
|
||||||
|
self?.push(c)
|
||||||
|
}, presentController: { [weak self] c in
|
||||||
|
self?.present(c, in: .window(.root))
|
||||||
|
}, linkUpdated: { _ in
|
||||||
|
})
|
||||||
|
|
||||||
/*self.push(folderInviteLinkListController(context: self.context, filterId: filterId, title: title, allPeerIds: data.includePeers.peers, currentInvitation: nil, linkUpdated: { _ in
|
/*self.push(folderInviteLinkListController(context: self.context, filterId: filterId, title: title, allPeerIds: data.includePeers.peers, currentInvitation: nil, linkUpdated: { _ in
|
||||||
}))*/
|
}))*/
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
|
|||||||
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
||||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
|
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {}, hideChatFolderUpdates: {})
|
||||||
interaction.isInlineMode = isInlineMode
|
interaction.isInlineMode = isInlineMode
|
||||||
|
|
||||||
let items = (0 ..< 2).map { _ -> ChatListItem in
|
let items = (0 ..< 2).map { _ -> ChatListItem in
|
||||||
|
@ -519,7 +519,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
|||||||
case .inviteLinkHeader:
|
case .inviteLinkHeader:
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: "INVITE LINK", badge: "NEW", sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: "INVITE LINK", badge: "NEW", sectionId: self.section)
|
||||||
case let.inviteLinkCreate(hasLinks):
|
case let .inviteLinkCreate(hasLinks):
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? "Create a New Link" : "Share Folder", sectionId: self.section, editing: false, action: {
|
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? "Create a New Link" : "Share Folder", sectionId: self.section, editing: false, action: {
|
||||||
arguments.createLink()
|
arguments.createLink()
|
||||||
@ -654,14 +654,21 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
|
|||||||
entries.append(.excludePeerInfo(presentationData.strings.ChatListFolder_ExcludeSectionInfo))
|
entries.append(.excludePeerInfo(presentationData.strings.ChatListFolder_ExcludeSectionInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isNewFilter, let inviteLinks {
|
if !isNewFilter {
|
||||||
entries.append(.inviteLinkHeader)
|
entries.append(.inviteLinkHeader)
|
||||||
entries.append(.inviteLinkCreate(hasLinks: !inviteLinks.isEmpty))
|
|
||||||
|
|
||||||
var index = 0
|
var hasLinks = false
|
||||||
for link in inviteLinks {
|
if let inviteLinks, !inviteLinks.isEmpty {
|
||||||
entries.append(.inviteLink(index, link))
|
hasLinks = true
|
||||||
index += 1
|
}
|
||||||
|
entries.append(.inviteLinkCreate(hasLinks: hasLinks))
|
||||||
|
|
||||||
|
if let inviteLinks {
|
||||||
|
var index = 0
|
||||||
|
for link in inviteLinks {
|
||||||
|
entries.append(.inviteLink(index, link))
|
||||||
|
index += 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.inviteLinkInfo)
|
entries.append(.inviteLinkInfo)
|
||||||
@ -1065,6 +1072,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
var focusOnNameImpl: (() -> Void)?
|
var focusOnNameImpl: (() -> Void)?
|
||||||
|
var applyImpl: ((@escaping () -> Void) -> Void)?
|
||||||
|
|
||||||
let sharedLinks = Promise<[ExportedChatFolderLink]?>(nil)
|
let sharedLinks = Promise<[ExportedChatFolderLink]?>(nil)
|
||||||
if let currentPreset {
|
if let currentPreset {
|
||||||
@ -1273,114 +1281,77 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
createLink: {
|
createLink: {
|
||||||
let state = stateValue.with({ $0 })
|
applyImpl?({
|
||||||
|
|
||||||
if let currentPreset, !state.additionallyIncludePeers.isEmpty {
|
|
||||||
let _ = (context.engine.data.get(
|
|
||||||
EngineDataList(state.additionallyIncludePeers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
|
||||||
)
|
|
||||||
|> deliverOnMainQueue).start(next: { peers in
|
|
||||||
let peers = peers.compactMap({ $0 })
|
|
||||||
if peers.allSatisfy({ !canShareLinkToPeer(peer: $0) }) {
|
|
||||||
pushControllerImpl?(folderInviteLinkListController(context: context, filterId: currentPreset.id, title: currentPreset.title, allPeerIds: state.additionallyIncludePeers, currentInvitation: nil, linkUpdated: { updatedLink in
|
|
||||||
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
|
||||||
guard var links else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let updatedLink {
|
|
||||||
links.insert(updatedLink, at: 0)
|
|
||||||
sharedLinks.set(.single(links))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
let _ = (context.engine.peers.exportChatFolder(filterId: currentPreset.id, title: "", peerIds: state.additionallyIncludePeers)
|
|
||||||
|> deliverOnMainQueue).start(next: { link in
|
|
||||||
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
|
||||||
guard var links else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
links.insert(link, at: 0)
|
|
||||||
sharedLinks.set(.single(links))
|
|
||||||
})
|
|
||||||
|
|
||||||
pushControllerImpl?(folderInviteLinkListController(context: context, filterId: currentPreset.id, title: currentPreset.title, allPeerIds: state.additionallyIncludePeers, currentInvitation: link, linkUpdated: { updatedLink in
|
|
||||||
if updatedLink != link {
|
|
||||||
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
|
||||||
guard var links else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let updatedLink {
|
|
||||||
if let index = links.firstIndex(where: { $0 == link }) {
|
|
||||||
links.remove(at: index)
|
|
||||||
}
|
|
||||||
links.insert(updatedLink, at: 0)
|
|
||||||
sharedLinks.set(.single(links))
|
|
||||||
} else {
|
|
||||||
if let index = links.firstIndex(where: { $0 == link }) {
|
|
||||||
links.remove(at: index)
|
|
||||||
sharedLinks.set(.single(links))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}, error: { error in
|
|
||||||
//TODO:localize
|
|
||||||
let text: String
|
|
||||||
switch error {
|
|
||||||
case .generic:
|
|
||||||
text = "An error occurred"
|
|
||||||
case let .limitExceeded(limit, premiumLimit):
|
|
||||||
if limit < premiumLimit {
|
|
||||||
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .linksPerSharedFolder, count: limit, action: {
|
|
||||||
})
|
|
||||||
pushControllerImpl?(limitController)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
text = "You can't create more links."
|
|
||||||
}
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, openLink: { link in
|
|
||||||
if let currentPreset, let _ = currentPreset.data {
|
|
||||||
let state = stateValue.with({ $0 })
|
let state = stateValue.with({ $0 })
|
||||||
pushControllerImpl?(folderInviteLinkListController(context: context, filterId: currentPreset.id, title: currentPreset.title, allPeerIds: state.additionallyIncludePeers, currentInvitation: link, linkUpdated: { updatedLink in
|
|
||||||
if updatedLink != link {
|
if let currentPreset, let data = currentPreset.data {
|
||||||
|
//TODO:localize
|
||||||
|
var unavailableText: String?
|
||||||
|
if !data.categories.isEmpty || data.excludeArchived || data.excludeRead || data.excludeMuted || !data.excludePeers.isEmpty {
|
||||||
|
unavailableText = "You can't share a link to this folder."
|
||||||
|
}
|
||||||
|
if let unavailableText {
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: unavailableText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
openCreateChatListFolderLink(context: context, folderId: currentPreset.id, title: currentPreset.title, peerIds: state.additionallyIncludePeers, pushController: { c in
|
||||||
|
pushControllerImpl?(c)
|
||||||
|
}, presentController: { c in
|
||||||
|
presentControllerImpl?(c, nil)
|
||||||
|
}, linkUpdated: { updatedLink in
|
||||||
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
||||||
guard var links else {
|
guard var links else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let updatedLink {
|
if let updatedLink {
|
||||||
if let index = links.firstIndex(where: { $0 == link }) {
|
if let index = links.firstIndex(where: { $0.link == updatedLink.link }) {
|
||||||
links.remove(at: index)
|
links[index] = updatedLink
|
||||||
|
} else {
|
||||||
|
links.insert(updatedLink, at: 0)
|
||||||
}
|
}
|
||||||
links.insert(updatedLink, at: 0)
|
|
||||||
sharedLinks.set(.single(links))
|
sharedLinks.set(.single(links))
|
||||||
} else {
|
|
||||||
if let index = links.firstIndex(where: { $0 == link }) {
|
|
||||||
links.remove(at: index)
|
|
||||||
sharedLinks.set(.single(links))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
}))
|
}
|
||||||
|
})
|
||||||
|
}, openLink: { link in
|
||||||
|
if let currentPreset, let _ = currentPreset.data {
|
||||||
|
applyImpl?({
|
||||||
|
let state = stateValue.with({ $0 })
|
||||||
|
pushControllerImpl?(folderInviteLinkListController(context: context, filterId: currentPreset.id, title: currentPreset.title, allPeerIds: state.additionallyIncludePeers, currentInvitation: link, linkUpdated: { updatedLink in
|
||||||
|
if updatedLink != link {
|
||||||
|
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
||||||
|
guard var links else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let updatedLink {
|
||||||
|
if let index = links.firstIndex(where: { $0 == link }) {
|
||||||
|
links.remove(at: index)
|
||||||
|
}
|
||||||
|
links.insert(updatedLink, at: 0)
|
||||||
|
sharedLinks.set(.single(links))
|
||||||
|
} else {
|
||||||
|
if let index = links.firstIndex(where: { $0 == link }) {
|
||||||
|
links.remove(at: index)
|
||||||
|
sharedLinks.set(.single(links))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
var attemptNavigationImpl: (() -> Bool)?
|
var attemptNavigationImpl: (() -> Bool)?
|
||||||
let applyImpl: (() -> Void)? = {
|
applyImpl = { completed in
|
||||||
let state = stateValue.with { $0 }
|
let state = stateValue.with { $0 }
|
||||||
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
|
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
|
||||||
var includePeers = ChatListFilterIncludePeers()
|
var includePeers = ChatListFilterIncludePeers()
|
||||||
@ -1422,7 +1393,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { filters in
|
|> deliverOnMainQueue).start(next: { filters in
|
||||||
updated(filters)
|
updated(filters)
|
||||||
dismissImpl?()
|
completed()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1449,7 +1420,9 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
let rightNavigationButton = ItemListNavigationButton(content: .text(currentPreset == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: {
|
let rightNavigationButton = ItemListNavigationButton(content: .text(currentPreset == nil ? presentationData.strings.Common_Create : presentationData.strings.Common_Done), style: .bold, enabled: state.isComplete, action: {
|
||||||
applyImpl?()
|
applyImpl?({
|
||||||
|
dismissImpl?()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let previousStateValue = previousState
|
let previousStateValue = previousState
|
||||||
@ -1531,3 +1504,42 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
return controller
|
return controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, title: String, peerIds: [EnginePeer.Id], pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController) -> Void, linkUpdated: @escaping (ExportedChatFolderLink?) -> Void) {
|
||||||
|
if peerIds.isEmpty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = (context.engine.data.get(
|
||||||
|
EngineDataList(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).start(next: { peers in
|
||||||
|
let peers = peers.compactMap({ $0 })
|
||||||
|
if peers.allSatisfy({ !canShareLinkToPeer(peer: $0) }) {
|
||||||
|
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peerIds, currentInvitation: nil, linkUpdated: linkUpdated))
|
||||||
|
} else {
|
||||||
|
let _ = (context.engine.peers.exportChatFolder(filterId: folderId, title: "", peerIds: peerIds)
|
||||||
|
|> deliverOnMainQueue).start(next: { link in
|
||||||
|
linkUpdated(link)
|
||||||
|
|
||||||
|
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: link.peerIds, currentInvitation: link, linkUpdated: linkUpdated))
|
||||||
|
}, error: { error in
|
||||||
|
//TODO:localize
|
||||||
|
let text: String
|
||||||
|
switch error {
|
||||||
|
case .generic:
|
||||||
|
text = "An error occurred"
|
||||||
|
case let .limitExceeded(limit, premiumLimit):
|
||||||
|
if limit < premiumLimit {
|
||||||
|
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .linksPerSharedFolder, count: limit, action: {
|
||||||
|
})
|
||||||
|
pushController(limitController)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
text = "You can't create more links."
|
||||||
|
}
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
presentController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -2165,6 +2165,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}, openPasswordSetup: {
|
}, openPasswordSetup: {
|
||||||
}, openPremiumIntro: {
|
}, openPremiumIntro: {
|
||||||
}, openChatFolderUpdates: {
|
}, openChatFolderUpdates: {
|
||||||
|
}, hideChatFolderUpdates: {
|
||||||
})
|
})
|
||||||
chatListInteraction.isSearchMode = true
|
chatListInteraction.isSearchMode = true
|
||||||
|
|
||||||
@ -3398,7 +3399,8 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
|
|||||||
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
||||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
|
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||||
|
})
|
||||||
var isInlineMode = false
|
var isInlineMode = false
|
||||||
if case .topics = key {
|
if case .topics = key {
|
||||||
isInlineMode = false
|
isInlineMode = false
|
||||||
|
@ -97,6 +97,7 @@ public final class ChatListNodeInteraction {
|
|||||||
let openPasswordSetup: () -> Void
|
let openPasswordSetup: () -> Void
|
||||||
let openPremiumIntro: () -> Void
|
let openPremiumIntro: () -> Void
|
||||||
let openChatFolderUpdates: () -> Void
|
let openChatFolderUpdates: () -> Void
|
||||||
|
let hideChatFolderUpdates: () -> Void
|
||||||
|
|
||||||
public var searchTextHighightState: String?
|
public var searchTextHighightState: String?
|
||||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||||
@ -142,7 +143,8 @@ public final class ChatListNodeInteraction {
|
|||||||
openStorageManagement: @escaping () -> Void,
|
openStorageManagement: @escaping () -> Void,
|
||||||
openPasswordSetup: @escaping () -> Void,
|
openPasswordSetup: @escaping () -> Void,
|
||||||
openPremiumIntro: @escaping () -> Void,
|
openPremiumIntro: @escaping () -> Void,
|
||||||
openChatFolderUpdates: @escaping () -> Void
|
openChatFolderUpdates: @escaping () -> Void,
|
||||||
|
hideChatFolderUpdates: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.activateSearch = activateSearch
|
self.activateSearch = activateSearch
|
||||||
self.peerSelected = peerSelected
|
self.peerSelected = peerSelected
|
||||||
@ -176,6 +178,7 @@ public final class ChatListNodeInteraction {
|
|||||||
self.openPasswordSetup = openPasswordSetup
|
self.openPasswordSetup = openPasswordSetup
|
||||||
self.openPremiumIntro = openPremiumIntro
|
self.openPremiumIntro = openPremiumIntro
|
||||||
self.openChatFolderUpdates = openChatFolderUpdates
|
self.openChatFolderUpdates = openChatFolderUpdates
|
||||||
|
self.hideChatFolderUpdates = hideChatFolderUpdates
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,16 +619,26 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
case let .ArchiveIntro(presentationData):
|
case let .ArchiveIntro(presentationData):
|
||||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
|
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
|
||||||
case let .Notice(presentationData, notice):
|
case let .Notice(presentationData, notice):
|
||||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] in
|
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
|
||||||
switch notice {
|
switch action {
|
||||||
case .clearStorage:
|
case .activate:
|
||||||
nodeInteraction?.openStorageManagement()
|
switch notice {
|
||||||
case .setupPassword:
|
case .clearStorage:
|
||||||
nodeInteraction?.openPasswordSetup()
|
nodeInteraction?.openStorageManagement()
|
||||||
case .premiumUpgrade, .premiumAnnualDiscount:
|
case .setupPassword:
|
||||||
nodeInteraction?.openPremiumIntro()
|
nodeInteraction?.openPasswordSetup()
|
||||||
case .chatFolderUpdates:
|
case .premiumUpgrade, .premiumAnnualDiscount:
|
||||||
nodeInteraction?.openChatFolderUpdates()
|
nodeInteraction?.openPremiumIntro()
|
||||||
|
case .chatFolderUpdates:
|
||||||
|
nodeInteraction?.openChatFolderUpdates()
|
||||||
|
}
|
||||||
|
case .hide:
|
||||||
|
switch notice {
|
||||||
|
case .chatFolderUpdates:
|
||||||
|
nodeInteraction?.hideChatFolderUpdates()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}), directionHint: entry.directionHint)
|
}), directionHint: entry.directionHint)
|
||||||
}
|
}
|
||||||
@ -871,16 +884,26 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
case let .ArchiveIntro(presentationData):
|
case let .ArchiveIntro(presentationData):
|
||||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
|
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
|
||||||
case let .Notice(presentationData, notice):
|
case let .Notice(presentationData, notice):
|
||||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] in
|
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
|
||||||
switch notice {
|
switch action {
|
||||||
case .clearStorage:
|
case .activate:
|
||||||
nodeInteraction?.openStorageManagement()
|
switch notice {
|
||||||
case .setupPassword:
|
case .clearStorage:
|
||||||
nodeInteraction?.openPasswordSetup()
|
nodeInteraction?.openStorageManagement()
|
||||||
case .premiumUpgrade, .premiumAnnualDiscount:
|
case .setupPassword:
|
||||||
nodeInteraction?.openPremiumIntro()
|
nodeInteraction?.openPasswordSetup()
|
||||||
case .chatFolderUpdates:
|
case .premiumUpgrade, .premiumAnnualDiscount:
|
||||||
nodeInteraction?.openChatFolderUpdates()
|
nodeInteraction?.openPremiumIntro()
|
||||||
|
case .chatFolderUpdates:
|
||||||
|
nodeInteraction?.openChatFolderUpdates()
|
||||||
|
}
|
||||||
|
case .hide:
|
||||||
|
switch notice {
|
||||||
|
case .chatFolderUpdates:
|
||||||
|
nodeInteraction?.hideChatFolderUpdates()
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}), directionHint: entry.directionHint)
|
}), directionHint: entry.directionHint)
|
||||||
case .HeaderEntry:
|
case .HeaderEntry:
|
||||||
@ -1076,8 +1099,9 @@ public final class ChatListNode: ListView {
|
|||||||
|
|
||||||
let hideArhiveIntro = ValuePromise<Bool>(false, ignoreRepeated: true)
|
let hideArhiveIntro = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
|
|
||||||
private let chatFolderUpdates = Promise<ChatFolderUpdates?>(nil)
|
private let chatFolderUpdates = Promise<ChatFolderUpdates?>()
|
||||||
private var pollFilterUpdatesDisposable: Disposable?
|
private var pollFilterUpdatesDisposable: Disposable?
|
||||||
|
private var chatFilterUpdatesDisposable: Disposable?
|
||||||
|
|
||||||
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) {
|
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.context = context
|
||||||
@ -1413,6 +1437,21 @@ public final class ChatListNode: ListView {
|
|||||||
|
|
||||||
self.push?(ChatFolderLinkPreviewScreen(context: self.context, subject: .updates(result), contents: result.chatFolderLinkContents))
|
self.push?(ChatFolderLinkPreviewScreen(context: self.context, subject: .updates(result), contents: result.chatFolderLinkContents))
|
||||||
})
|
})
|
||||||
|
}, hideChatFolderUpdates: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = (self.chatFolderUpdates.get()
|
||||||
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
|
guard let self, let result else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let localFilterId = result.chatFolderLinkContents.localFilterId {
|
||||||
|
let _ = self.context.engine.peers.hideChatFolderUpdates(folderId: localFilterId).start()
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
nodeInteraction.isInlineMode = isInlineMode
|
nodeInteraction.isInlineMode = isInlineMode
|
||||||
|
|
||||||
@ -1973,6 +2012,7 @@ public final class ChatListNode: ListView {
|
|||||||
var didIncludeRemovingPeerId = false
|
var didIncludeRemovingPeerId = false
|
||||||
var didIncludeHiddenByDefaultArchive = false
|
var didIncludeHiddenByDefaultArchive = false
|
||||||
var didIncludeHiddenThread = false
|
var didIncludeHiddenThread = false
|
||||||
|
var didIncludeNotice = false
|
||||||
if let previous = previousView {
|
if let previous = previousView {
|
||||||
for entry in previous.filteredEntries {
|
for entry in previous.filteredEntries {
|
||||||
if case let .PeerEntry(peerEntry) = entry {
|
if case let .PeerEntry(peerEntry) = entry {
|
||||||
@ -1999,12 +2039,15 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
} else if case let .GroupReferenceEntry(_, _, _, _, _, _, _, _, hiddenByDefault) = entry {
|
} else if case let .GroupReferenceEntry(_, _, _, _, _, _, _, _, hiddenByDefault) = entry {
|
||||||
didIncludeHiddenByDefaultArchive = hiddenByDefault
|
didIncludeHiddenByDefaultArchive = hiddenByDefault
|
||||||
|
} else if case .Notice = entry {
|
||||||
|
didIncludeNotice = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var doesIncludeRemovingPeerId = false
|
var doesIncludeRemovingPeerId = false
|
||||||
var doesIncludeArchive = false
|
var doesIncludeArchive = false
|
||||||
var doesIncludeHiddenByDefaultArchive = false
|
var doesIncludeHiddenByDefaultArchive = false
|
||||||
|
var doesIncludeNotice = false
|
||||||
|
|
||||||
var doesIncludeHiddenThread = false
|
var doesIncludeHiddenThread = false
|
||||||
for entry in processedView.filteredEntries {
|
for entry in processedView.filteredEntries {
|
||||||
@ -2033,6 +2076,8 @@ public final class ChatListNode: ListView {
|
|||||||
} else if case let .GroupReferenceEntry(_, _, _, _, _, _, _, _, hiddenByDefault) = entry {
|
} else if case let .GroupReferenceEntry(_, _, _, _, _, _, _, _, hiddenByDefault) = entry {
|
||||||
doesIncludeArchive = true
|
doesIncludeArchive = true
|
||||||
doesIncludeHiddenByDefaultArchive = hiddenByDefault
|
doesIncludeHiddenByDefaultArchive = hiddenByDefault
|
||||||
|
} else if case .Notice = entry {
|
||||||
|
doesIncludeNotice = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if previousPinnedChats != updatedPinnedChats || previousPinnedThreads != updatedPinnedThreads {
|
if previousPinnedChats != updatedPinnedChats || previousPinnedThreads != updatedPinnedThreads {
|
||||||
@ -2059,6 +2104,9 @@ public final class ChatListNode: ListView {
|
|||||||
if didIncludeHiddenThread != doesIncludeHiddenThread {
|
if didIncludeHiddenThread != doesIncludeHiddenThread {
|
||||||
disableAnimations = false
|
disableAnimations = false
|
||||||
}
|
}
|
||||||
|
if didIncludeNotice != doesIncludeNotice {
|
||||||
|
disableAnimations = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault {
|
if let _ = previousHideArchivedFolderByDefaultValue, previousHideArchivedFolderByDefaultValue != hideArchivedFolderByDefault {
|
||||||
@ -2578,7 +2626,7 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pollFilterUpdates(shouldDelay: false)
|
self.pollFilterUpdates()
|
||||||
self.resetFilter()
|
self.resetFilter()
|
||||||
|
|
||||||
let selectionRecognizer = ChatHistoryListSelectionRecognizer(target: self, action: #selector(self.selectionPanGesture(_:)))
|
let selectionRecognizer = ChatHistoryListSelectionRecognizer(target: self, action: #selector(self.selectionPanGesture(_:)))
|
||||||
@ -2596,6 +2644,7 @@ public final class ChatListNode: ListView {
|
|||||||
self.activityStatusesDisposable?.dispose()
|
self.activityStatusesDisposable?.dispose()
|
||||||
self.updatedFilterDisposable.dispose()
|
self.updatedFilterDisposable.dispose()
|
||||||
self.pollFilterUpdatesDisposable?.dispose()
|
self.pollFilterUpdatesDisposable?.dispose()
|
||||||
|
self.chatFilterUpdatesDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateFilter(_ filter: ChatListFilter?) {
|
func updateFilter(_ filter: ChatListFilter?) {
|
||||||
@ -2605,17 +2654,18 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func pollFilterUpdates(shouldDelay: Bool) {
|
private func pollFilterUpdates() {
|
||||||
guard let chatListFilter, case let .filter(id, _, _, data) = chatListFilter, data.isShared else {
|
guard let chatListFilter, case let .filter(id, _, _, data) = chatListFilter, data.isShared else {
|
||||||
|
self.chatFolderUpdates.set(.single(nil))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.pollFilterUpdatesDisposable = (context.engine.peers.getChatFolderUpdates(folderId: id)
|
self.pollFilterUpdatesDisposable = self.context.engine.peers.pollChatFolderUpdates(folderId: id).start()
|
||||||
|> delay(shouldDelay ? 5.0 : 0.0, queue: .mainQueue())).start(next: { [weak self] result in
|
self.chatFilterUpdatesDisposable = (self.context.engine.peers.subscribedChatFolderUpdates(folderId: id)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.chatFolderUpdates.set(.single(result))
|
self.chatFolderUpdates.set(.single(result))
|
||||||
self.pollFilterUpdates(shouldDelay: true)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,16 +7,22 @@ import SwiftSignalKit
|
|||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
import ListSectionHeaderNode
|
import ListSectionHeaderNode
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
import ItemListUI
|
||||||
|
|
||||||
class ChatListStorageInfoItem: ListViewItem {
|
class ChatListStorageInfoItem: ListViewItem {
|
||||||
|
enum Action {
|
||||||
|
case activate
|
||||||
|
case hide
|
||||||
|
}
|
||||||
|
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
let notice: ChatListNotice
|
let notice: ChatListNotice
|
||||||
let action: () -> Void
|
let action: (Action) -> Void
|
||||||
|
|
||||||
let selectable: Bool = true
|
let selectable: Bool = true
|
||||||
|
|
||||||
init(theme: PresentationTheme, strings: PresentationStrings, notice: ChatListNotice, action: @escaping () -> Void) {
|
init(theme: PresentationTheme, strings: PresentationStrings, notice: ChatListNotice, action: @escaping (Action) -> Void) {
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
self.strings = strings
|
self.strings = strings
|
||||||
self.notice = notice
|
self.notice = notice
|
||||||
@ -26,7 +32,7 @@ class ChatListStorageInfoItem: ListViewItem {
|
|||||||
func selected(listView: ListView) {
|
func selected(listView: ListView) {
|
||||||
listView.clearHighlightAnimated(true)
|
listView.clearHighlightAnimated(true)
|
||||||
|
|
||||||
self.action()
|
self.action(.activate)
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
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) {
|
||||||
@ -72,7 +78,8 @@ private let separatorHeight = 1.0 / UIScreen.main.scale
|
|||||||
private let titleFont = Font.semibold(15.0)
|
private let titleFont = Font.semibold(15.0)
|
||||||
private let textFont = Font.regular(15.0)
|
private let textFont = Font.regular(15.0)
|
||||||
|
|
||||||
class ChatListStorageInfoItemNode: ListViewItemNode {
|
class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
|
||||||
|
private let contentContainer: ASDisplayNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let textNode: TextNode
|
private let textNode: TextNode
|
||||||
private let arrowNode: ASImageNode
|
private let arrowNode: ASImageNode
|
||||||
@ -81,17 +88,23 @@ class ChatListStorageInfoItemNode: ListViewItemNode {
|
|||||||
private var item: ChatListStorageInfoItem?
|
private var item: ChatListStorageInfoItem?
|
||||||
|
|
||||||
required init() {
|
required init() {
|
||||||
|
self.contentContainer = ASDisplayNode()
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
self.textNode = TextNode()
|
self.textNode = TextNode()
|
||||||
self.arrowNode = ASImageNode()
|
self.arrowNode = ASImageNode()
|
||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
|
||||||
|
|
||||||
|
self.clipsToBounds = true
|
||||||
|
|
||||||
self.addSubnode(self.separatorNode)
|
self.addSubnode(self.separatorNode)
|
||||||
self.addSubnode(self.titleNode)
|
self.contentContainer.addSubnode(self.titleNode)
|
||||||
self.addSubnode(self.textNode)
|
self.contentContainer.addSubnode(self.textNode)
|
||||||
self.addSubnode(self.arrowNode)
|
self.contentContainer.addSubnode(self.arrowNode)
|
||||||
|
|
||||||
|
self.addSubnode(self.contentContainer)
|
||||||
|
|
||||||
self.zPosition = 1.0
|
self.zPosition = 1.0
|
||||||
}
|
}
|
||||||
@ -201,8 +214,35 @@ class ChatListStorageInfoItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
strongSelf.contentSize = layout.contentSize
|
strongSelf.contentSize = layout.contentSize
|
||||||
strongSelf.insets = layout.insets
|
strongSelf.insets = layout.insets
|
||||||
|
|
||||||
|
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||||
|
|
||||||
|
strongSelf.contentContainer.frame = CGRect(origin: CGPoint(), size: layout.contentSize)
|
||||||
|
|
||||||
|
switch item.notice {
|
||||||
|
case .chatFolderUpdates:
|
||||||
|
//TODO:locallize
|
||||||
|
strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: "Hide", icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
|
||||||
|
default:
|
||||||
|
strongSelf.setRevealOptions((left: [], right: []))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func updateRevealOffset(offset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.updateRevealOffset(offset: offset, transition: transition)
|
||||||
|
|
||||||
|
transition.updateSublayerTransformOffset(layer: self.contentContainer.layer, offset: CGPoint(x: offset, y: 0.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func revealOptionSelected(_ option: ItemListRevealOption, animated: Bool) {
|
||||||
|
if let item = self.item {
|
||||||
|
item.action(.hide)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.setRevealOptionsOpened(false, animated: true)
|
||||||
|
self.revealOptionsInteractivelyClosed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,13 +75,20 @@ public enum ContextMenuActionBadgeColor {
|
|||||||
case inactive
|
case inactive
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ContextMenuActionBadge {
|
public struct ContextMenuActionBadge: Equatable {
|
||||||
|
public enum Style {
|
||||||
|
case badge
|
||||||
|
case label
|
||||||
|
}
|
||||||
|
|
||||||
public var value: String
|
public var value: String
|
||||||
public var color: ContextMenuActionBadgeColor
|
public var color: ContextMenuActionBadgeColor
|
||||||
|
public var style: Style
|
||||||
|
|
||||||
public init(value: String, color: ContextMenuActionBadgeColor) {
|
public init(value: String, color: ContextMenuActionBadgeColor, style: Style = .badge) {
|
||||||
self.value = value
|
self.value = value
|
||||||
self.color = color
|
self.color = color
|
||||||
|
self.style = style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,8 +64,11 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
|||||||
private let titleLabelNode: ImmediateTextNode
|
private let titleLabelNode: ImmediateTextNode
|
||||||
private let subtitleNode: ImmediateTextNode
|
private let subtitleNode: ImmediateTextNode
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
|
private var badgeIconNode: ASImageNode?
|
||||||
private var animationNode: AnimationNode?
|
private var animationNode: AnimationNode?
|
||||||
|
|
||||||
|
private var currentBadge: (badge: ContextMenuActionBadge, image: UIImage)?
|
||||||
|
|
||||||
private var iconDisposable: Disposable?
|
private var iconDisposable: Disposable?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -302,14 +305,66 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
|||||||
iconSize = iconImage?.size
|
iconSize = iconImage?.size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let badgeSize: CGSize?
|
||||||
|
if let badge = self.item.badge {
|
||||||
|
var badgeImage: UIImage?
|
||||||
|
if let currentBadge = self.currentBadge, currentBadge.badge == badge {
|
||||||
|
badgeImage = currentBadge.image
|
||||||
|
} else {
|
||||||
|
let badgeTextColor: UIColor = presentationData.theme.list.itemCheckColors.foregroundColor
|
||||||
|
let badgeString = NSAttributedString(string: badge.value, font: Font.semibold(11.0), textColor: badgeTextColor)
|
||||||
|
let badgeTextBounds = badgeString.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil)
|
||||||
|
let badgeSideInset: CGFloat = 3.0
|
||||||
|
let badgeVerticalInset: CGFloat = 1.0
|
||||||
|
let badgeBackgroundSize = CGSize(width: badgeSideInset * 2.0 + ceil(badgeTextBounds.width), height: badgeVerticalInset * 2.0 + ceil(badgeTextBounds.height))
|
||||||
|
badgeImage = generateImage(badgeBackgroundSize, rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
|
||||||
|
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 5.0).cgPath)
|
||||||
|
context.fillPath()
|
||||||
|
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
|
||||||
|
badgeString.draw(at: CGPoint(x: badgeTextBounds.minX + badgeSideInset + UIScreenPixel, y: badgeTextBounds.minY + badgeVerticalInset + UIScreenPixel))
|
||||||
|
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let badgeIconNode: ASImageNode
|
||||||
|
if let current = self.badgeIconNode {
|
||||||
|
badgeIconNode = current
|
||||||
|
} else {
|
||||||
|
badgeIconNode = ASImageNode()
|
||||||
|
self.badgeIconNode = badgeIconNode
|
||||||
|
self.addSubnode(badgeIconNode)
|
||||||
|
}
|
||||||
|
badgeIconNode.image = badgeImage
|
||||||
|
|
||||||
|
badgeSize = badgeImage?.size
|
||||||
|
} else {
|
||||||
|
if let badgeIconNode = self.badgeIconNode {
|
||||||
|
self.badgeIconNode = nil
|
||||||
|
badgeIconNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
badgeSize = nil
|
||||||
|
}
|
||||||
|
|
||||||
var maxTextWidth: CGFloat = constrainedSize.width
|
var maxTextWidth: CGFloat = constrainedSize.width
|
||||||
maxTextWidth -= sideInset
|
maxTextWidth -= sideInset
|
||||||
|
|
||||||
if let iconSize = iconSize {
|
if let iconSize = iconSize {
|
||||||
maxTextWidth -= max(standardIconWidth, iconSize.width)
|
maxTextWidth -= max(standardIconWidth, iconSize.width)
|
||||||
maxTextWidth -= iconSpacing
|
maxTextWidth -= iconSpacing
|
||||||
} else {
|
} else {
|
||||||
maxTextWidth -= sideInset
|
maxTextWidth -= sideInset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let badgeSize = badgeSize {
|
||||||
|
maxTextWidth -= badgeSize.width
|
||||||
|
maxTextWidth -= 8.0
|
||||||
|
}
|
||||||
|
|
||||||
maxTextWidth = max(1.0, maxTextWidth)
|
maxTextWidth = max(1.0, maxTextWidth)
|
||||||
|
|
||||||
let titleSize = self.titleLabelNode.updateLayout(CGSize(width: maxTextWidth, height: 1000.0))
|
let titleSize = self.titleLabelNode.updateLayout(CGSize(width: maxTextWidth, height: 1000.0))
|
||||||
@ -351,6 +406,12 @@ private final class ContextControllerActionsListActionItemNode: HighlightTrackin
|
|||||||
transition.updateFrameAdditive(node: self.titleLabelNode, frame: titleFrame)
|
transition.updateFrameAdditive(node: self.titleLabelNode, frame: titleFrame)
|
||||||
transition.updateFrameAdditive(node: self.subtitleNode, frame: subtitleFrame)
|
transition.updateFrameAdditive(node: self.subtitleNode, frame: subtitleFrame)
|
||||||
|
|
||||||
|
if let badgeIconNode = self.badgeIconNode {
|
||||||
|
if let iconSize = badgeIconNode.image?.size {
|
||||||
|
transition.updateFrame(node: badgeIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 8.0, y: titleFrame.minY + floor((titleFrame.height - iconSize.height) * 0.5)), size: iconSize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let iconSize = iconSize {
|
if let iconSize = iconSize {
|
||||||
let iconWidth = max(standardIconWidth, iconSize.width)
|
let iconWidth = max(standardIconWidth, iconSize.width)
|
||||||
let iconFrame = CGRect(
|
let iconFrame = CGRect(
|
||||||
|
@ -95,6 +95,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||||||
}, openPasswordSetup: {
|
}, openPasswordSetup: {
|
||||||
}, openPremiumIntro: {
|
}, openPremiumIntro: {
|
||||||
}, openChatFolderUpdates: {
|
}, openChatFolderUpdates: {
|
||||||
|
}, hideChatFolderUpdates: {
|
||||||
})
|
})
|
||||||
|
|
||||||
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
|
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
|
||||||
|
@ -118,6 +118,7 @@ private class PremiumLimitAnimationComponent: Component {
|
|||||||
self.activeContainer.masksToBounds = true
|
self.activeContainer.masksToBounds = true
|
||||||
|
|
||||||
self.activeBackground = SimpleLayer()
|
self.activeBackground = SimpleLayer()
|
||||||
|
self.activeBackground.anchorPoint = CGPoint()
|
||||||
|
|
||||||
self.badgeView = UIView()
|
self.badgeView = UIView()
|
||||||
self.badgeView.alpha = 0.0
|
self.badgeView.alpha = 0.0
|
||||||
@ -244,13 +245,16 @@ private class PremiumLimitAnimationComponent: Component {
|
|||||||
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - lineHeight), size: CGSize(width: availableSize.width, height: lineHeight))
|
let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - lineHeight), size: CGSize(width: availableSize.width, height: lineHeight))
|
||||||
self.container.frame = containerFrame
|
self.container.frame = containerFrame
|
||||||
|
|
||||||
if !component.isPremiumDisabled {
|
let activityPosition: CGFloat = floor(containerFrame.width * component.badgePosition)
|
||||||
self.inactiveBackground.frame = CGRect(origin: .zero, size: CGSize(width: containerFrame.width / 2.0, height: lineHeight))
|
let activeWidth: CGFloat = containerFrame.width - activityPosition
|
||||||
self.activeContainer.frame = CGRect(origin: CGPoint(x: containerFrame.width / 2.0, y: 0.0), size: CGSize(width: containerFrame.width / 2.0, height: lineHeight))
|
|
||||||
|
|
||||||
self.activeBackground.bounds = CGRect(origin: .zero, size: CGSize(width: containerFrame.width * 3.0 / 2.0, height: lineHeight))
|
if !component.isPremiumDisabled {
|
||||||
|
self.inactiveBackground.frame = CGRect(origin: .zero, size: CGSize(width: activityPosition, height: lineHeight))
|
||||||
|
self.activeContainer.frame = CGRect(origin: CGPoint(x: activityPosition, y: 0.0), size: CGSize(width: activeWidth, height: lineHeight))
|
||||||
|
|
||||||
|
self.activeBackground.frame = CGRect(origin: .zero, size: CGSize(width: activeWidth * (1.0 + 0.35), height: lineHeight))
|
||||||
if self.activeBackground.animation(forKey: "movement") == nil {
|
if self.activeBackground.animation(forKey: "movement") == nil {
|
||||||
self.activeBackground.position = CGPoint(x: containerFrame.width * 3.0 / 4.0 - self.activeBackground.frame.width * 0.35, y: lineHeight / 2.0)
|
self.activeBackground.position = CGPoint(x: -self.activeContainer.frame.width * 0.35, y: lineHeight / 2.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,7 +310,7 @@ private class PremiumLimitAnimationComponent: Component {
|
|||||||
if let _ = self.badgeView.layer.animation(forKey: "appearance1") {
|
if let _ = self.badgeView.layer.animation(forKey: "appearance1") {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
self.badgeView.center = CGPoint(x: 3.0 + (availableSize.width - 6.0) * badgePosition, y: 82.0)
|
self.badgeView.center = CGPoint(x: availableSize.width * badgePosition, y: 82.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.badgeView.frame.maxX > availableSize.width {
|
if self.badgeView.frame.maxX > availableSize.width {
|
||||||
@ -375,13 +379,15 @@ private class PremiumLimitAnimationComponent: Component {
|
|||||||
}
|
}
|
||||||
self.badgeForeground.position = CGPoint(x: badgeNewValue, y: self.badgeForeground.bounds.size.height / 2.0)
|
self.badgeForeground.position = CGPoint(x: badgeNewValue, y: self.badgeForeground.bounds.size.height / 2.0)
|
||||||
|
|
||||||
let lineOffset = (self.activeBackground.frame.width - self.activeContainer.bounds.width) / 2.0
|
let lineOffset = 0.0
|
||||||
let linePreviousValue = self.activeBackground.position.x
|
let linePreviousValue = self.activeBackground.position.x
|
||||||
var lineNewValue: CGFloat = lineOffset
|
var lineNewValue: CGFloat = lineOffset
|
||||||
if lineOffset - linePreviousValue < self.activeBackground.frame.width * 0.25 {
|
if linePreviousValue < 0.0 {
|
||||||
lineNewValue -= self.activeBackground.frame.width * 0.35
|
lineNewValue = 0.0
|
||||||
|
} else {
|
||||||
|
lineNewValue = -self.activeContainer.bounds.width * 0.35
|
||||||
}
|
}
|
||||||
self.activeBackground.position = CGPoint(x: lineNewValue, y: self.activeBackground.bounds.size.height / 2.0)
|
self.activeBackground.position = CGPoint(x: lineNewValue, y: 0.0)
|
||||||
|
|
||||||
let badgeAnimation = CABasicAnimation(keyPath: "position.x")
|
let badgeAnimation = CABasicAnimation(keyPath: "position.x")
|
||||||
badgeAnimation.duration = 4.5
|
badgeAnimation.duration = 4.5
|
||||||
@ -585,16 +591,25 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
|
|||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let activityPosition = floor(context.availableSize.width * component.badgePosition)
|
||||||
|
|
||||||
|
var inactiveValueOpacity: CGFloat = 1.0
|
||||||
|
if inactiveValue.size.width + inactiveTitle.size.width >= activityPosition - 8.0 {
|
||||||
|
inactiveValueOpacity = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
context.add(inactiveTitle
|
context.add(inactiveTitle
|
||||||
.position(CGPoint(x: inactiveTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0))
|
.position(CGPoint(x: inactiveTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0))
|
||||||
|
.opacity(inactiveValueOpacity)
|
||||||
)
|
)
|
||||||
|
|
||||||
context.add(inactiveValue
|
context.add(inactiveValue
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0 - inactiveValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0))
|
.position(CGPoint(x: activityPosition - inactiveValue.size.width / 2.0 - 12.0, y: height - lineHeight / 2.0))
|
||||||
|
.opacity(inactiveValueOpacity)
|
||||||
)
|
)
|
||||||
|
|
||||||
context.add(activeTitle
|
context.add(activeTitle
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0 + activeTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0))
|
.position(CGPoint(x: activityPosition + activeTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0))
|
||||||
)
|
)
|
||||||
|
|
||||||
context.add(activeValue
|
context.add(activeValue
|
||||||
@ -766,22 +781,26 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
string = strings.Premium_MaxChatsInFolderNoPremiumText("\(limit)").string
|
string = strings.Premium_MaxChatsInFolderNoPremiumText("\(limit)").string
|
||||||
}
|
}
|
||||||
case .linksPerSharedFolder:
|
case .linksPerSharedFolder:
|
||||||
//TODO:localize
|
/*let count: Int32 = 5 + Int32("".count)// component.count
|
||||||
let limit = state.limits.maxSharedFolderInviteLinks
|
let limit: Int32 = 5 + Int32("".count)//state.limits.maxSharedFolderInviteLinks
|
||||||
let premiumLimit = state.premiumLimits.maxSharedFolderInviteLinks
|
let premiumLimit: Int32 = 100 + Int32("".count)//state.premiumLimits.maxSharedFolderInviteLinks*/
|
||||||
|
|
||||||
|
let count: Int32 = component.count
|
||||||
|
let limit: Int32 = state.limits.maxSharedFolderInviteLinks
|
||||||
|
let premiumLimit: Int32 = state.premiumLimits.maxSharedFolderInviteLinks
|
||||||
|
|
||||||
iconName = "Premium/Link"
|
iconName = "Premium/Link"
|
||||||
badgeText = "\(component.count)"
|
badgeText = "\(count)"
|
||||||
string = component.count >= premiumLimit ? strings.Premium_MaxSharedFolderLinksFinalText("\(premiumLimit)").string : strings.Premium_MaxSharedFolderLinksText("\(limit)", "\(premiumLimit)").string
|
string = count >= premiumLimit ? strings.Premium_MaxSharedFolderLinksFinalText("\(premiumLimit)").string : strings.Premium_MaxSharedFolderLinksText("\(limit)", "\(premiumLimit)").string
|
||||||
defaultValue = component.count > limit ? "\(limit)" : ""
|
defaultValue = count > limit ? "\(limit)" : ""
|
||||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
premiumValue = count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||||
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
badgePosition = max(0.1, CGFloat(count) / CGFloat(premiumLimit))
|
||||||
|
|
||||||
if isPremiumDisabled {
|
if isPremiumDisabled {
|
||||||
badgeText = "\(limit)"
|
badgeText = "\(limit)"
|
||||||
string = strings.Premium_MaxSharedFolderLinksNoPremiumText("\(limit)").string
|
string = strings.Premium_MaxSharedFolderLinksNoPremiumText("\(limit)").string
|
||||||
}
|
}
|
||||||
case .membershipInSharedFolders:
|
case .membershipInSharedFolders:
|
||||||
//TODO:localize
|
|
||||||
let limit = state.limits.maxSharedFolderJoin
|
let limit = state.limits.maxSharedFolderJoin
|
||||||
let premiumLimit = state.premiumLimits.maxSharedFolderJoin
|
let premiumLimit = state.premiumLimits.maxSharedFolderJoin
|
||||||
iconName = "Premium/Folder"
|
iconName = "Premium/Folder"
|
||||||
|
@ -47,7 +47,7 @@ public final class QrCodeScreen: ViewController {
|
|||||||
case let .invite(invite, _):
|
case let .invite(invite, _):
|
||||||
return invite.link ?? ""
|
return invite.link ?? ""
|
||||||
case let .chatFolder(slug):
|
case let .chatFolder(slug):
|
||||||
return "https://t.me/folder/\(slug)"
|
return "https://t.me/list/\(slug)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +222,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
|||||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
|
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
|
||||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
|
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||||
|
})
|
||||||
|
|
||||||
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||||
|
|
||||||
|
@ -843,7 +843,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
|||||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in
|
}, present: { _ in
|
||||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
|
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||||
|
})
|
||||||
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||||
|
|
||||||
func makeChatListItem(
|
func makeChatListItem(
|
||||||
|
@ -367,7 +367,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in
|
}, present: { _ in
|
||||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
|
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {}, hideChatFolderUpdates: {
|
||||||
|
})
|
||||||
|
|
||||||
func makeChatListItem(
|
func makeChatListItem(
|
||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
|
@ -997,7 +997,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-1699676497] = { return Api.channels.ChannelParticipants.parse_channelParticipants($0) }
|
dict[-1699676497] = { return Api.channels.ChannelParticipants.parse_channelParticipants($0) }
|
||||||
dict[-266911767] = { return Api.channels.ChannelParticipants.parse_channelParticipantsNotModified($0) }
|
dict[-266911767] = { return Api.channels.ChannelParticipants.parse_channelParticipantsNotModified($0) }
|
||||||
dict[-191450938] = { return Api.channels.SendAsPeers.parse_sendAsPeers($0) }
|
dict[-191450938] = { return Api.channels.SendAsPeers.parse_sendAsPeers($0) }
|
||||||
dict[-557919187] = { return Api.communities.CommunityInvite.parse_communityInvite($0) }
|
dict[59080097] = { return Api.communities.CommunityInvite.parse_communityInvite($0) }
|
||||||
dict[-951718393] = { return Api.communities.CommunityInvite.parse_communityInviteAlready($0) }
|
dict[-951718393] = { return Api.communities.CommunityInvite.parse_communityInviteAlready($0) }
|
||||||
dict[-414818125] = { return Api.communities.CommunityUpdates.parse_communityUpdates($0) }
|
dict[-414818125] = { return Api.communities.CommunityUpdates.parse_communityUpdates($0) }
|
||||||
dict[1805101290] = { return Api.communities.ExportedCommunityInvite.parse_exportedCommunityInvite($0) }
|
dict[1805101290] = { return Api.communities.ExportedCommunityInvite.parse_exportedCommunityInvite($0) }
|
||||||
|
@ -896,16 +896,18 @@ public extension Api.channels {
|
|||||||
}
|
}
|
||||||
public extension Api.communities {
|
public extension Api.communities {
|
||||||
enum CommunityInvite: TypeConstructorDescription {
|
enum CommunityInvite: TypeConstructorDescription {
|
||||||
case communityInvite(title: String, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
case communityInvite(flags: Int32, title: String, emoticon: String?, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
||||||
case communityInviteAlready(filterId: Int32, missingPeers: [Api.Peer], alreadyPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
case communityInviteAlready(filterId: Int32, missingPeers: [Api.Peer], alreadyPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
switch self {
|
switch self {
|
||||||
case .communityInvite(let title, let peers, let chats, let users):
|
case .communityInvite(let flags, let title, let emoticon, let peers, let chats, let users):
|
||||||
if boxed {
|
if boxed {
|
||||||
buffer.appendInt32(-557919187)
|
buffer.appendInt32(59080097)
|
||||||
}
|
}
|
||||||
|
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||||
serializeString(title, buffer: buffer, boxed: false)
|
serializeString(title, buffer: buffer, boxed: false)
|
||||||
|
if Int(flags) & Int(1 << 0) != 0 {serializeString(emoticon!, buffer: buffer, boxed: false)}
|
||||||
buffer.appendInt32(481674261)
|
buffer.appendInt32(481674261)
|
||||||
buffer.appendInt32(Int32(peers.count))
|
buffer.appendInt32(Int32(peers.count))
|
||||||
for item in peers {
|
for item in peers {
|
||||||
@ -953,34 +955,40 @@ public extension Api.communities {
|
|||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
switch self {
|
switch self {
|
||||||
case .communityInvite(let title, let peers, let chats, let users):
|
case .communityInvite(let flags, let title, let emoticon, let peers, let chats, let users):
|
||||||
return ("communityInvite", [("title", title as Any), ("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)])
|
return ("communityInvite", [("flags", flags as Any), ("title", title as Any), ("emoticon", emoticon as Any), ("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||||
case .communityInviteAlready(let filterId, let missingPeers, let alreadyPeers, let chats, let users):
|
case .communityInviteAlready(let filterId, let missingPeers, let alreadyPeers, let chats, let users):
|
||||||
return ("communityInviteAlready", [("filterId", filterId as Any), ("missingPeers", missingPeers as Any), ("alreadyPeers", alreadyPeers as Any), ("chats", chats as Any), ("users", users as Any)])
|
return ("communityInviteAlready", [("filterId", filterId as Any), ("missingPeers", missingPeers as Any), ("alreadyPeers", alreadyPeers as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func parse_communityInvite(_ reader: BufferReader) -> CommunityInvite? {
|
public static func parse_communityInvite(_ reader: BufferReader) -> CommunityInvite? {
|
||||||
var _1: String?
|
var _1: Int32?
|
||||||
_1 = parseString(reader)
|
_1 = reader.readInt32()
|
||||||
var _2: [Api.Peer]?
|
var _2: String?
|
||||||
|
_2 = parseString(reader)
|
||||||
|
var _3: String?
|
||||||
|
if Int(_1!) & Int(1 << 0) != 0 {_3 = parseString(reader) }
|
||||||
|
var _4: [Api.Peer]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
|
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
|
||||||
}
|
}
|
||||||
var _3: [Api.Chat]?
|
var _5: [Api.Chat]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||||
}
|
}
|
||||||
var _4: [Api.User]?
|
var _6: [Api.User]?
|
||||||
if let _ = reader.readInt32() {
|
if let _ = reader.readInt32() {
|
||||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||||
}
|
}
|
||||||
let _c1 = _1 != nil
|
let _c1 = _1 != nil
|
||||||
let _c2 = _2 != nil
|
let _c2 = _2 != nil
|
||||||
let _c3 = _3 != nil
|
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||||
let _c4 = _4 != nil
|
let _c4 = _4 != nil
|
||||||
if _c1 && _c2 && _c3 && _c4 {
|
let _c5 = _5 != nil
|
||||||
return Api.communities.CommunityInvite.communityInvite(title: _1!, peers: _2!, chats: _3!, users: _4!)
|
let _c6 = _6 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||||
|
return Api.communities.CommunityInvite.communityInvite(flags: _1!, title: _2!, emoticon: _3, peers: _4!, chats: _5!, users: _6!)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -256,6 +256,7 @@ private enum PreferencesKeyValues: Int32 {
|
|||||||
case globalMessageAutoremoveTimeoutSettings = 27
|
case globalMessageAutoremoveTimeoutSettings = 27
|
||||||
case accountSpecificCacheStorageSettings = 28
|
case accountSpecificCacheStorageSettings = 28
|
||||||
case linksConfiguration = 29
|
case linksConfiguration = 29
|
||||||
|
case chatListFilterUpdates = 30
|
||||||
}
|
}
|
||||||
|
|
||||||
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
||||||
@ -402,6 +403,12 @@ public struct PreferencesKeys {
|
|||||||
key.setInt32(0, value: PreferencesKeyValues.linksConfiguration.rawValue)
|
key.setInt32(0, value: PreferencesKeyValues.linksConfiguration.rawValue)
|
||||||
return key
|
return key
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
public static let chatListFilterUpdates: ValueBoxKey = {
|
||||||
|
let key = ValueBoxKey(length: 4)
|
||||||
|
key.setInt32(0, value: PreferencesKeyValues.chatListFilterUpdates.rawValue)
|
||||||
|
return key
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum SharedDataKeyValues: Int32 {
|
private enum SharedDataKeyValues: Int32 {
|
||||||
|
@ -872,14 +872,29 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ChatListFiltersState: Codable, Equatable {
|
struct ChatListFiltersState: Codable, Equatable {
|
||||||
|
struct ChatListFilterUpdates: Codable, Equatable {
|
||||||
|
var folderId: Int32
|
||||||
|
var timestamp: Int32
|
||||||
|
var peerIds: [PeerId]
|
||||||
|
|
||||||
|
init(folderId: Int32, timestamp: Int32, peerIds: [PeerId]) {
|
||||||
|
self.folderId = folderId
|
||||||
|
self.timestamp = timestamp
|
||||||
|
self.peerIds = peerIds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var filters: [ChatListFilter]
|
var filters: [ChatListFilter]
|
||||||
var remoteFilters: [ChatListFilter]?
|
var remoteFilters: [ChatListFilter]?
|
||||||
|
|
||||||
static var `default` = ChatListFiltersState(filters: [], remoteFilters: nil)
|
var updates: [ChatListFilterUpdates]
|
||||||
|
|
||||||
fileprivate init(filters: [ChatListFilter], remoteFilters: [ChatListFilter]?) {
|
static var `default` = ChatListFiltersState(filters: [], remoteFilters: nil, updates: [])
|
||||||
|
|
||||||
|
fileprivate init(filters: [ChatListFilter], remoteFilters: [ChatListFilter]?, updates: [ChatListFilterUpdates]) {
|
||||||
self.filters = filters
|
self.filters = filters
|
||||||
self.remoteFilters = remoteFilters
|
self.remoteFilters = remoteFilters
|
||||||
|
self.updates = updates
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -887,6 +902,7 @@ struct ChatListFiltersState: Codable, Equatable {
|
|||||||
|
|
||||||
self.filters = try container.decode([ChatListFilter].self, forKey: "filters")
|
self.filters = try container.decode([ChatListFilter].self, forKey: "filters")
|
||||||
self.remoteFilters = try container.decodeIfPresent([ChatListFilter].self, forKey: "remoteFilters")
|
self.remoteFilters = try container.decodeIfPresent([ChatListFilter].self, forKey: "remoteFilters")
|
||||||
|
self.updates = try container.decodeIfPresent([ChatListFilterUpdates].self, forKey: "updates") ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
func encode(to encoder: Encoder) throws {
|
func encode(to encoder: Encoder) throws {
|
||||||
@ -894,6 +910,14 @@ struct ChatListFiltersState: Codable, Equatable {
|
|||||||
|
|
||||||
try container.encode(self.filters, forKey: "filters")
|
try container.encode(self.filters, forKey: "filters")
|
||||||
try container.encodeIfPresent(self.remoteFilters, forKey: "remoteFilters")
|
try container.encodeIfPresent(self.remoteFilters, forKey: "remoteFilters")
|
||||||
|
try container.encode(self.updates, forKey: "updates")
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func normalize() {
|
||||||
|
if self.updates.isEmpty {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.updates.removeAll(where: { update in !self.filters.contains(where: { $0.id == update.folderId }) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -918,6 +942,9 @@ func _internal_updateChatListFiltersInteractively(postbox: Postbox, _ f: @escapi
|
|||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
}
|
}
|
||||||
updated = updatedFilters
|
updated = updatedFilters
|
||||||
|
|
||||||
|
state.normalize()
|
||||||
|
|
||||||
return PreferencesEntry(state)
|
return PreferencesEntry(state)
|
||||||
})
|
})
|
||||||
if hasUpdates {
|
if hasUpdates {
|
||||||
@ -936,6 +963,7 @@ func _internal_updateChatListFiltersInteractively(transaction: Transaction, _ f:
|
|||||||
state.filters = updatedFilters
|
state.filters = updatedFilters
|
||||||
hasUpdates = true
|
hasUpdates = true
|
||||||
}
|
}
|
||||||
|
state.normalize()
|
||||||
return PreferencesEntry(state)
|
return PreferencesEntry(state)
|
||||||
})
|
})
|
||||||
if hasUpdates {
|
if hasUpdates {
|
||||||
@ -943,7 +971,6 @@ func _internal_updateChatListFiltersInteractively(transaction: Transaction, _ f:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func _internal_updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> {
|
func _internal_updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> {
|
||||||
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|
||||||
|> map { preferences -> [ChatListFilter] in
|
|> map { preferences -> [ChatListFilter] in
|
||||||
@ -953,6 +980,15 @@ func _internal_updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilte
|
|||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _internal_updatedChatListFiltersState(postbox: Postbox) -> Signal<ChatListFiltersState, NoError> {
|
||||||
|
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|
||||||
|
|> map { preferences -> ChatListFiltersState in
|
||||||
|
let filtersState = preferences.values[PreferencesKeys.chatListFilters]?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default
|
||||||
|
return filtersState
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
}
|
||||||
|
|
||||||
func _internal_updatedChatListFiltersInfo(postbox: Postbox) -> Signal<(filters: [ChatListFilter], synchronized: Bool), NoError> {
|
func _internal_updatedChatListFiltersInfo(postbox: Postbox) -> Signal<(filters: [ChatListFilter], synchronized: Bool), NoError> {
|
||||||
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|
||||||
|> map { preferences -> (filters: [ChatListFilter], synchronized: Bool) in
|
|> map { preferences -> (filters: [ChatListFilter], synchronized: Bool) in
|
||||||
@ -982,11 +1018,17 @@ func _internal_currentChatListFilters(transaction: Transaction) -> [ChatListFilt
|
|||||||
return settings.filters
|
return settings.filters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _internal_currentChatListFiltersState(transaction: Transaction) -> ChatListFiltersState {
|
||||||
|
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters)?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
func updateChatListFiltersState(transaction: Transaction, _ f: (ChatListFiltersState) -> ChatListFiltersState) -> ChatListFiltersState {
|
func updateChatListFiltersState(transaction: Transaction, _ f: (ChatListFiltersState) -> ChatListFiltersState) -> ChatListFiltersState {
|
||||||
var result: ChatListFiltersState?
|
var result: ChatListFiltersState?
|
||||||
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
|
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
|
||||||
let settings = entry?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default
|
let settings = entry?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default
|
||||||
let updated = f(settings)
|
var updated = f(settings)
|
||||||
|
updated.normalize()
|
||||||
result = updated
|
result = updated
|
||||||
return PreferencesEntry(updated)
|
return PreferencesEntry(updated)
|
||||||
})
|
})
|
||||||
|
@ -60,7 +60,7 @@ func _internal_exportChatFolder(account: Account, filterId: Int32, title: String
|
|||||||
|> mapToSignal { inputPeers -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
|
|> mapToSignal { inputPeers -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
|
||||||
return account.network.request(Api.functions.communities.exportCommunityInvite(community: .inputCommunityDialogFilter(filterId: filterId), title: title, peers: inputPeers))
|
return account.network.request(Api.functions.communities.exportCommunityInvite(community: .inputCommunityDialogFilter(filterId: filterId), title: title, peers: inputPeers))
|
||||||
|> `catch` { error -> Signal<Api.communities.ExportedCommunityInvite, ExportChatFolderError> in
|
|> `catch` { error -> Signal<Api.communities.ExportedCommunityInvite, ExportChatFolderError> in
|
||||||
if error.errorDescription == "INVITES_TOO_MUCH" {
|
if error.errorDescription == "INVITES_TOO_MUCH" || error.errorDescription == "FILTERS_TOO_MUCH" {
|
||||||
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
|
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
|
||||||
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
|
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
|
||||||
}
|
}
|
||||||
@ -69,10 +69,18 @@ func _internal_exportChatFolder(account: Account, filterId: Int32, title: String
|
|||||||
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
|
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
|
||||||
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
|
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
|
||||||
|
|
||||||
if isPremium {
|
if error.errorDescription == "FILTERS_TOO_MUCH" {
|
||||||
return .fail(.limitExceeded(limit: userPremiumLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
|
if isPremium {
|
||||||
|
return .fail(.limitExceeded(limit: userPremiumLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
|
||||||
|
} else {
|
||||||
|
return .fail(.limitExceeded(limit: userDefaultLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return .fail(.limitExceeded(limit: userDefaultLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
|
if isPremium {
|
||||||
|
return .fail(.limitExceeded(limit: userPremiumLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
|
||||||
|
} else {
|
||||||
|
return .fail(.limitExceeded(limit: userDefaultLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -246,7 +254,9 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
|> mapToSignal { result -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> in
|
|> mapToSignal { result -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> in
|
||||||
return account.postbox.transaction { transaction -> ChatFolderLinkContents in
|
return account.postbox.transaction { transaction -> ChatFolderLinkContents in
|
||||||
switch result {
|
switch result {
|
||||||
case let .communityInvite(title, peers, chats, users):
|
case let .communityInvite(_, title, emoticon, peers, chats, users):
|
||||||
|
let _ = emoticon
|
||||||
|
|
||||||
var allPeers: [Peer] = []
|
var allPeers: [Peer] = []
|
||||||
var peerPresences: [PeerId: Api.User] = [:]
|
var peerPresences: [PeerId: Api.User] = [:]
|
||||||
|
|
||||||
@ -321,7 +331,7 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
if let peerValue = transaction.getPeer(peer.peerId) {
|
if let peerValue = transaction.getPeer(peer.peerId) {
|
||||||
resultPeers.append(EnginePeer(peerValue))
|
resultPeers.append(EnginePeer(peerValue))
|
||||||
|
|
||||||
if transaction.getPeerChatListIndex(peer.peerId) != nil {
|
if currentFilterPeers.contains(where: { $0 == peer.peerId }) && transaction.getPeerChatListIndex(peer.peerId) != nil {
|
||||||
alreadyMemberPeerIds.insert(peer.peerId)
|
alreadyMemberPeerIds.insert(peer.peerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -407,74 +417,148 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
|
|||||||
public final class ChatFolderUpdates: Equatable {
|
public final class ChatFolderUpdates: Equatable {
|
||||||
fileprivate let folderId: Int32
|
fileprivate let folderId: Int32
|
||||||
fileprivate let title: String
|
fileprivate let title: String
|
||||||
fileprivate let missingPeers: [Api.Peer]
|
fileprivate let missingPeers: [EnginePeer]
|
||||||
fileprivate let chats: [Api.Chat]
|
|
||||||
fileprivate let users: [Api.User]
|
|
||||||
|
|
||||||
public var availableChatsToJoin: Int {
|
public var availableChatsToJoin: Int {
|
||||||
return self.missingPeers.count
|
return self.missingPeers.count
|
||||||
}
|
}
|
||||||
|
|
||||||
public var chatFolderLinkContents: ChatFolderLinkContents {
|
public var chatFolderLinkContents: ChatFolderLinkContents {
|
||||||
var peers: [EnginePeer] = []
|
return ChatFolderLinkContents(localFilterId: self.folderId, title: self.title, peers: self.missingPeers, alreadyMemberPeerIds: Set())
|
||||||
for missingPeer in self.missingPeers {
|
|
||||||
for chat in chats {
|
|
||||||
if chat.peerId == missingPeer.peerId {
|
|
||||||
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
|
||||||
peers.append(EnginePeer(peer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ChatFolderLinkContents(localFilterId: self.folderId, title: self.title, peers: peers, alreadyMemberPeerIds: Set())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate init(
|
fileprivate init(
|
||||||
folderId: Int32,
|
folderId: Int32,
|
||||||
title: String,
|
title: String,
|
||||||
missingPeers: [Api.Peer],
|
missingPeers: [EnginePeer]
|
||||||
chats: [Api.Chat],
|
|
||||||
users: [Api.User]
|
|
||||||
) {
|
) {
|
||||||
self.folderId = folderId
|
self.folderId = folderId
|
||||||
self.title = title
|
self.title = title
|
||||||
self.missingPeers = missingPeers
|
self.missingPeers = missingPeers
|
||||||
self.chats = chats
|
|
||||||
self.users = users
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func ==(lhs: ChatFolderUpdates, rhs: ChatFolderUpdates) -> Bool {
|
public static func ==(lhs: ChatFolderUpdates, rhs: ChatFolderUpdates) -> Bool {
|
||||||
if lhs.folderId != rhs.folderId {
|
if lhs.folderId != rhs.folderId {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.missingPeers.map(\.peerId) != rhs.missingPeers.map(\.peerId) {
|
if lhs.missingPeers.map(\.id) != rhs.missingPeers.map(\.id) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_getChatFolderUpdates(account: Account, folderId: Int32) -> Signal<ChatFolderUpdates?, NoError> {
|
func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> Signal<Never, NoError> {
|
||||||
return account.network.request(Api.functions.communities.getCommunityUpdates(community: .inputCommunityDialogFilter(filterId: folderId)))
|
return account.postbox.transaction { transaction -> ChatListFiltersState in
|
||||||
|> map(Optional.init)
|
return _internal_currentChatListFiltersState(transaction: transaction)
|
||||||
|> `catch` { _ -> Signal<Api.communities.CommunityUpdates?, NoError> in
|
|
||||||
return .single(nil)
|
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<ChatFolderUpdates?, NoError> in
|
|> mapToSignal { state -> Signal<Never, NoError> in
|
||||||
guard let result = result else {
|
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||||
|
if let current = state.updates.first(where: { $0.folderId == folderId }) {
|
||||||
|
let updateInterval: Int32
|
||||||
|
#if DEBUG
|
||||||
|
updateInterval = 5
|
||||||
|
#else
|
||||||
|
updateInterval = 60 * 60
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if current.timestamp + updateInterval >= timestamp {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.network.request(Api.functions.communities.getCommunityUpdates(community: .inputCommunityDialogFilter(filterId: folderId)))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.communities.CommunityUpdates?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
switch result {
|
|> mapToSignal { result -> Signal<Never, NoError> in
|
||||||
case let .communityUpdates(missingPeers, chats, users):
|
guard let result = result else {
|
||||||
return account.postbox.transaction { transaction -> ChatFolderUpdates? in
|
return account.postbox.transaction { transaction -> Void in
|
||||||
for filter in _internal_currentChatListFilters(transaction: transaction) {
|
let _ = updateChatListFiltersState(transaction: transaction, { state in
|
||||||
if case let .filter(id, title, _, _) = filter, id == folderId {
|
var state = state
|
||||||
return ChatFolderUpdates(folderId: folderId, title: title, missingPeers: missingPeers, chats: chats, users: users)
|
|
||||||
}
|
state.updates.removeAll(where: { $0.folderId == folderId })
|
||||||
|
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: []))
|
||||||
|
|
||||||
|
return state
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return nil
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
|
switch result {
|
||||||
|
case let .communityUpdates(missingPeers, chats, users):
|
||||||
|
return account.postbox.transaction { transaction -> Void in
|
||||||
|
var peers: [Peer] = []
|
||||||
|
var peerPresences: [PeerId: Api.User] = [:]
|
||||||
|
|
||||||
|
for user in users {
|
||||||
|
let telegramUser = TelegramUser(user: user)
|
||||||
|
peers.append(telegramUser)
|
||||||
|
peerPresences[telegramUser.id] = user
|
||||||
|
}
|
||||||
|
for chat in chats {
|
||||||
|
if let peer = parseTelegramGroupOrChannel(chat: chat) {
|
||||||
|
peers.append(peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
|
||||||
|
return updated
|
||||||
|
})
|
||||||
|
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
|
||||||
|
|
||||||
|
let _ = updateChatListFiltersState(transaction: transaction, { state in
|
||||||
|
var state = state
|
||||||
|
|
||||||
|
state.updates.removeAll(where: { $0.folderId == folderId })
|
||||||
|
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: missingPeers.map(\.peerId)))
|
||||||
|
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_subscribedChatFolderUpdates(account: Account, folderId: Int32) -> Signal<ChatFolderUpdates?, NoError> {
|
||||||
|
struct InternalData: Equatable {
|
||||||
|
var title: String
|
||||||
|
var peerIds: [EnginePeer.Id]
|
||||||
|
}
|
||||||
|
|
||||||
|
return _internal_updatedChatListFiltersState(postbox: account.postbox)
|
||||||
|
|> map { state -> InternalData? in
|
||||||
|
guard let update = state.updates.first(where: { $0.folderId == folderId }) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let folder = state.filters.first(where: { $0.id == folderId }) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard case let .filter(_, title, _, data) = folder, data.isShared else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
let filteredPeerIds: [PeerId] = update.peerIds.filter { !data.includePeers.peers.contains($0) }
|
||||||
|
return InternalData(title: title, peerIds: filteredPeerIds)
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|> mapToSignal { internalData -> Signal<ChatFolderUpdates?, NoError> in
|
||||||
|
guard let internalData = internalData else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
if internalData.peerIds.isEmpty {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
return account.postbox.transaction { transaction -> ChatFolderUpdates? in
|
||||||
|
var peers: [EnginePeer] = []
|
||||||
|
for peerId in internalData.peerIds {
|
||||||
|
if let peer = transaction.getPeer(peerId) {
|
||||||
|
peers.append(EnginePeer(peer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ChatFolderUpdates(folderId: folderId, title: internalData.title, missingPeers: peers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -530,11 +614,22 @@ func _internal_joinAvailableChatsInFolder(account: Account, updates: ChatFolderU
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _internal_hideChatFolderUpdates(account: Account, folderId: Int32) -> Signal<Never, NoError> {
|
func _internal_hideChatFolderUpdates(account: Account, folderId: Int32) -> Signal<Never, NoError> {
|
||||||
return account.network.request(Api.functions.communities.hideCommunityUpdates(community: .inputCommunityDialogFilter(filterId: folderId)))
|
return account.postbox.transaction { transaction -> Void in
|
||||||
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
let _ = updateChatListFiltersState(transaction: transaction, { state in
|
||||||
return .single(.boolFalse)
|
var state = state
|
||||||
|
|
||||||
|
state.updates.removeAll(where: { $0.folderId == folderId })
|
||||||
|
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|> mapToSignal { _ -> Signal<Never, NoError> in
|
||||||
|
return account.network.request(Api.functions.communities.hideCommunityUpdates(community: .inputCommunityDialogFilter(filterId: folderId)))
|
||||||
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
|
return .single(.boolFalse)
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
}
|
}
|
||||||
|> ignoreValues
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_leaveChatFolder(account: Account, folderId: Int32, removePeerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
|
func _internal_leaveChatFolder(account: Account, folderId: Int32, removePeerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
|
||||||
|
@ -1050,8 +1050,20 @@ public extension TelegramEngine {
|
|||||||
return _internal_joinChatFolderLink(account: self.account, slug: slug, peerIds: peerIds)
|
return _internal_joinChatFolderLink(account: self.account, slug: slug, peerIds: peerIds)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getChatFolderUpdates(folderId: Int32) -> Signal<ChatFolderUpdates?, NoError> {
|
public func pollChatFolderUpdates(folderId: Int32) -> Signal<Never, NoError> {
|
||||||
return _internal_getChatFolderUpdates(account: self.account, folderId: folderId)
|
let signal = _internal_pollChatFolderUpdatesOnce(account: self.account, folderId: folderId)
|
||||||
|
return (
|
||||||
|
signal
|
||||||
|
|> then(
|
||||||
|
Signal<Never, NoError>.complete()
|
||||||
|
|> delay(10.0, queue: .concurrentDefaultQueue())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> restart
|
||||||
|
}
|
||||||
|
|
||||||
|
public func subscribedChatFolderUpdates(folderId: Int32) -> Signal<ChatFolderUpdates?, NoError> {
|
||||||
|
return _internal_subscribedChatFolderUpdates(account: self.account, folderId: folderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func joinAvailableChatsInFolder(updates: ChatFolderUpdates, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
|
public func joinAvailableChatsInFolder(updates: ChatFolderUpdates, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
|
||||||
|
@ -370,7 +370,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
contentHeight += 14.0
|
contentHeight += 14.0
|
||||||
|
|
||||||
var topBadge: String?
|
var topBadge: String?
|
||||||
if !allChatsAdded, let linkContents = component.linkContents, linkContents.localFilterId != nil {
|
if case .remove = component.subject {
|
||||||
|
} else if !allChatsAdded, let linkContents = component.linkContents, linkContents.localFilterId != nil {
|
||||||
topBadge = "+\(linkContents.peers.count)"
|
topBadge = "+\(linkContents.peers.count)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,6 +266,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||||||
}, openPasswordSetup: {
|
}, openPasswordSetup: {
|
||||||
}, openPremiumIntro: {
|
}, openPremiumIntro: {
|
||||||
}, openChatFolderUpdates: {
|
}, openChatFolderUpdates: {
|
||||||
|
}, hideChatFolderUpdates: {
|
||||||
})
|
})
|
||||||
interaction.searchTextHighightState = searchQuery
|
interaction.searchTextHighightState = searchQuery
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
Loading…
x
Reference in New Issue
Block a user