Update folders

This commit is contained in:
Ali 2023-03-28 00:14:36 +04:00
parent c40b9abc69
commit 8d79979422
18 changed files with 303 additions and 97 deletions

View File

@ -9092,3 +9092,8 @@ Sorry for the inconvenience.";
"StickerPacksSettings.SuggestAnimatedEmojiInfo" = "Each time you enter an emoji you can replace it with an animated emoji.";
"DialogList.DeleteBotClearHistory" = "Clear Chat History";
"ChatList.ChatFolderUpdateCount_1" = "1 new chat";
"ChatList.ChatFolderUpdateCount_any" = "%d new chats";
"ChatList.ChatFolderUpdateHintTitle" = "You can join %@";
"ChatList.ChatFolderUpdateHintText" = "Tap here to view them";

View File

@ -89,6 +89,7 @@ swift_library(
"//submodules/AvatarNode:AvatarNode",
"//submodules/AvatarVideoNode:AvatarVideoNode",
"//submodules/InviteLinksUI",
"//submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen",
],
visibility = [
"//visibility:public",

View File

@ -2705,50 +2705,78 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
private func askForFilterRemoval(id: Int32) {
let actionSheet = ActionSheetController(presentationData: self.presentationData)
let apply: () -> Void = { [weak self] in
guard let strongSelf = self else {
return
}
let commit: () -> Void = {
guard let strongSelf = self else {
return
}
if strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter?.id == id {
if strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.currentState.editing {
strongSelf.donePressed()
}
}
let _ = (strongSelf.context.engine.peers.updateChatListFiltersInteractively { filters in
return filters.filter({ $0.id != id })
}).start()
}
if strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter?.id == id {
strongSelf.chatListDisplayNode.mainContainerNode.switchToFilter(id: .all, completion: {
commit()
})
} else {
commit()
}
}
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: self.presentationData.strings.ChatList_RemoveFolderConfirmation),
ActionSheetButtonItem(title: self.presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
guard let strongSelf = self else {
return
}
let commit: () -> Void = {
guard let strongSelf = self else {
return
}
if strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter?.id == id {
if strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.currentState.editing {
strongSelf.donePressed()
}
}
let _ = (strongSelf.context.engine.peers.updateChatListFiltersInteractively { filters in
return filters.filter({ $0.id != id })
}).start()
}
if strongSelf.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter?.id == id {
strongSelf.chatListDisplayNode.mainContainerNode.switchToFilter(id: .all, completion: {
commit()
let _ = (context.engine.peers.currentChatListFilters()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] filters in
guard let self else {
return
}
guard let filter = filters.first(where: { $0.id == id }) else {
return
}
if case let .filter(_, _, _, data) = filter, data.isShared {
let presentationData = self.presentationData
//TODO:localize
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Folder", text: "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.", actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
apply()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
]), in: .window(.root))
} else {
let actionSheet = ActionSheetController(presentationData: self.presentationData)
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: self.presentationData.strings.ChatList_RemoveFolderConfirmation),
ActionSheetButtonItem(title: self.presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
apply()
})
} else {
commit()
}
})
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
self.present(actionSheet, in: .window(.root))
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
self.present(actionSheet, in: .window(.root))
}
})
}
public private(set) var isSearchActive: Bool = false

View File

@ -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
}, 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()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {})
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
interaction.isInlineMode = isInlineMode
let items = (0 ..< 2).map { _ -> ChatListItem in

View File

@ -380,32 +380,59 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
return state
}
}, removePreset: { id in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.ChatList_RemoveFolderConfirmation),
ActionSheetButtonItem(title: presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
var filters = filters
if let index = filters.firstIndex(where: { $0.id == id }) {
filters.remove(at: index)
let _ = (context.engine.peers.currentChatListFilters()
|> take(1)
|> deliverOnMainQueue).start(next: { filters in
guard let filter = filters.first(where: { $0.id == id }) else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if case let .filter(_, _, _, data) = filter, data.isShared {
//TODO:localize
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Folder", text: "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.", actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
var filters = filters
if let index = filters.firstIndex(where: { $0.id == id }) {
filters.remove(at: index)
}
return filters
}
return filters
}
|> deliverOnMainQueue).start()
})
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
presentControllerImpl?(actionSheet)
|> deliverOnMainQueue).start()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
]))
} else {
let actionSheet = ActionSheetController(presentationData: presentationData)
actionSheet.setItemGroups([
ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.ChatList_RemoveFolderConfirmation),
ActionSheetButtonItem(title: presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
var filters = filters
if let index = filters.firstIndex(where: { $0.id == id }) {
filters.remove(at: index)
}
return filters
}
|> deliverOnMainQueue).start()
})
]),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
presentControllerImpl?(actionSheet)
}
})
})
let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState])

View File

@ -2164,6 +2164,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}, openStorageManagement: {
}, openPasswordSetup: {
}, openPremiumIntro: {
}, openChatFolderUpdates: {
})
chatListInteraction.isSearchMode = true
@ -3397,7 +3398,7 @@ 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
}, 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()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {})
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
var isInlineMode = false
if case .topics = key {
isInlineMode = false

View File

@ -17,6 +17,7 @@ import PremiumUI
import AnimationCache
import MultiAnimationRenderer
import Postbox
import ChatFolderLinkPreviewScreen
public enum ChatListNodeMode {
case chatList
@ -95,6 +96,7 @@ public final class ChatListNodeInteraction {
let openStorageManagement: () -> Void
let openPasswordSetup: () -> Void
let openPremiumIntro: () -> Void
let openChatFolderUpdates: () -> Void
public var searchTextHighightState: String?
var highlightedChatLocation: ChatListHighlightedLocation?
@ -139,7 +141,8 @@ public final class ChatListNodeInteraction {
openForumThread: @escaping (EnginePeer.Id, Int64) -> Void,
openStorageManagement: @escaping () -> Void,
openPasswordSetup: @escaping () -> Void,
openPremiumIntro: @escaping () -> Void
openPremiumIntro: @escaping () -> Void,
openChatFolderUpdates: @escaping () -> Void
) {
self.activateSearch = activateSearch
self.peerSelected = peerSelected
@ -172,6 +175,7 @@ public final class ChatListNodeInteraction {
self.openStorageManagement = openStorageManagement
self.openPasswordSetup = openPasswordSetup
self.openPremiumIntro = openPremiumIntro
self.openChatFolderUpdates = openChatFolderUpdates
}
}
@ -620,6 +624,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction?.openPasswordSetup()
case .premiumUpgrade, .premiumAnnualDiscount:
nodeInteraction?.openPremiumIntro()
case .chatFolderUpdates:
nodeInteraction?.openChatFolderUpdates()
}
}), directionHint: entry.directionHint)
}
@ -873,6 +879,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
nodeInteraction?.openPasswordSetup()
case .premiumUpgrade, .premiumAnnualDiscount:
nodeInteraction?.openPremiumIntro()
case .chatFolderUpdates:
nodeInteraction?.openChatFolderUpdates()
}
}), directionHint: entry.directionHint)
case .HeaderEntry:
@ -1068,6 +1076,9 @@ public final class ChatListNode: ListView {
let hideArhiveIntro = ValuePromise<Bool>(false, ignoreRepeated: true)
private let chatFolderUpdates = Promise<ChatFolderUpdates?>(nil)
private var pollFilterUpdatesDisposable: 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) {
self.context = context
self.location = location
@ -1389,6 +1400,19 @@ public final class ChatListNode: ListView {
}
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads)
self.push?(controller)
}, openChatFolderUpdates: { [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
}
self.push?(ChatFolderLinkPreviewScreen(context: self.context, subject: .updates(result), contents: result.chatFolderLinkContents))
})
})
nodeInteraction.isInlineMode = isInlineMode
@ -1614,15 +1638,18 @@ public final class ChatListNode: ListView {
suggestedChatListNotice,
savedMessagesPeer,
chatListViewUpdate,
self.chatFolderUpdates.get() |> distinctUntilChanged,
self.statePromise.get()
)
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, state) -> Signal<ChatListNodeListViewTransition, NoError> in
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, chatFolderUpdates, state) -> Signal<ChatListNodeListViewTransition, NoError> in
let (update, filter) = updateAndFilter
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
let notice: ChatListNotice?
if let suggestedChatListNotice {
if let chatFolderUpdates, chatFolderUpdates.availableChatsToJoin != 0 {
notice = .chatFolderUpdates(count: chatFolderUpdates.availableChatsToJoin)
} else if let suggestedChatListNotice {
notice = suggestedChatListNotice
} else if let storageInfo {
notice = .clearStorage(sizeFraction: storageInfo)
@ -2551,6 +2578,7 @@ public final class ChatListNode: ListView {
}
}
self.pollFilterUpdates(shouldDelay: false)
self.resetFilter()
let selectionRecognizer = ChatHistoryListSelectionRecognizer(target: self, action: #selector(self.selectionPanGesture(_:)))
@ -2567,6 +2595,7 @@ public final class ChatListNode: ListView {
self.chatListDisposable.dispose()
self.activityStatusesDisposable?.dispose()
self.updatedFilterDisposable.dispose()
self.pollFilterUpdatesDisposable?.dispose()
}
func updateFilter(_ filter: ChatListFilter?) {
@ -2576,6 +2605,20 @@ public final class ChatListNode: ListView {
}
}
private func pollFilterUpdates(shouldDelay: Bool) {
guard let chatListFilter, case let .filter(id, _, _, data) = chatListFilter, data.isShared else {
return
}
self.pollFilterUpdatesDisposable = (context.engine.peers.getChatFolderUpdates(folderId: id)
|> delay(shouldDelay ? 5.0 : 0.0, queue: .mainQueue())).start(next: { [weak self] result in
guard let self else {
return
}
self.chatFolderUpdates.set(.single(result))
self.pollFilterUpdates(shouldDelay: true)
})
}
private func resetFilter() {
if let chatListFilter = self.chatListFilter {
self.updatedFilterDisposable.set((self.context.engine.peers.updatedChatListFilters()

View File

@ -51,6 +51,7 @@ enum ChatListNotice: Equatable {
case setupPassword
case premiumUpgrade(discount: Int32)
case premiumAnnualDiscount(discount: Int32)
case chatFolderUpdates(count: Int)
}
enum ChatListNodeEntry: Comparable, Identifiable {

View File

@ -160,6 +160,15 @@ class ChatListStorageInfoItemNode: ListViewItemNode {
titleString = titleStringValue
textString = NSAttributedString(string: item.strings.ChatList_PremiumAnnualDiscountText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
case let .chatFolderUpdates(count):
let rawTitleString = item.strings.ChatList_ChatFolderUpdateHintTitle(item.strings.ChatList_ChatFolderUpdateCount(Int32(count)))
let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString.string, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor))
if let range = rawTitleString.ranges.first {
titleStringValue.addAttribute(.foregroundColor, value: item.theme.rootController.navigationBar.accentTextColor, range: range.range)
}
titleString = titleStringValue
textString = NSAttributedString(string: item.strings.ChatList_ChatFolderUpdateHintText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
}
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))

View File

@ -94,6 +94,7 @@ public final class HashtagSearchController: TelegramBaseController {
}, openStorageManagement: {
}, openPasswordSetup: {
}, openPremiumIntro: {
}, openChatFolderUpdates: {
})
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)

View File

@ -222,7 +222,7 @@ 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
}, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {})
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
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)

View File

@ -843,7 +843,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
}, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()
}, present: { _ in
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {})
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
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(

View File

@ -367,7 +367,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
}, activateChatPreview: { _, _, _, gesture, _ in
gesture?.cancel()
}, present: { _ in
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {} )
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
func makeChatListItem(
peer: EnginePeer,

View File

@ -346,7 +346,7 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
|> mapToSignal { inputPeers -> Signal<Never, JoinChatFolderLinkError> in
return account.network.request(Api.functions.communities.joinCommunityInvite(slug: slug, peers: inputPeers))
|> mapError { error -> JoinChatFolderLinkError in
if error.errorDescription == "DIALOG_FILTERS_TOO_MUCH" {
if error.errorDescription.hasPrefix("DIALOG_FILTERS_TOO_MUCH") {
return .limitExceeded
} else {
return .generic
@ -360,7 +360,9 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
}
}
public final class ChatFolderUpdates {
public final class ChatFolderUpdates: Equatable {
fileprivate let folderId: Int32
fileprivate let title: String
fileprivate let missingPeers: [Api.Peer]
fileprivate let chats: [Api.Chat]
fileprivate let users: [Api.User]
@ -369,15 +371,44 @@ public final class ChatFolderUpdates {
return self.missingPeers.count
}
public var chatFolderLinkContents: ChatFolderLinkContents {
var peers: [EnginePeer] = []
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(
folderId: Int32,
title: String,
missingPeers: [Api.Peer],
chats: [Api.Chat],
users: [Api.User]
) {
self.folderId = folderId
self.title = title
self.missingPeers = missingPeers
self.chats = chats
self.users = users
}
public static func ==(lhs: ChatFolderUpdates, rhs: ChatFolderUpdates) -> Bool {
if lhs.folderId != rhs.folderId {
return false
}
if lhs.missingPeers.map(\.peerId) != rhs.missingPeers.map(\.peerId) {
return false
}
return true
}
}
func _internal_getChatFolderUpdates(account: Account, folderId: Int32) -> Signal<ChatFolderUpdates?, NoError> {
@ -392,18 +423,25 @@ func _internal_getChatFolderUpdates(account: Account, folderId: Int32) -> Signal
}
switch result {
case let .communityUpdates(missingPeers, chats, users):
return .single(ChatFolderUpdates(missingPeers: missingPeers, chats: chats, users: users))
return account.postbox.transaction { transaction -> ChatFolderUpdates? in
for filter in _internal_currentChatListFilters(transaction: transaction) {
if case let .filter(id, title, _, _) = filter, id == folderId {
return ChatFolderUpdates(folderId: folderId, title: title, missingPeers: missingPeers, chats: chats, users: users)
}
}
return nil
}
}
}
}
func _internal_joinAvailableChatsInFolder(account: Account, folderId: Int32, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
func _internal_joinAvailableChatsInFolder(account: Account, updates: ChatFolderUpdates, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
return account.postbox.transaction { transaction -> [Api.InputPeer] in
return peerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer)
}
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { inputPeers -> Signal<Never, JoinChatFolderLinkError> in
return account.network.request(Api.functions.communities.joinCommunityUpdates(community: .inputCommunityDialogFilter(filterId: folderId), peers: inputPeers))
return account.network.request(Api.functions.communities.joinCommunityUpdates(community: .inputCommunityDialogFilter(filterId: updates.folderId), peers: inputPeers))
|> mapError { error -> JoinChatFolderLinkError in
if error.errorDescription == "DIALOG_FILTERS_TOO_MUCH" {
return .limitExceeded

View File

@ -1054,8 +1054,8 @@ public extension TelegramEngine {
return _internal_getChatFolderUpdates(account: self.account, folderId: folderId)
}
public func joinAvailableChatsInFolder(folderId: Int32, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
return _internal_joinAvailableChatsInFolder(account: self.account, folderId: folderId, peerIds: peerIds)
public func joinAvailableChatsInFolder(updates: ChatFolderUpdates, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
return _internal_joinAvailableChatsInFolder(account: self.account, updates: updates, peerIds: peerIds)
}
public func hideChatFolderUpdates(folderId: Int32) -> Signal<Never, NoError> {

View File

@ -21,16 +21,16 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let slug: String
let subject: ChatFolderLinkPreviewScreen.Subject
let linkContents: ChatFolderLinkContents?
init(
context: AccountContext,
slug: String,
subject: ChatFolderLinkPreviewScreen.Subject,
linkContents: ChatFolderLinkContents?
) {
self.context = context
self.slug = slug
self.subject = subject
self.linkContents = linkContents
}
@ -38,7 +38,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
if lhs.context !== rhs.context {
return false
}
if lhs.slug != rhs.slug {
if lhs.subject != rhs.subject {
return false
}
if lhs.linkContents !== rhs.linkContents {
@ -660,7 +660,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
if let _ = component.linkContents {
if self.joinDisposable == nil, !self.selectedItems.isEmpty {
self.joinDisposable = (component.context.engine.peers.joinChatFolderLink(slug: component.slug, peerIds: Array(self.selectedItems))
let joinSignal: Signal<Never, JoinChatFolderLinkError>
switch component.subject {
case let .slug(slug):
joinSignal = component.context.engine.peers.joinChatFolderLink(slug: slug, peerIds: Array(self.selectedItems))
case let .updates(updates):
joinSignal = component.context.engine.peers.joinAvailableChatsInFolder(updates: updates, peerIds: Array(self.selectedItems))
}
self.joinDisposable = (joinSignal
|> deliverOnMainQueue).start(error: { [weak self] error in
guard let self, let component = self.component, let controller = self.environment?.controller() else {
return
@ -782,29 +790,33 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
}
public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer {
public enum Subject: Equatable {
case slug(String)
case updates(ChatFolderUpdates)
}
private let context: AccountContext
private var linkContents: ChatFolderLinkContents?
private var linkContentsDisposable: Disposable?
private var isDismissed: Bool = false
public init(context: AccountContext, slug: String) {
public init(context: AccountContext, subject: Subject, contents: ChatFolderLinkContents) {
self.context = context
super.init(context: context, component: ChatFolderLinkPreviewScreenComponent(context: context, slug: slug, linkContents: nil), navigationBarAppearance: .none)
super.init(context: context, component: ChatFolderLinkPreviewScreenComponent(context: context, subject: subject, linkContents: contents), navigationBarAppearance: .none)
self.statusBar.statusBarStyle = .Ignore
self.navigationPresentation = .flatModal
self.blocksBackgroundWhenInOverlay = true
self.automaticallyControlPresentationContextLayout = false
self.linkContentsDisposable = (context.engine.peers.checkChatFolderLink(slug: slug)
/*self.linkContentsDisposable = (context.engine.peers.checkChatFolderLink(subject: subject)
|> delay(0.2, queue: .mainQueue())
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else {
return
}
self.linkContents = result
self.updateComponent(component: AnyComponent(ChatFolderLinkPreviewScreenComponent(context: context, slug: slug, linkContents: result)), transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)).withUserData(ChatFolderLinkPreviewScreenComponent.AnimationHint()))
self.updateComponent(component: AnyComponent(ChatFolderLinkPreviewScreenComponent(context: context, subject: subject, linkContents: result)), transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)).withUserData(ChatFolderLinkPreviewScreenComponent.AnimationHint()))
}, error: { [weak self] _ in
guard let self else {
return
@ -812,9 +824,7 @@ public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "The folder link has expired."), elevatedLayout: false, action: { _ in true }), in: .window(.root))
self.dismiss()
})
self.automaticallyControlPresentationContextLayout = false
})*/
}
required public init(coder aDecoder: NSCoder) {

View File

@ -265,6 +265,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
}, openStorageManagement: {
}, openPasswordSetup: {
}, openPremiumIntro: {
}, openChatFolderUpdates: {
})
interaction.searchTextHighightState = searchQuery
self.interaction = interaction

View File

@ -753,7 +753,48 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}
case let .chatFolder(slug):
if let navigationController = navigationController {
navigationController.pushViewController(ChatFolderLinkPreviewScreen(context: context, slug: slug))
let signal = context.engine.peers.checkChatFolderLink(slug: slug)
var cancelImpl: (() -> Void)?
let progressSignal = Signal<Never, NoError> { subscriber in
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
cancelImpl?()
}))
present(controller, nil)
return ActionDisposable { [weak controller] in
Queue.mainQueue().async() {
controller?.dismiss()
}
}
}
|> runOn(Queue.mainQueue())
|> delay(0.35, queue: Queue.mainQueue())
let disposable = MetaDisposable()
let progressDisposable = progressSignal.start()
cancelImpl = {
disposable.set(nil)
}
disposable.set((signal
|> afterDisposed {
Queue.mainQueue().async {
progressDisposable.dispose()
}
}
|> deliverOnMainQueue).start(next: { [weak navigationController] result in
guard let navigationController else {
return
}
navigationController.pushViewController(ChatFolderLinkPreviewScreen(context: context, subject: .slug(slug), contents: result))
}, error: { error in
let errorText: String
switch error {
case .generic:
errorText = "The folder link has expired."
}
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}))
dismissInput()
}
}
}