mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
a271139ccc
@ -9102,3 +9102,16 @@ Sorry for the inconvenience.";
|
|||||||
|
|
||||||
"Wallpaper.ApplyForAll" = "Apply For All Chats";
|
"Wallpaper.ApplyForAll" = "Apply For All Chats";
|
||||||
"Wallpaper.ApplyForChat" = "Apply For This Chat";
|
"Wallpaper.ApplyForChat" = "Apply For This Chat";
|
||||||
|
|
||||||
|
"ChatList.ChatFolderUpdateCount_1" = "1 new chat";
|
||||||
|
"ChatList.ChatFolderUpdateCount_any" = "%d new chats";
|
||||||
|
"ChatList.ChatFolderUpdateHintTitle" = "You can join %@";
|
||||||
|
"ChatList.ChatFolderUpdateHintText" = "Tap here to view them";
|
||||||
|
|
||||||
|
"Premium.MaxSharedFolderMembershipText" = "You can only add **%1$@** shareable folders. Upgrade to **Telegram Premium** to increase this limit up to **%2$@**.";
|
||||||
|
"Premium.MaxSharedFolderMembershipNoPremiumText" = "You can only add **%1$@** shareable folders. We are working to let you increase this limit in the future.";
|
||||||
|
"Premium.MaxSharedFolderMembershipFinalText" = "Sorry, you can only add **%1$@** shareable folders.";
|
||||||
|
|
||||||
|
"Premium.MaxSharedFolderLinksText" = "You can only create **%1$@** invite links. Upgrade to **Telegram Premium** to increase the links limit to **%2$@**.";
|
||||||
|
"Premium.MaxSharedFolderLinksNoPremiumText" = "You can only create **%1$@** invite links. We are working to let you increase this limit in the future.";
|
||||||
|
"Premium.MaxSharedFolderLinksFinalText" = "Sorry, you can only create **%1$@** invite links";
|
||||||
|
@ -896,6 +896,8 @@ public enum PremiumLimitSubject {
|
|||||||
case pins
|
case pins
|
||||||
case files
|
case files
|
||||||
case accounts
|
case accounts
|
||||||
|
case linksPerSharedFolder
|
||||||
|
case membershipInSharedFolders
|
||||||
}
|
}
|
||||||
|
|
||||||
public protocol ComposeController: ViewController {
|
public protocol ComposeController: ViewController {
|
||||||
|
@ -89,6 +89,7 @@ swift_library(
|
|||||||
"//submodules/AvatarNode:AvatarNode",
|
"//submodules/AvatarNode:AvatarNode",
|
||||||
"//submodules/AvatarVideoNode:AvatarVideoNode",
|
"//submodules/AvatarVideoNode:AvatarVideoNode",
|
||||||
"//submodules/InviteLinksUI",
|
"//submodules/InviteLinksUI",
|
||||||
|
"//submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -44,6 +44,7 @@ import ComponentDisplayAdapters
|
|||||||
import ChatListHeaderComponent
|
import ChatListHeaderComponent
|
||||||
import ChatListTitleView
|
import ChatListTitleView
|
||||||
import InviteLinksUI
|
import InviteLinksUI
|
||||||
|
import ChatFolderLinkPreviewScreen
|
||||||
|
|
||||||
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
|
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
|
||||||
if listNode.scroller.isDragging {
|
if listNode.scroller.isDragging {
|
||||||
@ -1544,7 +1545,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
//TODO:localize
|
//TODO:localize
|
||||||
|
|
||||||
for filter in filters {
|
for filter in filters {
|
||||||
if filter.id == filterId, case let .filter(_, _, _, 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, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor)
|
||||||
@ -1553,7 +1554,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.shareFolder(filterId: filterId, data: data)
|
strongSelf.shareFolder(filterId: filterId, data: data, title: title)
|
||||||
})
|
})
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
@ -2699,56 +2700,122 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func shareFolder(filterId: Int32, data: ChatListFilterData) {
|
private func shareFolder(filterId: Int32, data: ChatListFilterData, title: String) {
|
||||||
self.push(folderInviteLinkListController(context: self.context, filterId: filterId, 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
|
||||||
}))
|
}))*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private func askForFilterRemoval(id: Int32) {
|
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([
|
let _ = (self.context.engine.peers.currentChatListFilters()
|
||||||
ActionSheetItemGroup(items: [
|
|> take(1)
|
||||||
ActionSheetTextItem(title: self.presentationData.strings.ChatList_RemoveFolderConfirmation),
|
|> deliverOnMainQueue).start(next: { [weak self] filters in
|
||||||
ActionSheetButtonItem(title: self.presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak self, weak actionSheet] in
|
guard let self else {
|
||||||
actionSheet?.dismissAnimated()
|
return
|
||||||
|
}
|
||||||
guard let strongSelf = self else {
|
guard let filter = filters.first(where: { $0.id == id }) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .filter(_, title, _, data) = filter, data.isShared {
|
||||||
|
let _ = (self.context.engine.data.get(
|
||||||
|
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||||
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let commit: () -> Void = {
|
let presentationData = self.presentationData
|
||||||
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 {
|
//TODO:localize
|
||||||
strongSelf.chatListDisplayNode.mainContainerNode.switchToFilter(id: .all, completion: {
|
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: [
|
||||||
commit()
|
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let previewScreen = ChatFolderLinkPreviewScreen(
|
||||||
|
context: self.context,
|
||||||
|
subject: .remove(folderId: id),
|
||||||
|
contents: ChatFolderLinkContents(
|
||||||
|
localFilterId: id,
|
||||||
|
title: title,
|
||||||
|
peers: peers.compactMap { $0 }.filter { peer in
|
||||||
|
if case .channel = peer {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
alreadyMemberPeerIds: Set()
|
||||||
|
),
|
||||||
|
completion: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter?.id == id {
|
||||||
|
self.chatListDisplayNode.mainContainerNode.switchToFilter(id: .all, completion: {
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.push(previewScreen)
|
||||||
|
}),
|
||||||
|
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
|
||||||
})
|
})
|
||||||
} else {
|
]), in: .window(.root))
|
||||||
commit()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
]),
|
} else {
|
||||||
ActionSheetItemGroup(items: [
|
let actionSheet = ActionSheetController(presentationData: self.presentationData)
|
||||||
ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet.setItemGroups([
|
||||||
})
|
ActionSheetItemGroup(items: [
|
||||||
])
|
ActionSheetTextItem(title: self.presentationData.strings.ChatList_RemoveFolderConfirmation),
|
||||||
])
|
ActionSheetButtonItem(title: self.presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in
|
||||||
self.present(actionSheet, in: .window(.root))
|
actionSheet?.dismissAnimated()
|
||||||
|
|
||||||
|
apply()
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
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
|
public private(set) var isSearchActive: Bool = false
|
||||||
|
@ -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: {})
|
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
|
||||||
interaction.isInlineMode = isInlineMode
|
interaction.isInlineMode = isInlineMode
|
||||||
|
|
||||||
let items = (0 ..< 2).map { _ -> ChatListItem in
|
let items = (0 ..< 2).map { _ -> ChatListItem in
|
||||||
|
@ -317,7 +317,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
|||||||
case includeExpand(String)
|
case includeExpand(String)
|
||||||
case excludeExpand(String)
|
case excludeExpand(String)
|
||||||
case inviteLinkHeader
|
case inviteLinkHeader
|
||||||
case inviteLinkCreate
|
case inviteLinkCreate(hasLinks: Bool)
|
||||||
case inviteLink(Int, ExportedChatFolderLink)
|
case inviteLink(Int, ExportedChatFolderLink)
|
||||||
case inviteLinkInfo
|
case inviteLinkInfo
|
||||||
|
|
||||||
@ -518,10 +518,10 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
case .inviteLinkHeader:
|
case .inviteLinkHeader:
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: "INVITE LINK", sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: "INVITE LINK", badge: "NEW", sectionId: self.section)
|
||||||
case .inviteLinkCreate:
|
case let.inviteLinkCreate(hasLinks):
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: "Share Folder with Others", 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()
|
||||||
})
|
})
|
||||||
case let .inviteLink(_, link):
|
case let .inviteLink(_, link):
|
||||||
@ -532,7 +532,7 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
case .inviteLinkInfo:
|
case .inviteLinkInfo:
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
return ItemListTextItem(presentationData: presentationData, text: .markdown("Give vour friends and colleagues access to the entire folder including all of its groups and channels where you have the necessary rights."), sectionId: self.section)
|
return ItemListTextItem(presentationData: presentationData, text: .markdown("Share access to some of this folder's groups and channels with others."), sectionId: self.section)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -575,7 +575,7 @@ private struct ChatListFilterPresetControllerState: Equatable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func chatListFilterPresetControllerEntries(presentationData: PresentationData, isNewFilter: Bool, state: ChatListFilterPresetControllerState, includePeers: [EngineRenderedPeer], excludePeers: [EngineRenderedPeer], isPremium: Bool, limit: Int32, inviteLinks: [ExportedChatFolderLink]?) -> [ChatListFilterPresetEntry] {
|
private func chatListFilterPresetControllerEntries(presentationData: PresentationData, isNewFilter: Bool, currentPreset: ChatListFilter?, state: ChatListFilterPresetControllerState, includePeers: [EngineRenderedPeer], excludePeers: [EngineRenderedPeer], isPremium: Bool, limit: Int32, inviteLinks: [ExportedChatFolderLink]?) -> [ChatListFilterPresetEntry] {
|
||||||
var entries: [ChatListFilterPresetEntry] = []
|
var entries: [ChatListFilterPresetEntry] = []
|
||||||
|
|
||||||
if isNewFilter {
|
if isNewFilter {
|
||||||
@ -614,46 +614,49 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
|
|||||||
|
|
||||||
entries.append(.includePeerInfo(presentationData.strings.ChatListFolder_IncludeSectionInfo))
|
entries.append(.includePeerInfo(presentationData.strings.ChatListFolder_IncludeSectionInfo))
|
||||||
|
|
||||||
entries.append(.excludePeersHeader(presentationData.strings.ChatListFolder_ExcludedSectionHeader))
|
if let currentPreset, let data = currentPreset.data, data.isShared {
|
||||||
entries.append(.addExcludePeer(title: presentationData.strings.ChatListFolder_AddChats))
|
} else {
|
||||||
|
entries.append(.excludePeersHeader(presentationData.strings.ChatListFolder_ExcludedSectionHeader))
|
||||||
var excludeCategoryIndex = 0
|
entries.append(.addExcludePeer(title: presentationData.strings.ChatListFolder_AddChats))
|
||||||
for category in ChatListFilterExcludeCategory.allCases {
|
|
||||||
let isExcluded: Bool
|
var excludeCategoryIndex = 0
|
||||||
switch category {
|
for category in ChatListFilterExcludeCategory.allCases {
|
||||||
case .read:
|
let isExcluded: Bool
|
||||||
isExcluded = state.excludeRead
|
switch category {
|
||||||
case .muted:
|
case .read:
|
||||||
isExcluded = state.excludeMuted
|
isExcluded = state.excludeRead
|
||||||
case .archived:
|
case .muted:
|
||||||
isExcluded = state.excludeArchived
|
isExcluded = state.excludeMuted
|
||||||
|
case .archived:
|
||||||
|
isExcluded = state.excludeArchived
|
||||||
|
}
|
||||||
|
|
||||||
|
if isExcluded {
|
||||||
|
entries.append(.excludeCategory(index: excludeCategoryIndex, category: category, title: category.title(strings: presentationData.strings), isRevealed: state.revealedItemId == .excludeCategory(category)))
|
||||||
|
}
|
||||||
|
excludeCategoryIndex += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if isExcluded {
|
if !excludePeers.isEmpty {
|
||||||
entries.append(.excludeCategory(index: excludeCategoryIndex, category: category, title: category.title(strings: presentationData.strings), isRevealed: state.revealedItemId == .excludeCategory(category)))
|
var count = 0
|
||||||
}
|
for peer in excludePeers {
|
||||||
excludeCategoryIndex += 1
|
entries.append(.excludePeer(index: entries.count, peer: peer, isRevealed: state.revealedItemId == .peer(peer.peerId)))
|
||||||
}
|
count += 1
|
||||||
|
if excludePeers.count >= 7 && count == 5 && !state.expandedSections.contains(.exclude) {
|
||||||
if !excludePeers.isEmpty {
|
break
|
||||||
var count = 0
|
}
|
||||||
for peer in excludePeers {
|
}
|
||||||
entries.append(.excludePeer(index: entries.count, peer: peer, isRevealed: state.revealedItemId == .peer(peer.peerId)))
|
if count < excludePeers.count {
|
||||||
count += 1
|
entries.append(.excludeExpand(presentationData.strings.ChatListFilter_ShowMoreChats(Int32(excludePeers.count - count))))
|
||||||
if excludePeers.count >= 7 && count == 5 && !state.expandedSections.contains(.exclude) {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if count < excludePeers.count {
|
|
||||||
entries.append(.excludeExpand(presentationData.strings.ChatListFilter_ShowMoreChats(Int32(excludePeers.count - count))))
|
entries.append(.excludePeerInfo(presentationData.strings.ChatListFolder_ExcludeSectionInfo))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.excludePeerInfo(presentationData.strings.ChatListFolder_ExcludeSectionInfo))
|
|
||||||
|
|
||||||
if !isNewFilter, let inviteLinks {
|
if !isNewFilter, let inviteLinks {
|
||||||
entries.append(.inviteLinkHeader)
|
entries.append(.inviteLinkHeader)
|
||||||
entries.append(.inviteLinkCreate)
|
entries.append(.inviteLinkCreate(hasLinks: !inviteLinks.isEmpty))
|
||||||
|
|
||||||
var index = 0
|
var index = 0
|
||||||
for link in inviteLinks {
|
for link in inviteLinks {
|
||||||
@ -691,38 +694,7 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
|
|||||||
}
|
}
|
||||||
|
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
let additionalCategories: [ChatListNodeAdditionalCategory] = [
|
var additionalCategories: [ChatListNodeAdditionalCategory] = []
|
||||||
ChatListNodeAdditionalCategory(
|
|
||||||
id: AdditionalCategoryId.contacts.rawValue,
|
|
||||||
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Contact"), color: .white), cornerRadius: 12.0, color: .blue),
|
|
||||||
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Contact"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .blue),
|
|
||||||
title: presentationData.strings.ChatListFolder_CategoryContacts
|
|
||||||
),
|
|
||||||
ChatListNodeAdditionalCategory(
|
|
||||||
id: AdditionalCategoryId.nonContacts.rawValue,
|
|
||||||
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/User"), color: .white), cornerRadius: 12.0, color: .yellow),
|
|
||||||
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/User"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .yellow),
|
|
||||||
title: presentationData.strings.ChatListFolder_CategoryNonContacts
|
|
||||||
),
|
|
||||||
ChatListNodeAdditionalCategory(
|
|
||||||
id: AdditionalCategoryId.groups.rawValue,
|
|
||||||
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Group"), color: .white), cornerRadius: 12.0, color: .green),
|
|
||||||
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Group"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .green),
|
|
||||||
title: presentationData.strings.ChatListFolder_CategoryGroups
|
|
||||||
),
|
|
||||||
ChatListNodeAdditionalCategory(
|
|
||||||
id: AdditionalCategoryId.channels.rawValue,
|
|
||||||
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white), cornerRadius: 12.0, color: .red),
|
|
||||||
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .red),
|
|
||||||
title: presentationData.strings.ChatListFolder_CategoryChannels
|
|
||||||
),
|
|
||||||
ChatListNodeAdditionalCategory(
|
|
||||||
id: AdditionalCategoryId.bots.rawValue,
|
|
||||||
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Bot"), color: .white), cornerRadius: 12.0, color: .violet),
|
|
||||||
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Bot"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .violet),
|
|
||||||
title: presentationData.strings.ChatListFolder_CategoryBots
|
|
||||||
)
|
|
||||||
]
|
|
||||||
var selectedCategories = Set<Int>()
|
var selectedCategories = Set<Int>()
|
||||||
let categoryMapping: [ChatListFilterPeerCategories: AdditionalCategoryId] = [
|
let categoryMapping: [ChatListFilterPeerCategories: AdditionalCategoryId] = [
|
||||||
.contacts: .contacts,
|
.contacts: .contacts,
|
||||||
@ -731,9 +703,46 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
|
|||||||
.channels: .channels,
|
.channels: .channels,
|
||||||
.bots: .bots
|
.bots: .bots
|
||||||
]
|
]
|
||||||
for (category, id) in categoryMapping {
|
|
||||||
if filterData.categories.contains(category) {
|
if let data = filter.data, data.isShared {
|
||||||
selectedCategories.insert(id.rawValue)
|
} else {
|
||||||
|
additionalCategories = [
|
||||||
|
ChatListNodeAdditionalCategory(
|
||||||
|
id: AdditionalCategoryId.contacts.rawValue,
|
||||||
|
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Contact"), color: .white), cornerRadius: 12.0, color: .blue),
|
||||||
|
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Contact"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .blue),
|
||||||
|
title: presentationData.strings.ChatListFolder_CategoryContacts
|
||||||
|
),
|
||||||
|
ChatListNodeAdditionalCategory(
|
||||||
|
id: AdditionalCategoryId.nonContacts.rawValue,
|
||||||
|
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/User"), color: .white), cornerRadius: 12.0, color: .yellow),
|
||||||
|
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/User"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .yellow),
|
||||||
|
title: presentationData.strings.ChatListFolder_CategoryNonContacts
|
||||||
|
),
|
||||||
|
ChatListNodeAdditionalCategory(
|
||||||
|
id: AdditionalCategoryId.groups.rawValue,
|
||||||
|
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Group"), color: .white), cornerRadius: 12.0, color: .green),
|
||||||
|
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Group"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .green),
|
||||||
|
title: presentationData.strings.ChatListFolder_CategoryGroups
|
||||||
|
),
|
||||||
|
ChatListNodeAdditionalCategory(
|
||||||
|
id: AdditionalCategoryId.channels.rawValue,
|
||||||
|
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white), cornerRadius: 12.0, color: .red),
|
||||||
|
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Channel"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .red),
|
||||||
|
title: presentationData.strings.ChatListFolder_CategoryChannels
|
||||||
|
),
|
||||||
|
ChatListNodeAdditionalCategory(
|
||||||
|
id: AdditionalCategoryId.bots.rawValue,
|
||||||
|
icon: generateAvatarImage(size: CGSize(width: 40.0, height: 40.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Bot"), color: .white), cornerRadius: 12.0, color: .violet),
|
||||||
|
smallIcon: generateAvatarImage(size: CGSize(width: 22.0, height: 22.0), icon: generateTintedImage(image: UIImage(bundleImageName: "Chat List/Filters/Bot"), color: .white), iconScale: 0.6, cornerRadius: 6.0, circleCorners: true, color: .violet),
|
||||||
|
title: presentationData.strings.ChatListFolder_CategoryBots
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
for (category, id) in categoryMapping {
|
||||||
|
if filterData.categories.contains(category) {
|
||||||
|
selectedCategories.insert(id.rawValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1059,8 +1068,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
|
|
||||||
let sharedLinks = Promise<[ExportedChatFolderLink]?>(nil)
|
let sharedLinks = Promise<[ExportedChatFolderLink]?>(nil)
|
||||||
if let currentPreset {
|
if let currentPreset {
|
||||||
sharedLinks.set(Signal<[ExportedChatFolderLink]?, NoError>.single(nil) |> then(context.engine.peers.getExportedChatFolderLinks(id: currentPreset.id)
|
sharedLinks.set(Signal<[ExportedChatFolderLink]?, NoError>.single(nil) |> then(context.engine.peers.getExportedChatFolderLinks(id: currentPreset.id)))
|
||||||
|> map(Optional.init)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentPeers = Atomic<[PeerId: EngineRenderedPeer]>(value: [:])
|
let currentPeers = Atomic<[PeerId: EngineRenderedPeer]>(value: [:])
|
||||||
@ -1265,23 +1273,87 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
createLink: {
|
createLink: {
|
||||||
if let currentPreset, let data = currentPreset.data, !data.includePeers.peers.isEmpty {
|
let state = stateValue.with({ $0 })
|
||||||
pushControllerImpl?(folderInviteLinkListController(context: context, filterId: currentPreset.id, allPeerIds: data.includePeers.peers, currentInvitation: nil, linkUpdated: { updatedLink in
|
|
||||||
let _ = (sharedLinks.get() |> take(1) |> deliverOnMainQueue).start(next: { links in
|
if let currentPreset, !state.additionallyIncludePeers.isEmpty {
|
||||||
guard var links else {
|
let _ = (context.engine.data.get(
|
||||||
return
|
EngineDataList(state.additionallyIncludePeers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||||
}
|
)
|
||||||
|
|> deliverOnMainQueue).start(next: { peers in
|
||||||
if let updatedLink {
|
let peers = peers.compactMap({ $0 })
|
||||||
links.insert(updatedLink, at: 0)
|
if peers.allSatisfy({ !canShareLinkToPeer(peer: $0) }) {
|
||||||
sharedLinks.set(.single(links))
|
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
|
}, openLink: { link in
|
||||||
if let currentPreset, let data = currentPreset.data {
|
if let currentPreset, let _ = currentPreset.data {
|
||||||
pushControllerImpl?(folderInviteLinkListController(context: context, filterId: currentPreset.id, allPeerIds: data.includePeers.peers, currentInvitation: link, linkUpdated: { updatedLink in
|
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 updatedLink != link {
|
||||||
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 {
|
||||||
@ -1387,7 +1459,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
|
|||||||
}
|
}
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(currentPreset != nil ? presentationData.strings.ChatListFolder_TitleEdit : presentationData.strings.ChatListFolder_TitleCreate), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(currentPreset != nil ? presentationData.strings.ChatListFolder_TitleEdit : presentationData.strings.ChatListFolder_TitleCreate), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetControllerEntries(presentationData: presentationData, isNewFilter: currentPreset == nil, state: state, includePeers: includePeers, excludePeers: excludePeers, isPremium: isPremium, limit: premiumLimits.maxFolderChatsCount, inviteLinks: sharedLinks), style: .blocks, emptyStateItem: nil, animateChanges: !skipStateAnimation)
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: chatListFilterPresetControllerEntries(presentationData: presentationData, isNewFilter: currentPreset == nil, currentPreset: currentPreset, state: state, includePeers: includePeers, excludePeers: excludePeers, isPremium: isPremium, limit: premiumLimits.maxFolderChatsCount, inviteLinks: sharedLinks), style: .blocks, emptyStateItem: nil, animateChanges: !skipStateAnimation)
|
||||||
skipStateAnimation = false
|
skipStateAnimation = false
|
||||||
|
|
||||||
return (controllerState, (listState, arguments))
|
return (controllerState, (listState, arguments))
|
||||||
|
@ -12,6 +12,7 @@ import ItemListPeerActionItem
|
|||||||
import ChatListFilterSettingsHeaderItem
|
import ChatListFilterSettingsHeaderItem
|
||||||
import PremiumUI
|
import PremiumUI
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
import ChatFolderLinkPreviewScreen
|
||||||
|
|
||||||
private final class ChatListFilterPresetListControllerArguments {
|
private final class ChatListFilterPresetListControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -380,32 +381,75 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}, removePreset: { id in
|
}, removePreset: { id in
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let _ = (context.engine.peers.currentChatListFilters()
|
||||||
let actionSheet = ActionSheetController(presentationData: presentationData)
|
|> take(1)
|
||||||
|
|> deliverOnMainQueue).start(next: { filters in
|
||||||
actionSheet.setItemGroups([
|
guard let filter = filters.first(where: { $0.id == id }) else {
|
||||||
ActionSheetItemGroup(items: [
|
return
|
||||||
ActionSheetTextItem(title: presentationData.strings.ChatList_RemoveFolderConfirmation),
|
}
|
||||||
ActionSheetButtonItem(title: presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
if case let .filter(_, title, _, data) = filter, data.isShared {
|
||||||
|
let _ = (context.engine.data.get(
|
||||||
|
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||||
|
)
|
||||||
|
|> deliverOnMainQueue).start(next: { peers in
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
|
//TODO:localize
|
||||||
var filters = filters
|
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: [
|
||||||
if let index = filters.firstIndex(where: { $0.id == id }) {
|
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
|
||||||
filters.remove(at: index)
|
let previewScreen = ChatFolderLinkPreviewScreen(
|
||||||
}
|
context: context,
|
||||||
return filters
|
subject: .remove(folderId: id),
|
||||||
}
|
contents: ChatFolderLinkContents(
|
||||||
|> deliverOnMainQueue).start()
|
localFilterId: id,
|
||||||
|
title: title,
|
||||||
|
peers: peers.compactMap { $0 }.filter { peer in
|
||||||
|
if case .channel = peer {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
alreadyMemberPeerIds: Set()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
pushControllerImpl?(previewScreen)
|
||||||
|
}),
|
||||||
|
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
|
||||||
|
})
|
||||||
|
]))
|
||||||
})
|
})
|
||||||
]),
|
} else {
|
||||||
ActionSheetItemGroup(items: [
|
let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||||
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
|
||||||
actionSheet?.dismissAnimated()
|
actionSheet.setItemGroups([
|
||||||
})
|
ActionSheetItemGroup(items: [
|
||||||
])
|
ActionSheetTextItem(title: presentationData.strings.ChatList_RemoveFolderConfirmation),
|
||||||
])
|
ActionSheetButtonItem(title: presentationData.strings.ChatList_RemoveFolderAction, color: .destructive, action: { [weak actionSheet] in
|
||||||
presentControllerImpl?(actionSheet)
|
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])
|
let featuredFilters = context.account.postbox.preferencesView(keys: [PreferencesKeys.chatListFiltersFeaturedState])
|
||||||
|
@ -125,6 +125,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
|||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let labelNode: TextNode
|
private let labelNode: TextNode
|
||||||
private let arrowNode: ASImageNode
|
private let arrowNode: ASImageNode
|
||||||
|
private let sharedIconNode: ASImageNode
|
||||||
|
|
||||||
private let activateArea: AccessibilityAreaNode
|
private let activateArea: AccessibilityAreaNode
|
||||||
|
|
||||||
@ -169,6 +170,11 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
|||||||
self.arrowNode.displaysAsynchronously = false
|
self.arrowNode.displaysAsynchronously = false
|
||||||
self.arrowNode.isLayerBacked = true
|
self.arrowNode.isLayerBacked = true
|
||||||
|
|
||||||
|
self.sharedIconNode = ASImageNode()
|
||||||
|
self.sharedIconNode.displayWithoutProcessing = true
|
||||||
|
self.sharedIconNode.displaysAsynchronously = false
|
||||||
|
self.sharedIconNode.isLayerBacked = true
|
||||||
|
|
||||||
self.activateArea = AccessibilityAreaNode()
|
self.activateArea = AccessibilityAreaNode()
|
||||||
|
|
||||||
self.highlightedBackgroundNode = ASDisplayNode()
|
self.highlightedBackgroundNode = ASDisplayNode()
|
||||||
@ -180,6 +186,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
|||||||
self.containerNode.addSubnode(self.titleNode)
|
self.containerNode.addSubnode(self.titleNode)
|
||||||
self.containerNode.addSubnode(self.labelNode)
|
self.containerNode.addSubnode(self.labelNode)
|
||||||
self.containerNode.addSubnode(self.arrowNode)
|
self.containerNode.addSubnode(self.arrowNode)
|
||||||
|
self.containerNode.addSubnode(self.sharedIconNode)
|
||||||
self.addSubnode(self.activateArea)
|
self.addSubnode(self.activateArea)
|
||||||
|
|
||||||
self.activateArea.activate = { [weak self] in
|
self.activateArea.activate = { [weak self] in
|
||||||
@ -199,6 +206,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
|||||||
return { item, params, neighbors in
|
return { item, params, neighbors in
|
||||||
var updatedTheme: PresentationTheme?
|
var updatedTheme: PresentationTheme?
|
||||||
var updateArrowImage: UIImage?
|
var updateArrowImage: UIImage?
|
||||||
|
var updatedSharedIconImage: UIImage?
|
||||||
|
|
||||||
if currentItem?.presentationData.theme !== item.presentationData.theme || currentItem?.isDisabled != item.isDisabled {
|
if currentItem?.presentationData.theme !== item.presentationData.theme || currentItem?.isDisabled != item.isDisabled {
|
||||||
updatedTheme = item.presentationData.theme
|
updatedTheme = item.presentationData.theme
|
||||||
@ -207,6 +215,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
|||||||
} else {
|
} else {
|
||||||
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
|
||||||
}
|
}
|
||||||
|
updatedSharedIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Share"), color: item.presentationData.theme.list.disclosureArrowColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
let peerRevealOptions: [ItemListRevealOption]
|
let peerRevealOptions: [ItemListRevealOption]
|
||||||
@ -379,10 +388,14 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
|||||||
|
|
||||||
transition.updateAlpha(node: strongSelf.labelNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
|
transition.updateAlpha(node: strongSelf.labelNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
|
||||||
transition.updateAlpha(node: strongSelf.arrowNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
|
transition.updateAlpha(node: strongSelf.arrowNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
|
||||||
|
transition.updateAlpha(node: strongSelf.sharedIconNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
|
||||||
|
|
||||||
if let updateArrowImage = updateArrowImage {
|
if let updateArrowImage = updateArrowImage {
|
||||||
strongSelf.arrowNode.image = updateArrowImage
|
strongSelf.arrowNode.image = updateArrowImage
|
||||||
}
|
}
|
||||||
|
if let updatedSharedIconImage {
|
||||||
|
strongSelf.sharedIconNode.image = updatedSharedIconImage
|
||||||
|
}
|
||||||
|
|
||||||
if let arrowImage = strongSelf.arrowNode.image {
|
if let arrowImage = strongSelf.arrowNode.image {
|
||||||
var rightArrowInset = 0.0
|
var rightArrowInset = 0.0
|
||||||
@ -393,6 +406,15 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
|||||||
}
|
}
|
||||||
strongSelf.arrowNode.isHidden = item.isAllChats
|
strongSelf.arrowNode.isHidden = item.isAllChats
|
||||||
|
|
||||||
|
if let sharedIconImage = strongSelf.sharedIconNode.image {
|
||||||
|
strongSelf.sharedIconNode.frame = CGRect(origin: CGPoint(x: strongSelf.arrowNode.frame.minX + 2.0 - sharedIconImage.size.width, y: floorToScreenPixels((layout.contentSize.height - sharedIconImage.size.height) / 2.0) + 1.0), size: sharedIconImage.size)
|
||||||
|
}
|
||||||
|
var isShared = false
|
||||||
|
if case let .filter(_, _, _, data) = item.preset, data.isShared {
|
||||||
|
isShared = true
|
||||||
|
}
|
||||||
|
strongSelf.sharedIconNode.isHidden = !isShared
|
||||||
|
|
||||||
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 0.0), size: CGSize(width: params.width - params.rightInset - 56.0 - (leftInset + revealOffset + editingOffset), height: layout.contentSize.height))
|
strongSelf.activateArea.frame = CGRect(origin: CGPoint(x: leftInset + revealOffset + editingOffset, y: 0.0), size: CGSize(width: params.width - params.rightInset - 56.0 - (leftInset + revealOffset + editingOffset), height: layout.contentSize.height))
|
||||||
|
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: layout.contentSize.height + UIScreenPixel + UIScreenPixel))
|
||||||
@ -483,6 +505,10 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
|
|||||||
var arrowFrame = self.arrowNode.frame
|
var arrowFrame = self.arrowNode.frame
|
||||||
arrowFrame.origin.x = params.width - params.rightInset - 7.0 - arrowFrame.width + revealOffset
|
arrowFrame.origin.x = params.width - params.rightInset - 7.0 - arrowFrame.width + revealOffset
|
||||||
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
|
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
|
||||||
|
|
||||||
|
var sharedIconFrame = self.sharedIconNode.frame
|
||||||
|
sharedIconFrame.origin.x = arrowFrame.minX + 2.0 - sharedIconFrame.width
|
||||||
|
transition.updateFrame(node: self.sharedIconNode, frame: sharedIconFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func revealOptionsInteractivelyOpened() {
|
override func revealOptionsInteractivelyOpened() {
|
||||||
|
@ -2164,6 +2164,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||||||
}, openStorageManagement: {
|
}, openStorageManagement: {
|
||||||
}, openPasswordSetup: {
|
}, openPasswordSetup: {
|
||||||
}, openPremiumIntro: {
|
}, openPremiumIntro: {
|
||||||
|
}, openChatFolderUpdates: {
|
||||||
})
|
})
|
||||||
chatListInteraction.isSearchMode = true
|
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
|
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: {})
|
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
|
||||||
var isInlineMode = false
|
var isInlineMode = false
|
||||||
if case .topics = key {
|
if case .topics = key {
|
||||||
isInlineMode = false
|
isInlineMode = false
|
||||||
|
@ -17,6 +17,7 @@ import PremiumUI
|
|||||||
import AnimationCache
|
import AnimationCache
|
||||||
import MultiAnimationRenderer
|
import MultiAnimationRenderer
|
||||||
import Postbox
|
import Postbox
|
||||||
|
import ChatFolderLinkPreviewScreen
|
||||||
|
|
||||||
public enum ChatListNodeMode {
|
public enum ChatListNodeMode {
|
||||||
case chatList
|
case chatList
|
||||||
@ -95,6 +96,7 @@ public final class ChatListNodeInteraction {
|
|||||||
let openStorageManagement: () -> Void
|
let openStorageManagement: () -> Void
|
||||||
let openPasswordSetup: () -> Void
|
let openPasswordSetup: () -> Void
|
||||||
let openPremiumIntro: () -> Void
|
let openPremiumIntro: () -> Void
|
||||||
|
let openChatFolderUpdates: () -> Void
|
||||||
|
|
||||||
public var searchTextHighightState: String?
|
public var searchTextHighightState: String?
|
||||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||||
@ -139,7 +141,8 @@ public final class ChatListNodeInteraction {
|
|||||||
openForumThread: @escaping (EnginePeer.Id, Int64) -> Void,
|
openForumThread: @escaping (EnginePeer.Id, Int64) -> Void,
|
||||||
openStorageManagement: @escaping () -> Void,
|
openStorageManagement: @escaping () -> Void,
|
||||||
openPasswordSetup: @escaping () -> Void,
|
openPasswordSetup: @escaping () -> Void,
|
||||||
openPremiumIntro: @escaping () -> Void
|
openPremiumIntro: @escaping () -> Void,
|
||||||
|
openChatFolderUpdates: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.activateSearch = activateSearch
|
self.activateSearch = activateSearch
|
||||||
self.peerSelected = peerSelected
|
self.peerSelected = peerSelected
|
||||||
@ -172,6 +175,7 @@ public final class ChatListNodeInteraction {
|
|||||||
self.openStorageManagement = openStorageManagement
|
self.openStorageManagement = openStorageManagement
|
||||||
self.openPasswordSetup = openPasswordSetup
|
self.openPasswordSetup = openPasswordSetup
|
||||||
self.openPremiumIntro = openPremiumIntro
|
self.openPremiumIntro = openPremiumIntro
|
||||||
|
self.openChatFolderUpdates = openChatFolderUpdates
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,6 +624,8 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
nodeInteraction?.openPasswordSetup()
|
nodeInteraction?.openPasswordSetup()
|
||||||
case .premiumUpgrade, .premiumAnnualDiscount:
|
case .premiumUpgrade, .premiumAnnualDiscount:
|
||||||
nodeInteraction?.openPremiumIntro()
|
nodeInteraction?.openPremiumIntro()
|
||||||
|
case .chatFolderUpdates:
|
||||||
|
nodeInteraction?.openChatFolderUpdates()
|
||||||
}
|
}
|
||||||
}), directionHint: entry.directionHint)
|
}), directionHint: entry.directionHint)
|
||||||
}
|
}
|
||||||
@ -873,6 +879,8 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
|||||||
nodeInteraction?.openPasswordSetup()
|
nodeInteraction?.openPasswordSetup()
|
||||||
case .premiumUpgrade, .premiumAnnualDiscount:
|
case .premiumUpgrade, .premiumAnnualDiscount:
|
||||||
nodeInteraction?.openPremiumIntro()
|
nodeInteraction?.openPremiumIntro()
|
||||||
|
case .chatFolderUpdates:
|
||||||
|
nodeInteraction?.openChatFolderUpdates()
|
||||||
}
|
}
|
||||||
}), directionHint: entry.directionHint)
|
}), directionHint: entry.directionHint)
|
||||||
case .HeaderEntry:
|
case .HeaderEntry:
|
||||||
@ -1068,6 +1076,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 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) {
|
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
|
||||||
self.location = location
|
self.location = location
|
||||||
@ -1389,6 +1400,19 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads)
|
let controller = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .ads)
|
||||||
self.push?(controller)
|
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
|
nodeInteraction.isInlineMode = isInlineMode
|
||||||
|
|
||||||
@ -1614,15 +1638,18 @@ public final class ChatListNode: ListView {
|
|||||||
suggestedChatListNotice,
|
suggestedChatListNotice,
|
||||||
savedMessagesPeer,
|
savedMessagesPeer,
|
||||||
chatListViewUpdate,
|
chatListViewUpdate,
|
||||||
|
self.chatFolderUpdates.get() |> distinctUntilChanged,
|
||||||
self.statePromise.get()
|
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 (update, filter) = updateAndFilter
|
||||||
|
|
||||||
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
|
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
|
||||||
|
|
||||||
let notice: ChatListNotice?
|
let notice: ChatListNotice?
|
||||||
if let suggestedChatListNotice {
|
if let chatFolderUpdates, chatFolderUpdates.availableChatsToJoin != 0 {
|
||||||
|
notice = .chatFolderUpdates(count: chatFolderUpdates.availableChatsToJoin)
|
||||||
|
} else if let suggestedChatListNotice {
|
||||||
notice = suggestedChatListNotice
|
notice = suggestedChatListNotice
|
||||||
} else if let storageInfo {
|
} else if let storageInfo {
|
||||||
notice = .clearStorage(sizeFraction: storageInfo)
|
notice = .clearStorage(sizeFraction: storageInfo)
|
||||||
@ -2551,6 +2578,7 @@ public final class ChatListNode: ListView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.pollFilterUpdates(shouldDelay: false)
|
||||||
self.resetFilter()
|
self.resetFilter()
|
||||||
|
|
||||||
let selectionRecognizer = ChatHistoryListSelectionRecognizer(target: self, action: #selector(self.selectionPanGesture(_:)))
|
let selectionRecognizer = ChatHistoryListSelectionRecognizer(target: self, action: #selector(self.selectionPanGesture(_:)))
|
||||||
@ -2567,6 +2595,7 @@ public final class ChatListNode: ListView {
|
|||||||
self.chatListDisposable.dispose()
|
self.chatListDisposable.dispose()
|
||||||
self.activityStatusesDisposable?.dispose()
|
self.activityStatusesDisposable?.dispose()
|
||||||
self.updatedFilterDisposable.dispose()
|
self.updatedFilterDisposable.dispose()
|
||||||
|
self.pollFilterUpdatesDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateFilter(_ filter: ChatListFilter?) {
|
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() {
|
private func resetFilter() {
|
||||||
if let chatListFilter = self.chatListFilter {
|
if let chatListFilter = self.chatListFilter {
|
||||||
self.updatedFilterDisposable.set((self.context.engine.peers.updatedChatListFilters()
|
self.updatedFilterDisposable.set((self.context.engine.peers.updatedChatListFilters()
|
||||||
|
@ -51,6 +51,7 @@ enum ChatListNotice: Equatable {
|
|||||||
case setupPassword
|
case setupPassword
|
||||||
case premiumUpgrade(discount: Int32)
|
case premiumUpgrade(discount: Int32)
|
||||||
case premiumAnnualDiscount(discount: Int32)
|
case premiumAnnualDiscount(discount: Int32)
|
||||||
|
case chatFolderUpdates(count: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||||
|
@ -160,6 +160,15 @@ class ChatListStorageInfoItemNode: ListViewItemNode {
|
|||||||
titleString = titleStringValue
|
titleString = titleStringValue
|
||||||
|
|
||||||
textString = NSAttributedString(string: item.strings.ChatList_PremiumAnnualDiscountText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
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)))
|
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||||
|
@ -94,6 +94,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||||||
}, openStorageManagement: {
|
}, openStorageManagement: {
|
||||||
}, openPasswordSetup: {
|
}, openPasswordSetup: {
|
||||||
}, openPremiumIntro: {
|
}, openPremiumIntro: {
|
||||||
|
}, openChatFolderUpdates: {
|
||||||
})
|
})
|
||||||
|
|
||||||
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
|
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
|
||||||
|
@ -57,6 +57,7 @@ swift_library(
|
|||||||
"//submodules/LocalizedPeerData:LocalizedPeerData",
|
"//submodules/LocalizedPeerData:LocalizedPeerData",
|
||||||
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
|
"//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode",
|
||||||
"//submodules/QrCodeUI:QrCodeUI",
|
"//submodules/QrCodeUI:QrCodeUI",
|
||||||
|
"//submodules/PromptUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -21,6 +21,7 @@ import ItemListPeerItem
|
|||||||
import ShareController
|
import ShareController
|
||||||
import UndoUI
|
import UndoUI
|
||||||
import QrCodeUI
|
import QrCodeUI
|
||||||
|
import PromptUI
|
||||||
|
|
||||||
private final class FolderInviteLinkListControllerArguments {
|
private final class FolderInviteLinkListControllerArguments {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
@ -169,16 +170,20 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
case let .mainLinkHeader(text):
|
case let .mainLinkHeader(text):
|
||||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||||
case let .mainLink(link, isGenerating):
|
case let .mainLink(link, isGenerating):
|
||||||
return ItemListFolderInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: link, count: 0, peers: [], displayButton: true, enableButton: !isGenerating, buttonTitle: link != nil ? "Share Invite Link" : "Generate Invite Link", displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
return ItemListFolderInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: link, count: 0, peers: [], displayButton: true, enableButton: !isGenerating, buttonTitle: link != nil ? "Copy" : "Generate Invite Link", secondaryButtonTitle: link != nil ? "Share" : nil, displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
|
||||||
if let link {
|
if let link {
|
||||||
arguments.copyLink(link.link)
|
arguments.copyLink(link.link)
|
||||||
}
|
}
|
||||||
}, shareAction: {
|
}, shareAction: {
|
||||||
if let link {
|
if let link {
|
||||||
arguments.shareMainLink(link.link)
|
arguments.copyLink(link.link)
|
||||||
} else {
|
} else {
|
||||||
arguments.generateLink()
|
arguments.generateLink()
|
||||||
}
|
}
|
||||||
|
}, secondaryAction: {
|
||||||
|
if let link {
|
||||||
|
arguments.shareMainLink(link.link)
|
||||||
|
}
|
||||||
}, contextAction: { node, gesture in
|
}, contextAction: { node, gesture in
|
||||||
arguments.mainLinkContextAction(link, node, gesture)
|
arguments.mainLinkContextAction(link, node, gesture)
|
||||||
}, viewAction: {
|
}, viewAction: {
|
||||||
@ -205,6 +210,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
switchValue: ItemListPeerItemSwitch(value: isSelected, style: .leftCheck, isEnabled: isEnabled),
|
switchValue: ItemListPeerItemSwitch(value: isSelected, style: .leftCheck, isEnabled: isEnabled),
|
||||||
enabled: true,
|
enabled: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
|
highlightable: false,
|
||||||
sectionId: self.section,
|
sectionId: self.section,
|
||||||
action: {
|
action: {
|
||||||
arguments.peerAction(peer, isEnabled)
|
arguments.peerAction(peer, isEnabled)
|
||||||
@ -218,46 +224,44 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func canShareLinkToPeer(peer: EnginePeer) -> Bool {
|
|
||||||
var isEnabled = false
|
|
||||||
switch peer {
|
|
||||||
case let .channel(channel):
|
|
||||||
if channel.hasPermission(.inviteMembers) {
|
|
||||||
isEnabled = true
|
|
||||||
} else if channel.username != nil {
|
|
||||||
isEnabled = true
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return isEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
private func folderInviteLinkListControllerEntries(
|
private func folderInviteLinkListControllerEntries(
|
||||||
presentationData: PresentationData,
|
presentationData: PresentationData,
|
||||||
state: FolderInviteLinkListControllerState,
|
state: FolderInviteLinkListControllerState,
|
||||||
|
title: String,
|
||||||
allPeers: [EnginePeer]
|
allPeers: [EnginePeer]
|
||||||
) -> [InviteLinksListEntry] {
|
) -> [InviteLinksListEntry] {
|
||||||
var entries: [InviteLinksListEntry] = []
|
var entries: [InviteLinksListEntry] = []
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
|
||||||
|
var infoString: String?
|
||||||
let chatCountString: String
|
let chatCountString: String
|
||||||
let peersHeaderString: String
|
let peersHeaderString: String
|
||||||
if state.selectedPeerIds.isEmpty {
|
|
||||||
chatCountString = "Anyone with this link can add Gaming Club folder and the chats selected below."
|
let canShareChats = !allPeers.allSatisfy({ !canShareLinkToPeer(peer: $0) })
|
||||||
|
|
||||||
|
if !canShareChats {
|
||||||
|
infoString = "You can only share groups and channels in which you are allowed to create invite links."
|
||||||
|
chatCountString = "There are no chats in this folder that you can share with others."
|
||||||
|
peersHeaderString = "THESE CHATS CANNOT BE SHARED"
|
||||||
|
} else if state.selectedPeerIds.isEmpty {
|
||||||
|
chatCountString = "Anyone with this link can add \(title) folder and the chats selected below."
|
||||||
peersHeaderString = "CHATS"
|
peersHeaderString = "CHATS"
|
||||||
} else if state.selectedPeerIds.count == 1 {
|
} else if state.selectedPeerIds.count == 1 {
|
||||||
chatCountString = "Anyone with this link can add Gaming Club folder and the 1 chat selected below."
|
chatCountString = "Anyone with this link can add \(title) folder and the 1 chat selected below."
|
||||||
peersHeaderString = "1 CHAT SELECTED"
|
peersHeaderString = "1 CHAT SELECTED"
|
||||||
} else {
|
} else {
|
||||||
chatCountString = "Anyone with this link can add Gaming Club folder and the \(state.selectedPeerIds.count) chats selected below."
|
chatCountString = "Anyone with this link can add \(title) folder and the \(state.selectedPeerIds.count) chats selected below."
|
||||||
peersHeaderString = "\(state.selectedPeerIds.count) CHATS SELECTED"
|
peersHeaderString = "\(state.selectedPeerIds.count) CHATS SELECTED"
|
||||||
}
|
}
|
||||||
entries.append(.header(chatCountString))
|
entries.append(.header(chatCountString))
|
||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
|
|
||||||
entries.append(.mainLinkHeader("INVITE LINK"))
|
if canShareChats {
|
||||||
entries.append(.mainLink(link: state.currentLink, isGenerating: state.generatingLink))
|
entries.append(.mainLinkHeader("INVITE LINK"))
|
||||||
|
entries.append(.mainLink(link: state.currentLink, isGenerating: state.generatingLink))
|
||||||
|
}
|
||||||
|
|
||||||
entries.append(.peersHeader(peersHeaderString))
|
entries.append(.peersHeader(peersHeaderString))
|
||||||
|
|
||||||
@ -274,27 +278,35 @@ private func folderInviteLinkListControllerEntries(
|
|||||||
entries.append(.peer(index: entries.count, peer: peer, isSelected: state.selectedPeerIds.contains(peer.id), isEnabled: isEnabled))
|
entries.append(.peer(index: entries.count, peer: peer, isSelected: state.selectedPeerIds.contains(peer.id), isEnabled: isEnabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let infoString {
|
||||||
|
entries.append(.peersInfo(infoString))
|
||||||
|
}
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct FolderInviteLinkListControllerState: Equatable {
|
private struct FolderInviteLinkListControllerState: Equatable {
|
||||||
|
var title: String?
|
||||||
var currentLink: ExportedChatFolderLink?
|
var currentLink: ExportedChatFolderLink?
|
||||||
var selectedPeerIds = Set<EnginePeer.Id>()
|
var selectedPeerIds = Set<EnginePeer.Id>()
|
||||||
var generatingLink: Bool = false
|
var generatingLink: Bool = false
|
||||||
|
var isSaving: Bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
public func folderInviteLinkListController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filterId: Int32, allPeerIds: [PeerId], currentInvitation: ExportedChatFolderLink?, linkUpdated: @escaping (ExportedChatFolderLink?) -> Void) -> ViewController {
|
public func folderInviteLinkListController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, filterId: Int32, title filterTitle: String, allPeerIds: [PeerId], currentInvitation: ExportedChatFolderLink?, linkUpdated: @escaping (ExportedChatFolderLink?) -> Void) -> ViewController {
|
||||||
var pushControllerImpl: ((ViewController) -> Void)?
|
var pushControllerImpl: ((ViewController) -> Void)?
|
||||||
let _ = pushControllerImpl
|
let _ = pushControllerImpl
|
||||||
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
|
||||||
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
var presentInGlobalOverlayImpl: ((ViewController) -> Void)?
|
||||||
var dismissImpl: (() -> Void)?
|
var dismissImpl: (() -> Void)?
|
||||||
|
var attemptNavigationImpl: ((@escaping () -> Void) -> Bool)?
|
||||||
|
|
||||||
var dismissTooltipsImpl: (() -> Void)?
|
var dismissTooltipsImpl: (() -> Void)?
|
||||||
|
|
||||||
let actionsDisposable = DisposableSet()
|
let actionsDisposable = DisposableSet()
|
||||||
|
|
||||||
var initialState = FolderInviteLinkListControllerState()
|
var initialState = FolderInviteLinkListControllerState()
|
||||||
|
initialState.title = currentInvitation?.title
|
||||||
initialState.currentLink = currentInvitation
|
initialState.currentLink = currentInvitation
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
let stateValue = Atomic(value: initialState)
|
||||||
@ -313,6 +325,8 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
|
|
||||||
var displayTooltipImpl: ((UndoOverlayContent) -> Void)?
|
var displayTooltipImpl: ((UndoOverlayContent) -> Void)?
|
||||||
|
|
||||||
|
var didDisplayAddPeerNotice: Bool = false
|
||||||
|
|
||||||
let arguments = FolderInviteLinkListControllerArguments(context: context, shareMainLink: { inviteLink in
|
let arguments = FolderInviteLinkListControllerArguments(context: context, shareMainLink: { inviteLink in
|
||||||
let shareController = ShareController(context: context, subject: .url(inviteLink), updatedPresentationData: updatedPresentationData)
|
let shareController = ShareController(context: context, subject: .url(inviteLink), updatedPresentationData: updatedPresentationData)
|
||||||
shareController.completed = { peerIds in
|
shareController.completed = { peerIds in
|
||||||
@ -368,6 +382,33 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
}
|
}
|
||||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
var items: [ContextMenuItem] = []
|
var items: [ContextMenuItem] = []
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
items.append(.action(ContextMenuActionItem(text: "Name Link", icon: { theme in
|
||||||
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pencil"), color: theme.contextMenu.primaryColor)
|
||||||
|
}, action: { _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
|
let state = stateValue.with({ $0 })
|
||||||
|
|
||||||
|
let promptController = promptController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, text: "Link title", value: state.title ?? "", apply: { value in
|
||||||
|
if let value {
|
||||||
|
updateState { state in
|
||||||
|
var state = state
|
||||||
|
|
||||||
|
state.title = value
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
/*promptController.dismissed = { byOutsideTap in
|
||||||
|
if byOutsideTap {
|
||||||
|
completionHandler(nil)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
presentControllerImpl?(promptController, nil)
|
||||||
|
})))
|
||||||
|
|
||||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
|
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
|
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor)
|
||||||
@ -395,9 +436,9 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
}, action: { _, f in
|
}, action: { _, f in
|
||||||
f(.dismissWithoutContent)
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
let _ = (context.engine.peers.editChatFolderLink(filterId: filterId, link: invite, title: nil, revoke: true)
|
let _ = (context.engine.peers.editChatFolderLink(filterId: filterId, link: invite, title: nil, peerIds: nil, revoke: true)
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
let _ = (context.engine.peers.revokeChatFolderLink(filterId: filterId, link: invite)
|
let _ = (context.engine.peers.deleteChatFolderLink(filterId: filterId, link: invite)
|
||||||
|> deliverOnMainQueue).start(completed: {
|
|> deliverOnMainQueue).start(completed: {
|
||||||
linkUpdated(nil)
|
linkUpdated(nil)
|
||||||
dismissImpl?()
|
dismissImpl?()
|
||||||
@ -408,12 +449,8 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
|
||||||
presentInGlobalOverlayImpl?(contextController)
|
presentInGlobalOverlayImpl?(contextController)
|
||||||
}, peerAction: { peer, isEnabled in
|
}, peerAction: { peer, isEnabled in
|
||||||
let state = stateValue.with({ $0 })
|
|
||||||
if state.currentLink != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if isEnabled {
|
if isEnabled {
|
||||||
|
var added = false
|
||||||
updateState { state in
|
updateState { state in
|
||||||
var state = state
|
var state = state
|
||||||
|
|
||||||
@ -421,13 +458,22 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
state.selectedPeerIds.remove(peer.id)
|
state.selectedPeerIds.remove(peer.id)
|
||||||
} else {
|
} else {
|
||||||
state.selectedPeerIds.insert(peer.id)
|
state.selectedPeerIds.insert(peer.id)
|
||||||
|
added = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if added && !didDisplayAddPeerNotice {
|
||||||
|
didDisplayAddPeerNotice = true
|
||||||
|
|
||||||
|
dismissTooltipsImpl?()
|
||||||
|
//TODO:localize
|
||||||
|
displayTooltipImpl?(.info(title: nil, text: "People who already used the invite link will be able to join newly added chats."))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
//TODO:localized
|
//TODO:localize
|
||||||
var text = "You can't invite others here"
|
var text = "You can't invite others here."
|
||||||
switch peer {
|
switch peer {
|
||||||
case .channel:
|
case .channel:
|
||||||
text = "You don't have the admin rights to share invite links to this group chat."
|
text = "You don't have the admin rights to share invite links to this group chat."
|
||||||
@ -483,6 +529,40 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
EngineDataList(combinedPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
EngineDataList(combinedPeerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let applyChangesImpl: (() -> Void)? = {
|
||||||
|
let state = stateValue.with({ $0 })
|
||||||
|
if let currentLink = state.currentLink {
|
||||||
|
if currentLink.title != state.title || Set(currentLink.peerIds) != state.selectedPeerIds {
|
||||||
|
updateState { state in
|
||||||
|
var state = state
|
||||||
|
state.isSaving = true
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
actionsDisposable.add((context.engine.peers.editChatFolderLink(filterId: filterId, link: currentLink, title: state.title, peerIds: Array(state.selectedPeerIds), revoke: false)
|
||||||
|
|> deliverOnMainQueue).start(error: { _ in
|
||||||
|
updateState { state in
|
||||||
|
var state = state
|
||||||
|
state.isSaving = false
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
dismissTooltipsImpl?()
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
//TODO:localize
|
||||||
|
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "An error occurred."), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
|
||||||
|
}, completed: {
|
||||||
|
linkUpdated(ExportedChatFolderLink(title: state.title ?? "", link: currentLink.link, peerIds: Array(state.selectedPeerIds), isRevoked: false))
|
||||||
|
dismissImpl?()
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
dismissImpl?()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dismissImpl?()
|
||||||
|
}
|
||||||
|
dismissImpl?()
|
||||||
|
}
|
||||||
|
|
||||||
let _ = (allPeers
|
let _ = (allPeers
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { peers in
|
|> deliverOnMainQueue).start(next: { peers in
|
||||||
@ -519,28 +599,27 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
|
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
let title: ItemListControllerTitle
|
let title: ItemListControllerTitle
|
||||||
title = .text("Share Folder")
|
|
||||||
|
var folderTitle = "Share Folder"
|
||||||
|
if let title = state.title, !title.isEmpty {
|
||||||
|
folderTitle = title
|
||||||
|
}
|
||||||
|
title = .text(folderTitle)
|
||||||
|
|
||||||
var doneButton: ItemListNavigationButton?
|
var doneButton: ItemListNavigationButton?
|
||||||
doneButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
if state.isSaving {
|
||||||
/*let state = stateValue.with({ $0 })
|
doneButton = ItemListNavigationButton(content: .none, style: .activity, enabled: true, action: {})
|
||||||
if let currentLink = state.currentLink {
|
} else {
|
||||||
updateState { state in
|
doneButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Done), style: .bold, enabled: true, action: {
|
||||||
var state = state
|
applyChangesImpl?()
|
||||||
state.isSaving = true
|
})
|
||||||
return state
|
}
|
||||||
}
|
|
||||||
actionsDisposable.add(context.engine.peers.editChatFolderLink(filterId: filterId, link: currentLink, title: nil, revoke: false))
|
|
||||||
} else {
|
|
||||||
dismissImpl?()
|
|
||||||
}*/
|
|
||||||
dismissImpl?()
|
|
||||||
})
|
|
||||||
|
|
||||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: doneButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: doneButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: folderInviteLinkListControllerEntries(
|
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: folderInviteLinkListControllerEntries(
|
||||||
presentationData: presentationData,
|
presentationData: presentationData,
|
||||||
state: state,
|
state: state,
|
||||||
|
title: filterTitle,
|
||||||
allPeers: allPeers.compactMap { $0 }
|
allPeers: allPeers.compactMap { $0 }
|
||||||
), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges)
|
), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges)
|
||||||
|
|
||||||
@ -563,6 +642,43 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
controller.attemptNavigation = { f in
|
||||||
|
return attemptNavigationImpl?(f) ?? true
|
||||||
|
}
|
||||||
|
attemptNavigationImpl = { f in
|
||||||
|
if let currentInvitation {
|
||||||
|
let state = stateValue.with({ $0 })
|
||||||
|
|
||||||
|
var hasChanges = false
|
||||||
|
if state.title != currentInvitation.title {
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
|
if state.selectedPeerIds != Set(currentInvitation.peerIds) {
|
||||||
|
hasChanges = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasChanges {
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
//TODO:localize
|
||||||
|
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Unsaved Changes", text: "You have changed the settings of this folder. Apply changes?", actions: [
|
||||||
|
TextAlertAction(type: .genericAction, title: "Discard", action: {
|
||||||
|
f()
|
||||||
|
dismissImpl?()
|
||||||
|
}),
|
||||||
|
TextAlertAction(type: .defaultAction, title: "Apply", action: {
|
||||||
|
applyChangesImpl?()
|
||||||
|
})
|
||||||
|
]), nil)
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
f()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
pushControllerImpl = { [weak controller] c in
|
pushControllerImpl = { [weak controller] c in
|
||||||
if let controller = controller {
|
if let controller = controller {
|
||||||
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
|
(controller.navigationController as? NavigationController)?.pushViewController(c, animated: true)
|
||||||
|
@ -34,12 +34,14 @@ public class ItemListFolderInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
let displayButton: Bool
|
let displayButton: Bool
|
||||||
let enableButton: Bool
|
let enableButton: Bool
|
||||||
let buttonTitle: String
|
let buttonTitle: String
|
||||||
|
let secondaryButtonTitle: String?
|
||||||
let displayImporters: Bool
|
let displayImporters: Bool
|
||||||
let buttonColor: UIColor?
|
let buttonColor: UIColor?
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let style: ItemListStyle
|
let style: ItemListStyle
|
||||||
let copyAction: (() -> Void)?
|
let copyAction: (() -> Void)?
|
||||||
let shareAction: (() -> Void)?
|
let shareAction: (() -> Void)?
|
||||||
|
let secondaryAction: (() -> Void)?
|
||||||
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
let contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||||
let viewAction: (() -> Void)?
|
let viewAction: (() -> Void)?
|
||||||
public let tag: ItemListItemTag?
|
public let tag: ItemListItemTag?
|
||||||
@ -53,12 +55,14 @@ public class ItemListFolderInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
displayButton: Bool,
|
displayButton: Bool,
|
||||||
enableButton: Bool,
|
enableButton: Bool,
|
||||||
buttonTitle: String,
|
buttonTitle: String,
|
||||||
|
secondaryButtonTitle: String?,
|
||||||
displayImporters: Bool,
|
displayImporters: Bool,
|
||||||
buttonColor: UIColor?,
|
buttonColor: UIColor?,
|
||||||
sectionId: ItemListSectionId,
|
sectionId: ItemListSectionId,
|
||||||
style: ItemListStyle,
|
style: ItemListStyle,
|
||||||
copyAction: (() -> Void)?,
|
copyAction: (() -> Void)?,
|
||||||
shareAction: (() -> Void)?,
|
shareAction: (() -> Void)?,
|
||||||
|
secondaryAction: (() -> Void)?,
|
||||||
contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?,
|
contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?,
|
||||||
viewAction: (() -> Void)?,
|
viewAction: (() -> Void)?,
|
||||||
tag: ItemListItemTag? = nil
|
tag: ItemListItemTag? = nil
|
||||||
@ -71,12 +75,14 @@ public class ItemListFolderInviteLinkItem: ListViewItem, ItemListItem {
|
|||||||
self.displayButton = displayButton
|
self.displayButton = displayButton
|
||||||
self.enableButton = enableButton
|
self.enableButton = enableButton
|
||||||
self.buttonTitle = buttonTitle
|
self.buttonTitle = buttonTitle
|
||||||
|
self.secondaryButtonTitle = secondaryButtonTitle
|
||||||
self.displayImporters = displayImporters
|
self.displayImporters = displayImporters
|
||||||
self.buttonColor = buttonColor
|
self.buttonColor = buttonColor
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.style = style
|
self.style = style
|
||||||
self.copyAction = copyAction
|
self.copyAction = copyAction
|
||||||
self.shareAction = shareAction
|
self.shareAction = shareAction
|
||||||
|
self.secondaryAction = secondaryAction
|
||||||
self.contextAction = contextAction
|
self.contextAction = contextAction
|
||||||
self.viewAction = viewAction
|
self.viewAction = viewAction
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
@ -133,6 +139,7 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod
|
|||||||
private let addressButtonIconNode: ASImageNode
|
private let addressButtonIconNode: ASImageNode
|
||||||
private var addressShimmerNode: ShimmerEffectNode?
|
private var addressShimmerNode: ShimmerEffectNode?
|
||||||
private var shareButtonNode: SolidRoundedButtonNode?
|
private var shareButtonNode: SolidRoundedButtonNode?
|
||||||
|
private var secondaryButtonNode: SolidRoundedButtonNode?
|
||||||
|
|
||||||
private let avatarsButtonNode: HighlightTrackingButtonNode
|
private let avatarsButtonNode: HighlightTrackingButtonNode
|
||||||
private let avatarsContext: AnimatedAvatarSetContext
|
private let avatarsContext: AnimatedAvatarSetContext
|
||||||
@ -465,13 +472,49 @@ public class ItemListFolderInviteLinkItemNode: ListViewItemNode, ItemListItemNod
|
|||||||
strongSelf.addSubnode(shareButtonNode)
|
strongSelf.addSubnode(shareButtonNode)
|
||||||
strongSelf.shareButtonNode = shareButtonNode
|
strongSelf.shareButtonNode = shareButtonNode
|
||||||
}
|
}
|
||||||
|
|
||||||
shareButtonNode.title = item.buttonTitle
|
shareButtonNode.title = item.buttonTitle
|
||||||
|
|
||||||
let buttonWidth = contentSize.width - leftInset - rightInset
|
if let secondaryButtonTitle = item.secondaryButtonTitle {
|
||||||
|
let secondaryButtonNode: SolidRoundedButtonNode
|
||||||
|
if let current = strongSelf.secondaryButtonNode {
|
||||||
|
secondaryButtonNode = current
|
||||||
|
} else {
|
||||||
|
let buttonTheme: SolidRoundedButtonTheme
|
||||||
|
if let buttonColor = item.buttonColor {
|
||||||
|
buttonTheme = SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||||
|
} else {
|
||||||
|
buttonTheme = SolidRoundedButtonTheme(theme: item.presentationData.theme)
|
||||||
|
}
|
||||||
|
secondaryButtonNode = SolidRoundedButtonNode(theme: buttonTheme, height: 50.0, cornerRadius: 11.0)
|
||||||
|
secondaryButtonNode.pressed = { [weak self] in
|
||||||
|
self?.item?.secondaryAction?()
|
||||||
|
}
|
||||||
|
strongSelf.addSubnode(secondaryButtonNode)
|
||||||
|
strongSelf.secondaryButtonNode = secondaryButtonNode
|
||||||
|
}
|
||||||
|
secondaryButtonNode.title = secondaryButtonTitle
|
||||||
|
} else {
|
||||||
|
if let secondaryButtonNode = strongSelf.secondaryButtonNode {
|
||||||
|
strongSelf.secondaryButtonNode = nil
|
||||||
|
secondaryButtonNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonWidth = contentSize.width - leftInset - rightInset
|
||||||
|
let totalButtonWidth = buttonWidth
|
||||||
|
let buttonSpacing: CGFloat = 8.0
|
||||||
|
if strongSelf.secondaryButtonNode != nil {
|
||||||
|
buttonWidth = floor((buttonWidth - 8.0) / 2.0)
|
||||||
|
}
|
||||||
|
|
||||||
let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
|
let _ = shareButtonNode.updateLayout(width: buttonWidth, transition: .immediate)
|
||||||
shareButtonNode.frame = CGRect(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
|
shareButtonNode.frame = CGRect(x: leftInset, y: verticalInset + fieldHeight + fieldSpacing, width: buttonWidth, height: buttonHeight)
|
||||||
|
|
||||||
|
if let secondaryButtonNode = strongSelf.secondaryButtonNode {
|
||||||
|
let _ = secondaryButtonNode.updateLayout(width: totalButtonWidth - buttonWidth - buttonSpacing, transition: .immediate)
|
||||||
|
secondaryButtonNode.frame = CGRect(x: leftInset + buttonWidth + buttonSpacing, y: verticalInset + fieldHeight + fieldSpacing, width: totalButtonWidth - buttonWidth - buttonSpacing, height: buttonHeight)
|
||||||
|
}
|
||||||
|
|
||||||
var totalWidth = invitedPeersLayout.size.width
|
var totalWidth = invitedPeersLayout.size.width
|
||||||
var leftOrigin: CGFloat = floorToScreenPixels((params.width - invitedPeersLayout.size.width) / 2.0)
|
var leftOrigin: CGFloat = floorToScreenPixels((params.width - invitedPeersLayout.size.width) / 2.0)
|
||||||
let avatarSpacing: CGFloat = 21.0
|
let avatarSpacing: CGFloat = 21.0
|
||||||
|
@ -338,6 +338,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
|||||||
let enabled: Bool
|
let enabled: Bool
|
||||||
let highlighted: Bool
|
let highlighted: Bool
|
||||||
public let selectable: Bool
|
public let selectable: Bool
|
||||||
|
let highlightable: Bool
|
||||||
let animateFirstAvatarTransition: Bool
|
let animateFirstAvatarTransition: Bool
|
||||||
public let sectionId: ItemListSectionId
|
public let sectionId: ItemListSectionId
|
||||||
let action: (() -> Void)?
|
let action: (() -> Void)?
|
||||||
@ -355,7 +356,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
|||||||
let displayDecorations: Bool
|
let displayDecorations: Bool
|
||||||
let disableInteractiveTransitionIfNecessary: Bool
|
let disableInteractiveTransitionIfNecessary: Bool
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, threadInfo: EngineMessageHistoryThread.Info? = nil, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: EnginePeer.Presence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, highlighted: Bool = false, selectable: Bool, animateFirstAvatarTransition: Bool = true, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, noCorners: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil, displayDecorations: Bool = true, disableInteractiveTransitionIfNecessary: Bool = false) {
|
public init(presentationData: ItemListPresentationData, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, context: AccountContext, peer: EnginePeer, threadInfo: EngineMessageHistoryThread.Info? = nil, height: ItemListPeerItemHeight = .peerList, aliasHandling: ItemListPeerItemAliasHandling = .standard, nameColor: ItemListPeerItemNameColor = .primary, nameStyle: ItemListPeerItemNameStyle = .distinctBold, presence: EnginePeer.Presence?, text: ItemListPeerItemText, label: ItemListPeerItemLabel, editing: ItemListPeerItemEditing, revealOptions: ItemListPeerItemRevealOptions? = nil, switchValue: ItemListPeerItemSwitch?, enabled: Bool, highlighted: Bool = false, selectable: Bool, highlightable: Bool = true, animateFirstAvatarTransition: Bool = true, sectionId: ItemListSectionId, action: (() -> Void)?, setPeerIdWithRevealedOptions: @escaping (EnginePeer.Id?, EnginePeer.Id?) -> Void, removePeer: @escaping (EnginePeer.Id) -> Void, toggleUpdated: ((Bool) -> Void)? = nil, contextAction: ((ASDisplayNode, ContextGesture?) -> Void)? = nil, hasTopStripe: Bool = true, hasTopGroupInset: Bool = true, noInsets: Bool = false, noCorners: Bool = false, tag: ItemListItemTag? = nil, header: ListViewItemHeader? = nil, shimmering: ItemListPeerItemShimmering? = nil, displayDecorations: Bool = true, disableInteractiveTransitionIfNecessary: Bool = false) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.dateTimeFormat = dateTimeFormat
|
self.dateTimeFormat = dateTimeFormat
|
||||||
self.nameDisplayOrder = nameDisplayOrder
|
self.nameDisplayOrder = nameDisplayOrder
|
||||||
@ -375,6 +376,7 @@ public final class ItemListPeerItem: ListViewItem, ItemListItem {
|
|||||||
self.enabled = enabled
|
self.enabled = enabled
|
||||||
self.highlighted = highlighted
|
self.highlighted = highlighted
|
||||||
self.selectable = selectable
|
self.selectable = selectable
|
||||||
|
self.highlightable = highlightable
|
||||||
self.animateFirstAvatarTransition = animateFirstAvatarTransition
|
self.animateFirstAvatarTransition = animateFirstAvatarTransition
|
||||||
self.sectionId = sectionId
|
self.sectionId = sectionId
|
||||||
self.action = action
|
self.action = action
|
||||||
@ -1256,6 +1258,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
var checkTheme = CheckNodeTheme(theme: item.presentationData.theme, style: .plain)
|
var checkTheme = CheckNodeTheme(theme: item.presentationData.theme, style: .plain)
|
||||||
checkTheme.isDottedBorder = !switchValue.isEnabled
|
checkTheme.isDottedBorder = !switchValue.isEnabled
|
||||||
leftCheckNode = CheckNode(theme: checkTheme)
|
leftCheckNode = CheckNode(theme: checkTheme)
|
||||||
|
leftCheckNode.isUserInteractionEnabled = false
|
||||||
strongSelf.leftCheckNode = leftCheckNode
|
strongSelf.leftCheckNode = leftCheckNode
|
||||||
strongSelf.avatarNode.supernode?.addSubnode(leftCheckNode)
|
strongSelf.avatarNode.supernode?.addSubnode(leftCheckNode)
|
||||||
}
|
}
|
||||||
@ -1377,7 +1380,7 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo
|
|||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.backgroundNode.isHidden = !item.displayDecorations
|
strongSelf.backgroundNode.isHidden = !item.displayDecorations
|
||||||
strongSelf.highlightedBackgroundNode.isHidden = !item.displayDecorations
|
strongSelf.highlightedBackgroundNode.isHidden = !item.displayDecorations || !item.highlightable
|
||||||
|
|
||||||
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ public enum ItemListSectionHeaderActivityIndicator {
|
|||||||
public class ItemListSectionHeaderItem: ListViewItem, ItemListItem {
|
public class ItemListSectionHeaderItem: ListViewItem, ItemListItem {
|
||||||
let presentationData: ItemListPresentationData
|
let presentationData: ItemListPresentationData
|
||||||
let text: String
|
let text: String
|
||||||
|
let badge: String?
|
||||||
let multiline: Bool
|
let multiline: Bool
|
||||||
let activityIndicator: ItemListSectionHeaderActivityIndicator
|
let activityIndicator: ItemListSectionHeaderActivityIndicator
|
||||||
let accessoryText: ItemListSectionHeaderAccessoryText?
|
let accessoryText: ItemListSectionHeaderAccessoryText?
|
||||||
@ -48,9 +49,10 @@ public class ItemListSectionHeaderItem: ListViewItem, ItemListItem {
|
|||||||
|
|
||||||
public let isAlwaysPlain: Bool = true
|
public let isAlwaysPlain: Bool = true
|
||||||
|
|
||||||
public init(presentationData: ItemListPresentationData, text: String, multiline: Bool = false, activityIndicator: ItemListSectionHeaderActivityIndicator = .none, accessoryText: ItemListSectionHeaderAccessoryText? = nil, sectionId: ItemListSectionId) {
|
public init(presentationData: ItemListPresentationData, text: String, badge: String? = nil, multiline: Bool = false, activityIndicator: ItemListSectionHeaderActivityIndicator = .none, accessoryText: ItemListSectionHeaderAccessoryText? = nil, sectionId: ItemListSectionId) {
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.text = text
|
self.text = text
|
||||||
|
self.badge = badge
|
||||||
self.multiline = multiline
|
self.multiline = multiline
|
||||||
self.activityIndicator = activityIndicator
|
self.activityIndicator = activityIndicator
|
||||||
self.accessoryText = accessoryText
|
self.accessoryText = accessoryText
|
||||||
@ -97,6 +99,8 @@ public class ItemListSectionHeaderItemNode: ListViewItemNode {
|
|||||||
private var item: ItemListSectionHeaderItem?
|
private var item: ItemListSectionHeaderItem?
|
||||||
|
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
|
private var badgeBackgroundLayer: SimpleLayer?
|
||||||
|
private var badgeTextNode: TextNode?
|
||||||
private let accessoryTextNode: TextNode
|
private let accessoryTextNode: TextNode
|
||||||
private var accessoryImageNode: ASImageNode?
|
private var accessoryImageNode: ASImageNode?
|
||||||
private var activityIndicator: ActivityIndicator?
|
private var activityIndicator: ActivityIndicator?
|
||||||
@ -126,6 +130,7 @@ public class ItemListSectionHeaderItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
public func asyncLayout() -> (_ item: ItemListSectionHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
public func asyncLayout() -> (_ item: ItemListSectionHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||||
|
let makeBadgeTextLayout = TextNode.asyncLayout(self.badgeTextNode)
|
||||||
let makeAccessoryTextLayout = TextNode.asyncLayout(self.accessoryTextNode)
|
let makeAccessoryTextLayout = TextNode.asyncLayout(self.accessoryTextNode)
|
||||||
|
|
||||||
let previousItem = self.item
|
let previousItem = self.item
|
||||||
@ -135,7 +140,19 @@ public class ItemListSectionHeaderItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
|
let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseHeaderFontSize)
|
||||||
|
|
||||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.text, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: item.multiline ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
var badgeLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||||
|
if let badge = item.badge {
|
||||||
|
let badgeFont = Font.semibold(item.presentationData.fontSize.itemListBaseHeaderFontSize * 11.0 / 13.0)
|
||||||
|
badgeLayoutAndApply = makeBadgeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: badge, font: badgeFont, textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
let badgeSpacing: CGFloat = 6.0
|
||||||
|
var textRightInset: CGFloat = 20.0
|
||||||
|
if let badgeLayoutAndApply {
|
||||||
|
textRightInset += badgeLayoutAndApply.0.size.width + badgeSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.text, font: titleFont, textColor: item.presentationData.theme.list.sectionHeaderTextColor), backgroundColor: nil, maximumNumberOfLines: item.multiline ? 0 : 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||||
var accessoryTextString: NSAttributedString?
|
var accessoryTextString: NSAttributedString?
|
||||||
var accessoryIcon: UIImage?
|
var accessoryIcon: UIImage?
|
||||||
if let accessoryText = item.accessoryText {
|
if let accessoryText = item.accessoryText {
|
||||||
@ -178,6 +195,43 @@ public class ItemListSectionHeaderItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 7.0), size: titleLayout.size)
|
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 7.0), size: titleLayout.size)
|
||||||
|
|
||||||
|
if let badgeLayoutAndApply {
|
||||||
|
let badgeTextNode = badgeLayoutAndApply.1()
|
||||||
|
let badgeSideInset: CGFloat = 4.0
|
||||||
|
let badgeBackgroundSize = CGSize(width: badgeSideInset * 2.0 + badgeLayoutAndApply.0.size.width, height: badgeLayoutAndApply.0.size.height + 3.0)
|
||||||
|
let badgeBackgroundFrame = CGRect(origin: CGPoint(x: strongSelf.titleNode.frame.maxX + badgeSpacing, y: strongSelf.titleNode.frame.minY - UIScreenPixel + floorToScreenPixels((strongSelf.titleNode.bounds.height - badgeBackgroundSize.height) * 0.5)), size: badgeBackgroundSize)
|
||||||
|
|
||||||
|
let badgeBackgroundLayer: SimpleLayer
|
||||||
|
if let current = strongSelf.badgeBackgroundLayer {
|
||||||
|
badgeBackgroundLayer = current
|
||||||
|
} else {
|
||||||
|
badgeBackgroundLayer = SimpleLayer()
|
||||||
|
strongSelf.badgeBackgroundLayer = badgeBackgroundLayer
|
||||||
|
strongSelf.layer.addSublayer(badgeBackgroundLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strongSelf.badgeTextNode !== badgeTextNode {
|
||||||
|
strongSelf.badgeTextNode?.removeFromSupernode()
|
||||||
|
strongSelf.badgeTextNode = badgeTextNode
|
||||||
|
strongSelf.addSubnode(badgeTextNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
badgeBackgroundLayer.frame = badgeBackgroundFrame
|
||||||
|
badgeBackgroundLayer.backgroundColor = item.presentationData.theme.list.itemCheckColors.fillColor.cgColor
|
||||||
|
badgeBackgroundLayer.cornerRadius = 5.0
|
||||||
|
|
||||||
|
badgeTextNode.frame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.minX + floor((badgeBackgroundFrame.width - badgeLayoutAndApply.0.size.width) * 0.5), y: badgeBackgroundFrame.minY + 1.0 + floorToScreenPixels((badgeBackgroundFrame.height - badgeLayoutAndApply.0.size.height) * 0.5)), size: badgeLayoutAndApply.0.size)
|
||||||
|
} else {
|
||||||
|
if let badgeTextNode = strongSelf.badgeTextNode {
|
||||||
|
strongSelf.badgeTextNode = nil
|
||||||
|
badgeTextNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
if let badgeBackgroundLayer = strongSelf.badgeBackgroundLayer {
|
||||||
|
strongSelf.badgeBackgroundLayer = nil
|
||||||
|
badgeBackgroundLayer.removeFromSuperlayer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var accessoryTextOffset: CGFloat = 0.0
|
var accessoryTextOffset: CGFloat = 0.0
|
||||||
if let accessoryIcon = accessoryIcon {
|
if let accessoryIcon = accessoryIcon {
|
||||||
accessoryTextOffset += accessoryIcon.size.width + 3.0
|
accessoryTextOffset += accessoryIcon.size.width + 3.0
|
||||||
|
@ -172,6 +172,18 @@ public enum PremiumSource: Equatable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case .linksPerSharedFolder:
|
||||||
|
if case .linksPerSharedFolder = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .membershipInSharedFolders:
|
||||||
|
if case .membershipInSharedFolders = rhs {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +211,8 @@ public enum PremiumSource: Equatable {
|
|||||||
case voiceToText
|
case voiceToText
|
||||||
case fasterDownload
|
case fasterDownload
|
||||||
case translation
|
case translation
|
||||||
|
case linksPerSharedFolder
|
||||||
|
case membershipInSharedFolders
|
||||||
|
|
||||||
var identifier: String? {
|
var identifier: String? {
|
||||||
switch self {
|
switch self {
|
||||||
@ -252,6 +266,10 @@ public enum PremiumSource: Equatable {
|
|||||||
}
|
}
|
||||||
case .translation:
|
case .translation:
|
||||||
return "translations"
|
return "translations"
|
||||||
|
case .linksPerSharedFolder:
|
||||||
|
return "double_limits__community_invites"
|
||||||
|
case .membershipInSharedFolders:
|
||||||
|
return "double_limits__communities_joined"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -765,6 +765,36 @@ private final class LimitSheetContent: CombinedComponent {
|
|||||||
badgeText = "\(limit)"
|
badgeText = "\(limit)"
|
||||||
string = strings.Premium_MaxChatsInFolderNoPremiumText("\(limit)").string
|
string = strings.Premium_MaxChatsInFolderNoPremiumText("\(limit)").string
|
||||||
}
|
}
|
||||||
|
case .linksPerSharedFolder:
|
||||||
|
//TODO:localize
|
||||||
|
let limit = state.limits.maxSharedFolderInviteLinks
|
||||||
|
let premiumLimit = state.premiumLimits.maxSharedFolderInviteLinks
|
||||||
|
iconName = "Premium/Link"
|
||||||
|
badgeText = "\(component.count)"
|
||||||
|
string = component.count >= premiumLimit ? strings.Premium_MaxSharedFolderLinksFinalText("\(premiumLimit)").string : strings.Premium_MaxSharedFolderLinksText("\(limit)", "\(premiumLimit)").string
|
||||||
|
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||||
|
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||||
|
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||||
|
|
||||||
|
if isPremiumDisabled {
|
||||||
|
badgeText = "\(limit)"
|
||||||
|
string = strings.Premium_MaxSharedFolderLinksNoPremiumText("\(limit)").string
|
||||||
|
}
|
||||||
|
case .membershipInSharedFolders:
|
||||||
|
//TODO:localize
|
||||||
|
let limit = state.limits.maxSharedFolderJoin
|
||||||
|
let premiumLimit = state.premiumLimits.maxSharedFolderJoin
|
||||||
|
iconName = "Premium/Folder"
|
||||||
|
badgeText = "\(component.count)"
|
||||||
|
string = component.count >= premiumLimit ? strings.Premium_MaxSharedFolderMembershipFinalText("\(premiumLimit)").string : strings.Premium_MaxSharedFolderMembershipText("\(limit)", "\(premiumLimit)").string
|
||||||
|
defaultValue = component.count > limit ? "\(limit)" : ""
|
||||||
|
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||||
|
badgePosition = CGFloat(component.count) / CGFloat(premiumLimit)
|
||||||
|
|
||||||
|
if isPremiumDisabled {
|
||||||
|
badgeText = "\(limit)"
|
||||||
|
string = strings.Premium_MaxSharedFolderMembershipNoPremiumText("\(limit)").string
|
||||||
|
}
|
||||||
case .pins:
|
case .pins:
|
||||||
let limit = state.limits.maxPinnedChatCount
|
let limit = state.limits.maxPinnedChatCount
|
||||||
let premiumLimit = state.premiumLimits.maxPinnedChatCount
|
let premiumLimit = state.premiumLimits.maxPinnedChatCount
|
||||||
@ -1048,6 +1078,8 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
|
|||||||
case pins
|
case pins
|
||||||
case files
|
case files
|
||||||
case accounts
|
case accounts
|
||||||
|
case linksPerSharedFolder
|
||||||
|
case membershipInSharedFolders
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, action: @escaping () -> Void) {
|
public init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, action: @escaping () -> Void) {
|
||||||
|
@ -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
|
}, 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: {})
|
}, 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)
|
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,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
|||||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in
|
}, 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)
|
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,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||||
gesture?.cancel()
|
gesture?.cancel()
|
||||||
}, present: { _ in
|
}, present: { _ in
|
||||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {} )
|
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {}, openPremiumIntro: {}, openChatFolderUpdates: {})
|
||||||
|
|
||||||
func makeChatListItem(
|
func makeChatListItem(
|
||||||
peer: EnginePeer,
|
peer: EnginePeer,
|
||||||
|
@ -5,7 +5,6 @@ public enum Api {
|
|||||||
public enum bots {}
|
public enum bots {}
|
||||||
public enum channels {}
|
public enum channels {}
|
||||||
public enum communities {}
|
public enum communities {}
|
||||||
public enum community {}
|
|
||||||
public enum contacts {}
|
public enum contacts {}
|
||||||
public enum help {}
|
public enum help {}
|
||||||
public enum messages {}
|
public enum messages {}
|
||||||
@ -998,11 +997,11 @@ 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[59080097] = { return Api.communities.CommunityInvite.parse_communityInvite($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) }
|
||||||
dict[-2662489] = { return Api.communities.ExportedInvites.parse_exportedInvites($0) }
|
dict[-2662489] = { return Api.communities.ExportedInvites.parse_exportedInvites($0) }
|
||||||
dict[988463765] = { return Api.community.CommunityInvite.parse_communityInvite($0) }
|
|
||||||
dict[74184410] = { return Api.community.CommunityInvite.parse_communityInviteAlready($0) }
|
|
||||||
dict[182326673] = { return Api.contacts.Blocked.parse_blocked($0) }
|
dict[182326673] = { return Api.contacts.Blocked.parse_blocked($0) }
|
||||||
dict[-513392236] = { return Api.contacts.Blocked.parse_blockedSlice($0) }
|
dict[-513392236] = { return Api.contacts.Blocked.parse_blockedSlice($0) }
|
||||||
dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) }
|
dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) }
|
||||||
@ -1802,14 +1801,14 @@ public extension Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.channels.SendAsPeers:
|
case let _1 as Api.channels.SendAsPeers:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.communities.CommunityInvite:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.communities.CommunityUpdates:
|
case let _1 as Api.communities.CommunityUpdates:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.communities.ExportedCommunityInvite:
|
case let _1 as Api.communities.ExportedCommunityInvite:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.communities.ExportedInvites:
|
case let _1 as Api.communities.ExportedInvites:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.community.CommunityInvite:
|
|
||||||
_1.serialize(buffer, boxed)
|
|
||||||
case let _1 as Api.contacts.Blocked:
|
case let _1 as Api.contacts.Blocked:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.contacts.Contacts:
|
case let _1 as Api.contacts.Contacts:
|
||||||
|
@ -894,6 +894,140 @@ public extension Api.channels {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.communities {
|
||||||
|
enum CommunityInvite: TypeConstructorDescription {
|
||||||
|
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])
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .communityInvite(let flags, let title, let emoticon, let peers, let chats, let users):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(59080097)
|
||||||
|
}
|
||||||
|
serializeInt32(flags, 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(Int32(peers.count))
|
||||||
|
for item in peers {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(chats.count))
|
||||||
|
for item in chats {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(users.count))
|
||||||
|
for item in users {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case .communityInviteAlready(let filterId, let missingPeers, let alreadyPeers, let chats, let users):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-951718393)
|
||||||
|
}
|
||||||
|
serializeInt32(filterId, buffer: buffer, boxed: false)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(missingPeers.count))
|
||||||
|
for item in missingPeers {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(alreadyPeers.count))
|
||||||
|
for item in alreadyPeers {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(chats.count))
|
||||||
|
for item in chats {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(users.count))
|
||||||
|
for item in users {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .communityInvite(let flags, let title, let emoticon, let peers, let chats, let users):
|
||||||
|
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):
|
||||||
|
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? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
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() {
|
||||||
|
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
|
||||||
|
}
|
||||||
|
var _5: [Api.Chat]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||||
|
}
|
||||||
|
var _6: [Api.User]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||||
|
let _c4 = _4 != nil
|
||||||
|
let _c5 = _5 != nil
|
||||||
|
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 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static func parse_communityInviteAlready(_ reader: BufferReader) -> CommunityInvite? {
|
||||||
|
var _1: Int32?
|
||||||
|
_1 = reader.readInt32()
|
||||||
|
var _2: [Api.Peer]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
|
||||||
|
}
|
||||||
|
var _3: [Api.Peer]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
|
||||||
|
}
|
||||||
|
var _4: [Api.Chat]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||||
|
}
|
||||||
|
var _5: [Api.User]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
let _c4 = _4 != nil
|
||||||
|
let _c5 = _5 != nil
|
||||||
|
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||||
|
return Api.communities.CommunityInvite.communityInviteAlready(filterId: _1!, missingPeers: _2!, alreadyPeers: _3!, chats: _4!, users: _5!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.communities {
|
public extension Api.communities {
|
||||||
enum CommunityUpdates: TypeConstructorDescription {
|
enum CommunityUpdates: TypeConstructorDescription {
|
||||||
case communityUpdates(missingPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
case communityUpdates(missingPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
||||||
@ -1000,181 +1134,3 @@ public extension Api.communities {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.communities {
|
|
||||||
enum ExportedInvites: TypeConstructorDescription {
|
|
||||||
case exportedInvites(invites: [Api.ExportedCommunityInvite], chats: [Api.Chat], users: [Api.User])
|
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
|
||||||
switch self {
|
|
||||||
case .exportedInvites(let invites, let chats, let users):
|
|
||||||
if boxed {
|
|
||||||
buffer.appendInt32(-2662489)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(invites.count))
|
|
||||||
for item in invites {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(chats.count))
|
|
||||||
for item in chats {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(users.count))
|
|
||||||
for item in users {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
|
||||||
switch self {
|
|
||||||
case .exportedInvites(let invites, let chats, let users):
|
|
||||||
return ("exportedInvites", [("invites", invites as Any), ("chats", chats as Any), ("users", users as Any)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func parse_exportedInvites(_ reader: BufferReader) -> ExportedInvites? {
|
|
||||||
var _1: [Api.ExportedCommunityInvite]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedCommunityInvite.self)
|
|
||||||
}
|
|
||||||
var _2: [Api.Chat]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
|
||||||
}
|
|
||||||
var _3: [Api.User]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
|
||||||
}
|
|
||||||
let _c1 = _1 != nil
|
|
||||||
let _c2 = _2 != nil
|
|
||||||
let _c3 = _3 != nil
|
|
||||||
if _c1 && _c2 && _c3 {
|
|
||||||
return Api.communities.ExportedInvites.exportedInvites(invites: _1!, chats: _2!, users: _3!)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public extension Api.community {
|
|
||||||
enum CommunityInvite: TypeConstructorDescription {
|
|
||||||
case communityInvite(title: String, peers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
|
||||||
case communityInviteAlready(filterId: Int32, missingPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
|
|
||||||
|
|
||||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
|
||||||
switch self {
|
|
||||||
case .communityInvite(let title, let peers, let chats, let users):
|
|
||||||
if boxed {
|
|
||||||
buffer.appendInt32(988463765)
|
|
||||||
}
|
|
||||||
serializeString(title, buffer: buffer, boxed: false)
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(peers.count))
|
|
||||||
for item in peers {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(chats.count))
|
|
||||||
for item in chats {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(users.count))
|
|
||||||
for item in users {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case .communityInviteAlready(let filterId, let missingPeers, let chats, let users):
|
|
||||||
if boxed {
|
|
||||||
buffer.appendInt32(74184410)
|
|
||||||
}
|
|
||||||
serializeInt32(filterId, buffer: buffer, boxed: false)
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(missingPeers.count))
|
|
||||||
for item in missingPeers {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(chats.count))
|
|
||||||
for item in chats {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
buffer.appendInt32(481674261)
|
|
||||||
buffer.appendInt32(Int32(users.count))
|
|
||||||
for item in users {
|
|
||||||
item.serialize(buffer, true)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
|
||||||
switch self {
|
|
||||||
case .communityInvite(let title, let peers, let chats, let users):
|
|
||||||
return ("communityInvite", [("title", title as Any), ("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)])
|
|
||||||
case .communityInviteAlready(let filterId, let missingPeers, let chats, let users):
|
|
||||||
return ("communityInviteAlready", [("filterId", filterId as Any), ("missingPeers", missingPeers as Any), ("chats", chats as Any), ("users", users as Any)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static func parse_communityInvite(_ reader: BufferReader) -> CommunityInvite? {
|
|
||||||
var _1: String?
|
|
||||||
_1 = parseString(reader)
|
|
||||||
var _2: [Api.Peer]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
|
|
||||||
}
|
|
||||||
var _3: [Api.Chat]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
|
||||||
}
|
|
||||||
var _4: [Api.User]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
|
||||||
}
|
|
||||||
let _c1 = _1 != nil
|
|
||||||
let _c2 = _2 != nil
|
|
||||||
let _c3 = _3 != nil
|
|
||||||
let _c4 = _4 != nil
|
|
||||||
if _c1 && _c2 && _c3 && _c4 {
|
|
||||||
return Api.community.CommunityInvite.communityInvite(title: _1!, peers: _2!, chats: _3!, users: _4!)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static func parse_communityInviteAlready(_ reader: BufferReader) -> CommunityInvite? {
|
|
||||||
var _1: Int32?
|
|
||||||
_1 = reader.readInt32()
|
|
||||||
var _2: [Api.Peer]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
|
|
||||||
}
|
|
||||||
var _3: [Api.Chat]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
|
||||||
}
|
|
||||||
var _4: [Api.User]?
|
|
||||||
if let _ = reader.readInt32() {
|
|
||||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
|
||||||
}
|
|
||||||
let _c1 = _1 != nil
|
|
||||||
let _c2 = _2 != nil
|
|
||||||
let _c3 = _3 != nil
|
|
||||||
let _c4 = _4 != nil
|
|
||||||
if _c1 && _c2 && _c3 && _c4 {
|
|
||||||
return Api.community.CommunityInvite.communityInviteAlready(filterId: _1!, missingPeers: _2!, chats: _3!, users: _4!)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,65 @@
|
|||||||
|
public extension Api.communities {
|
||||||
|
enum ExportedInvites: TypeConstructorDescription {
|
||||||
|
case exportedInvites(invites: [Api.ExportedCommunityInvite], chats: [Api.Chat], users: [Api.User])
|
||||||
|
|
||||||
|
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||||
|
switch self {
|
||||||
|
case .exportedInvites(let invites, let chats, let users):
|
||||||
|
if boxed {
|
||||||
|
buffer.appendInt32(-2662489)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(invites.count))
|
||||||
|
for item in invites {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(chats.count))
|
||||||
|
for item in chats {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(users.count))
|
||||||
|
for item in users {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||||
|
switch self {
|
||||||
|
case .exportedInvites(let invites, let chats, let users):
|
||||||
|
return ("exportedInvites", [("invites", invites as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func parse_exportedInvites(_ reader: BufferReader) -> ExportedInvites? {
|
||||||
|
var _1: [Api.ExportedCommunityInvite]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.ExportedCommunityInvite.self)
|
||||||
|
}
|
||||||
|
var _2: [Api.Chat]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||||
|
}
|
||||||
|
var _3: [Api.User]?
|
||||||
|
if let _ = reader.readInt32() {
|
||||||
|
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||||
|
}
|
||||||
|
let _c1 = _1 != nil
|
||||||
|
let _c2 = _2 != nil
|
||||||
|
let _c3 = _3 != nil
|
||||||
|
if _c1 && _c2 && _c3 {
|
||||||
|
return Api.communities.ExportedInvites.exportedInvites(invites: _1!, chats: _2!, users: _3!)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.contacts {
|
public extension Api.contacts {
|
||||||
enum Blocked: TypeConstructorDescription {
|
enum Blocked: TypeConstructorDescription {
|
||||||
case blocked(blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User])
|
case blocked(blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User])
|
||||||
|
@ -2953,15 +2953,15 @@ public extension Api.functions.channels {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
public extension Api.functions.communities {
|
public extension Api.functions.communities {
|
||||||
static func checkCommunityInvite(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.community.CommunityInvite>) {
|
static func checkCommunityInvite(slug: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.communities.CommunityInvite>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
buffer.appendInt32(-1753956947)
|
buffer.appendInt32(161196517)
|
||||||
serializeString(slug, buffer: buffer, boxed: false)
|
serializeString(slug, buffer: buffer, boxed: false)
|
||||||
return (FunctionDescription(name: "communities.checkCommunityInvite", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.community.CommunityInvite? in
|
return (FunctionDescription(name: "communities.checkCommunityInvite", parameters: [("slug", String(describing: slug))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.communities.CommunityInvite? in
|
||||||
let reader = BufferReader(buffer)
|
let reader = BufferReader(buffer)
|
||||||
var result: Api.community.CommunityInvite?
|
var result: Api.communities.CommunityInvite?
|
||||||
if let signature = reader.readInt32() {
|
if let signature = reader.readInt32() {
|
||||||
result = Api.parse(reader, signature: signature) as? Api.community.CommunityInvite
|
result = Api.parse(reader, signature: signature) as? Api.communities.CommunityInvite
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
@ -3092,6 +3092,46 @@ public extension Api.functions.communities {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public extension Api.functions.communities {
|
||||||
|
static func joinCommunityUpdates(community: Api.InputCommunity, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(1372856854)
|
||||||
|
community.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(peers.count))
|
||||||
|
for item in peers {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
return (FunctionDescription(name: "communities.joinCommunityUpdates", parameters: [("community", String(describing: community)), ("peers", String(describing: peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public extension Api.functions.communities {
|
||||||
|
static func leaveCommunity(community: Api.InputCommunity, peers: [Api.InputPeer]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
|
let buffer = Buffer()
|
||||||
|
buffer.appendInt32(903443807)
|
||||||
|
community.serialize(buffer, true)
|
||||||
|
buffer.appendInt32(481674261)
|
||||||
|
buffer.appendInt32(Int32(peers.count))
|
||||||
|
for item in peers {
|
||||||
|
item.serialize(buffer, true)
|
||||||
|
}
|
||||||
|
return (FunctionDescription(name: "communities.leaveCommunity", parameters: [("community", String(describing: community)), ("peers", String(describing: peers))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||||
|
let reader = BufferReader(buffer)
|
||||||
|
var result: Api.Updates?
|
||||||
|
if let signature = reader.readInt32() {
|
||||||
|
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
public extension Api.functions.contacts {
|
public extension Api.functions.contacts {
|
||||||
static func acceptContact(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
static func acceptContact(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||||
let buffer = Buffer()
|
let buffer = Buffer()
|
||||||
|
@ -15,6 +15,8 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
public let maxAboutLength: Int32
|
public let maxAboutLength: Int32
|
||||||
public let maxAnimatedEmojisInText: Int32
|
public let maxAnimatedEmojisInText: Int32
|
||||||
public let maxReactionsPerMessage: Int32
|
public let maxReactionsPerMessage: Int32
|
||||||
|
public let maxSharedFolderInviteLinks: Int32
|
||||||
|
public let maxSharedFolderJoin: Int32
|
||||||
|
|
||||||
public static var defaultValue: UserLimitsConfiguration {
|
public static var defaultValue: UserLimitsConfiguration {
|
||||||
return UserLimitsConfiguration(
|
return UserLimitsConfiguration(
|
||||||
@ -30,7 +32,9 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
maxUploadFileParts: 4000,
|
maxUploadFileParts: 4000,
|
||||||
maxAboutLength: 70,
|
maxAboutLength: 70,
|
||||||
maxAnimatedEmojisInText: 10,
|
maxAnimatedEmojisInText: 10,
|
||||||
maxReactionsPerMessage: 1
|
maxReactionsPerMessage: 1,
|
||||||
|
maxSharedFolderInviteLinks: 3,
|
||||||
|
maxSharedFolderJoin: 2
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +51,9 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
maxUploadFileParts: Int32,
|
maxUploadFileParts: Int32,
|
||||||
maxAboutLength: Int32,
|
maxAboutLength: Int32,
|
||||||
maxAnimatedEmojisInText: Int32,
|
maxAnimatedEmojisInText: Int32,
|
||||||
maxReactionsPerMessage: Int32
|
maxReactionsPerMessage: Int32,
|
||||||
|
maxSharedFolderInviteLinks: Int32,
|
||||||
|
maxSharedFolderJoin: Int32
|
||||||
) {
|
) {
|
||||||
self.maxPinnedChatCount = maxPinnedChatCount
|
self.maxPinnedChatCount = maxPinnedChatCount
|
||||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||||
@ -62,6 +68,8 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
self.maxAboutLength = maxAboutLength
|
self.maxAboutLength = maxAboutLength
|
||||||
self.maxAnimatedEmojisInText = maxAnimatedEmojisInText
|
self.maxAnimatedEmojisInText = maxAnimatedEmojisInText
|
||||||
self.maxReactionsPerMessage = maxReactionsPerMessage
|
self.maxReactionsPerMessage = maxReactionsPerMessage
|
||||||
|
self.maxSharedFolderInviteLinks = maxSharedFolderInviteLinks
|
||||||
|
self.maxSharedFolderJoin = maxSharedFolderJoin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,5 +107,7 @@ extension UserLimitsConfiguration {
|
|||||||
self.maxAboutLength = getValue("about_length_limit", orElse: defaultValue.maxAboutLength)
|
self.maxAboutLength = getValue("about_length_limit", orElse: defaultValue.maxAboutLength)
|
||||||
self.maxAnimatedEmojisInText = getGeneralValue("message_animated_emoji_max", orElse: defaultValue.maxAnimatedEmojisInText)
|
self.maxAnimatedEmojisInText = getGeneralValue("message_animated_emoji_max", orElse: defaultValue.maxAnimatedEmojisInText)
|
||||||
self.maxReactionsPerMessage = getValue("reactions_user_max", orElse: 1)
|
self.maxReactionsPerMessage = getValue("reactions_user_max", orElse: 1)
|
||||||
|
self.maxSharedFolderInviteLinks = getValue("community_invites_limit", orElse: 3)
|
||||||
|
self.maxSharedFolderJoin = getValue("communities_joined_limit", orElse: 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,8 @@ public enum EngineConfiguration {
|
|||||||
public let maxAboutLength: Int32
|
public let maxAboutLength: Int32
|
||||||
public let maxAnimatedEmojisInText: Int32
|
public let maxAnimatedEmojisInText: Int32
|
||||||
public let maxReactionsPerMessage: Int32
|
public let maxReactionsPerMessage: Int32
|
||||||
|
public let maxSharedFolderInviteLinks: Int32
|
||||||
|
public let maxSharedFolderJoin: Int32
|
||||||
|
|
||||||
public static var defaultValue: UserLimits {
|
public static var defaultValue: UserLimits {
|
||||||
return UserLimits(UserLimitsConfiguration.defaultValue)
|
return UserLimits(UserLimitsConfiguration.defaultValue)
|
||||||
@ -67,7 +69,9 @@ public enum EngineConfiguration {
|
|||||||
maxUploadFileParts: Int32,
|
maxUploadFileParts: Int32,
|
||||||
maxAboutLength: Int32,
|
maxAboutLength: Int32,
|
||||||
maxAnimatedEmojisInText: Int32,
|
maxAnimatedEmojisInText: Int32,
|
||||||
maxReactionsPerMessage: Int32
|
maxReactionsPerMessage: Int32,
|
||||||
|
maxSharedFolderInviteLinks: Int32,
|
||||||
|
maxSharedFolderJoin: Int32
|
||||||
) {
|
) {
|
||||||
self.maxPinnedChatCount = maxPinnedChatCount
|
self.maxPinnedChatCount = maxPinnedChatCount
|
||||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||||
@ -82,6 +86,8 @@ public enum EngineConfiguration {
|
|||||||
self.maxAboutLength = maxAboutLength
|
self.maxAboutLength = maxAboutLength
|
||||||
self.maxAnimatedEmojisInText = maxAnimatedEmojisInText
|
self.maxAnimatedEmojisInText = maxAnimatedEmojisInText
|
||||||
self.maxReactionsPerMessage = maxReactionsPerMessage
|
self.maxReactionsPerMessage = maxReactionsPerMessage
|
||||||
|
self.maxSharedFolderInviteLinks = maxSharedFolderInviteLinks
|
||||||
|
self.maxSharedFolderJoin = maxSharedFolderJoin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,7 +137,9 @@ public extension EngineConfiguration.UserLimits {
|
|||||||
maxUploadFileParts: userLimitsConfiguration.maxUploadFileParts,
|
maxUploadFileParts: userLimitsConfiguration.maxUploadFileParts,
|
||||||
maxAboutLength: userLimitsConfiguration.maxAboutLength,
|
maxAboutLength: userLimitsConfiguration.maxAboutLength,
|
||||||
maxAnimatedEmojisInText: userLimitsConfiguration.maxAnimatedEmojisInText,
|
maxAnimatedEmojisInText: userLimitsConfiguration.maxAnimatedEmojisInText,
|
||||||
maxReactionsPerMessage: userLimitsConfiguration.maxReactionsPerMessage
|
maxReactionsPerMessage: userLimitsConfiguration.maxReactionsPerMessage,
|
||||||
|
maxSharedFolderInviteLinks: userLimitsConfiguration.maxSharedFolderInviteLinks,
|
||||||
|
maxSharedFolderJoin: userLimitsConfiguration.maxSharedFolderJoin
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,24 @@ import SwiftSignalKit
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramApi
|
import TelegramApi
|
||||||
|
|
||||||
//communities.exportCommunityInvite#41fe69d9 community:InputCommunity title:string peers:Vector<InputPeer> = communities.ExportedCommunityInvite;
|
public func canShareLinkToPeer(peer: EnginePeer) -> Bool {
|
||||||
//communities.exportedCommunityInvite#6b97a8ea filter:DialogFilter invite:ExportedCommunityInvite = communities.ExportedCommunityInvite;
|
var isEnabled = false
|
||||||
//exportedCommunityInvite#af7afb2f title:string url:string peers:Vector<Peer> = ExportedCommunityInvite;
|
switch peer {
|
||||||
|
case let .channel(channel):
|
||||||
|
if channel.adminRights != nil && channel.hasPermission(.inviteMembers) {
|
||||||
|
isEnabled = true
|
||||||
|
} else if channel.username != nil {
|
||||||
|
isEnabled = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
public enum ExportChatFolderError {
|
public enum ExportChatFolderError {
|
||||||
case generic
|
case generic
|
||||||
|
case limitExceeded(limit: Int32, premiumLimit: Int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ExportedChatFolderLink: Equatable {
|
public struct ExportedChatFolderLink: Equatable {
|
||||||
@ -47,8 +59,25 @@ func _internal_exportChatFolder(account: Account, filterId: Int32, title: String
|
|||||||
|> castError(ExportChatFolderError.self)
|
|> castError(ExportChatFolderError.self)
|
||||||
|> 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))
|
||||||
|> mapError { _ -> ExportChatFolderError in
|
|> `catch` { error -> Signal<Api.communities.ExportedCommunityInvite, ExportChatFolderError> in
|
||||||
return .generic
|
if error.errorDescription == "INVITES_TOO_MUCH" {
|
||||||
|
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
|
||||||
|
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
|
||||||
|
}
|
||||||
|
|> castError(ExportChatFolderError.self)
|
||||||
|
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.communities.ExportedCommunityInvite, ExportChatFolderError> in
|
||||||
|
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
|
||||||
|
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
|
||||||
|
|
||||||
|
if isPremium {
|
||||||
|
return .fail(.limitExceeded(limit: userPremiumLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
|
||||||
|
} else {
|
||||||
|
return .fail(.limitExceeded(limit: userDefaultLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
|
|> mapToSignal { result -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
|
||||||
return account.postbox.transaction { transaction -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
|
return account.postbox.transaction { transaction -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
|
||||||
@ -178,9 +207,9 @@ public enum RevokeChatFolderLinkError {
|
|||||||
case generic
|
case generic
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_revokeChatFolderLink(account: Account, filterId: Int32, link: ExportedChatFolderLink) -> Signal<Never, RevokeChatFolderLinkError> {
|
func _internal_deleteChatFolderLink(account: Account, filterId: Int32, link: ExportedChatFolderLink) -> Signal<Never, RevokeChatFolderLinkError> {
|
||||||
return account.network.request(Api.functions.communities.deleteExportedInvite(community: .inputCommunityDialogFilter(filterId: filterId), slug: link.slug))
|
return account.network.request(Api.functions.communities.deleteExportedInvite(community: .inputCommunityDialogFilter(filterId: filterId), slug: link.slug))
|
||||||
|> mapError { _ -> RevokeChatFolderLinkError in
|
|> mapError { error -> RevokeChatFolderLinkError in
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
@ -248,9 +277,12 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
alreadyMemberPeerIds.removeAll()
|
||||||
|
|
||||||
return ChatFolderLinkContents(localFilterId: nil, title: title, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds)
|
return ChatFolderLinkContents(localFilterId: nil, title: title, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds)
|
||||||
case let .communityInviteAlready(filterId, missingPeers, chats, users):
|
case let .communityInviteAlready(filterId, missingPeers, alreadyPeers, chats, users):
|
||||||
|
let _ = alreadyPeers
|
||||||
|
|
||||||
var allPeers: [Peer] = []
|
var allPeers: [Peer] = []
|
||||||
var peerPresences: [PeerId: Api.User] = [:]
|
var peerPresences: [PeerId: Api.User] = [:]
|
||||||
|
|
||||||
@ -272,10 +304,12 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
|
|
||||||
let currentFilters = _internal_currentChatListFilters(transaction: transaction)
|
let currentFilters = _internal_currentChatListFilters(transaction: transaction)
|
||||||
var currentFilterTitle: String?
|
var currentFilterTitle: String?
|
||||||
|
var currentFilterPeers: [EnginePeer.Id] = []
|
||||||
if let index = currentFilters.firstIndex(where: { $0.id == filterId }) {
|
if let index = currentFilters.firstIndex(where: { $0.id == filterId }) {
|
||||||
switch currentFilters[index] {
|
switch currentFilters[index] {
|
||||||
case let .filter(_, title, _, _):
|
case let .filter(_, title, _, data):
|
||||||
currentFilterTitle = title
|
currentFilterTitle = title
|
||||||
|
currentFilterPeers = data.includePeers.peers
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -292,6 +326,20 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for peerId in currentFilterPeers {
|
||||||
|
if resultPeers.contains(where: { $0.id == peerId }) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if let peerValue = transaction.getPeer(peerId) {
|
||||||
|
if canShareLinkToPeer(peer: EnginePeer(peerValue)) {
|
||||||
|
resultPeers.append(EnginePeer(peerValue))
|
||||||
|
|
||||||
|
if transaction.getPeerChatListIndex(peerId) != nil {
|
||||||
|
alreadyMemberPeerIds.insert(peerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ChatFolderLinkContents(localFilterId: filterId, title: currentFilterTitle, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds)
|
return ChatFolderLinkContents(localFilterId: filterId, title: currentFilterTitle, peers: resultPeers, alreadyMemberPeerIds: alreadyMemberPeerIds)
|
||||||
}
|
}
|
||||||
@ -302,6 +350,8 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|
|||||||
|
|
||||||
public enum JoinChatFolderLinkError {
|
public enum JoinChatFolderLinkError {
|
||||||
case generic
|
case generic
|
||||||
|
case dialogFilterLimitExceeded(limit: Int32, premiumLimit: Int32)
|
||||||
|
case sharedFolderLimitExceeded(limit: Int32, premiumLimit: Int32)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
|
func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
|
||||||
@ -311,8 +361,40 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
|
|||||||
|> castError(JoinChatFolderLinkError.self)
|
|> castError(JoinChatFolderLinkError.self)
|
||||||
|> mapToSignal { inputPeers -> Signal<Never, JoinChatFolderLinkError> in
|
|> mapToSignal { inputPeers -> Signal<Never, JoinChatFolderLinkError> in
|
||||||
return account.network.request(Api.functions.communities.joinCommunityInvite(slug: slug, peers: inputPeers))
|
return account.network.request(Api.functions.communities.joinCommunityInvite(slug: slug, peers: inputPeers))
|
||||||
|> mapError { _ -> JoinChatFolderLinkError in
|
|> `catch` { error -> Signal<Api.Updates, JoinChatFolderLinkError> in
|
||||||
return .generic
|
if error.errorDescription == "DIALOG_FILTERS_TOO_MUCH" {
|
||||||
|
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
|
||||||
|
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
|
||||||
|
}
|
||||||
|
|> castError(JoinChatFolderLinkError.self)
|
||||||
|
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.Updates, JoinChatFolderLinkError> in
|
||||||
|
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
|
||||||
|
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
|
||||||
|
|
||||||
|
if isPremium {
|
||||||
|
return .fail(.dialogFilterLimitExceeded(limit: userPremiumLimits.maxFoldersCount, premiumLimit: userPremiumLimits.maxFoldersCount))
|
||||||
|
} else {
|
||||||
|
return .fail(.dialogFilterLimitExceeded(limit: userDefaultLimits.maxFoldersCount, premiumLimit: userPremiumLimits.maxFoldersCount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if error.errorDescription == "FILTERS_TOO_MUCH" {
|
||||||
|
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
|
||||||
|
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
|
||||||
|
}
|
||||||
|
|> castError(JoinChatFolderLinkError.self)
|
||||||
|
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.Updates, JoinChatFolderLinkError> in
|
||||||
|
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
|
||||||
|
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
|
||||||
|
|
||||||
|
if isPremium {
|
||||||
|
return .fail(.sharedFolderLimitExceeded(limit: userPremiumLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
|
||||||
|
} else {
|
||||||
|
return .fail(.sharedFolderLimitExceeded(limit: userDefaultLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> mapToSignal { result -> Signal<Never, JoinChatFolderLinkError> in
|
|> mapToSignal { result -> Signal<Never, JoinChatFolderLinkError> in
|
||||||
account.stateManager.addUpdates(result)
|
account.stateManager.addUpdates(result)
|
||||||
@ -321,3 +403,157 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
public var availableChatsToJoin: Int {
|
||||||
|
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> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<ChatFolderUpdates?, NoError> in
|
||||||
|
guard let result = result else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
switch result {
|
||||||
|
case let .communityUpdates(missingPeers, chats, 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, 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: updates.folderId), peers: inputPeers))
|
||||||
|
|> `catch` { error -> Signal<Api.Updates, JoinChatFolderLinkError> in
|
||||||
|
if error.errorDescription == "DIALOG_FILTERS_TOO_MUCH" {
|
||||||
|
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
|
||||||
|
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
|
||||||
|
}
|
||||||
|
|> castError(JoinChatFolderLinkError.self)
|
||||||
|
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.Updates, JoinChatFolderLinkError> in
|
||||||
|
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
|
||||||
|
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
|
||||||
|
|
||||||
|
if isPremium {
|
||||||
|
return .fail(.dialogFilterLimitExceeded(limit: userPremiumLimits.maxFoldersCount, premiumLimit: userPremiumLimits.maxFoldersCount))
|
||||||
|
} else {
|
||||||
|
return .fail(.dialogFilterLimitExceeded(limit: userDefaultLimits.maxFoldersCount, premiumLimit: userPremiumLimits.maxFoldersCount))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if error.errorDescription == "FILTERS_TOO_MUCH" {
|
||||||
|
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
|
||||||
|
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
|
||||||
|
}
|
||||||
|
|> castError(JoinChatFolderLinkError.self)
|
||||||
|
|> mapToSignal { appConfiguration, isPremium -> Signal<Api.Updates, JoinChatFolderLinkError> in
|
||||||
|
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
|
||||||
|
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
|
||||||
|
|
||||||
|
if isPremium {
|
||||||
|
return .fail(.sharedFolderLimitExceeded(limit: userPremiumLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
|
||||||
|
} else {
|
||||||
|
return .fail(.sharedFolderLimitExceeded(limit: userDefaultLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .fail(.generic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> mapToSignal { result -> Signal<Never, JoinChatFolderLinkError> in
|
||||||
|
account.stateManager.addUpdates(result)
|
||||||
|
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_hideChatFolderUpdates(account: Account, folderId: Int32) -> Signal<Never, NoError> {
|
||||||
|
return account.network.request(Api.functions.communities.hideCommunityUpdates(community: .inputCommunityDialogFilter(filterId: folderId)))
|
||||||
|
|> `catch` { _ -> Signal<Api.Bool, NoError> in
|
||||||
|
return .single(.boolFalse)
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_leaveChatFolder(account: Account, folderId: Int32, removePeerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
|
||||||
|
return account.postbox.transaction { transaction -> [Api.InputPeer] in
|
||||||
|
return removePeerIds.compactMap(transaction.getPeer).compactMap(apiInputPeer)
|
||||||
|
}
|
||||||
|
|> mapToSignal { inputPeers -> Signal<Never, NoError> in
|
||||||
|
return account.network.request(Api.functions.communities.leaveCommunity(community: .inputCommunityDialogFilter(filterId: folderId), peers: inputPeers))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { updates -> Signal<Never, NoError> in
|
||||||
|
if let updates = updates {
|
||||||
|
account.stateManager.addUpdates(updates)
|
||||||
|
}
|
||||||
|
return account.postbox.transaction { transaction -> Void in
|
||||||
|
}
|
||||||
|
|> ignoreValues
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,7 +9,7 @@ public enum ConvertGroupToSupergroupError {
|
|||||||
case tooManyChannels
|
case tooManyChannels
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_convertGroupToSupergroup(account: Account, peerId: PeerId) -> Signal<PeerId, ConvertGroupToSupergroupError> {
|
func _internal_convertGroupToSupergroup(account: Account, peerId: PeerId, additionalProcessing: ((EnginePeer.Id) -> Signal<Never, NoError>)?) -> Signal<PeerId, ConvertGroupToSupergroupError> {
|
||||||
return account.network.request(Api.functions.messages.migrateChat(chatId: peerId.id._internalGetInt64Value()))
|
return account.network.request(Api.functions.messages.migrateChat(chatId: peerId.id._internalGetInt64Value()))
|
||||||
|> mapError { error -> ConvertGroupToSupergroupError in
|
|> mapError { error -> ConvertGroupToSupergroupError in
|
||||||
if error.errorDescription == "CHANNELS_TOO_MUCH" {
|
if error.errorDescription == "CHANNELS_TOO_MUCH" {
|
||||||
@ -20,7 +20,6 @@ func _internal_convertGroupToSupergroup(account: Account, peerId: PeerId) -> Sig
|
|||||||
}
|
}
|
||||||
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
|
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
|
||||||
|> mapToSignal { updates -> Signal<PeerId, ConvertGroupToSupergroupError> in
|
|> mapToSignal { updates -> Signal<PeerId, ConvertGroupToSupergroupError> in
|
||||||
account.stateManager.addUpdates(updates)
|
|
||||||
var createdPeerId: PeerId?
|
var createdPeerId: PeerId?
|
||||||
for message in updates.messages {
|
for message in updates.messages {
|
||||||
if apiMessagePeerId(message) != peerId {
|
if apiMessagePeerId(message) != peerId {
|
||||||
@ -30,19 +29,35 @@ func _internal_convertGroupToSupergroup(account: Account, peerId: PeerId) -> Sig
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let createdPeerId = createdPeerId {
|
if let createdPeerId = createdPeerId {
|
||||||
return account.postbox.multiplePeersView([createdPeerId])
|
let additionalProcessingValue: Signal<Never, NoError> = additionalProcessing?(createdPeerId) ?? Signal<Never, NoError>.complete()
|
||||||
|> filter { view in
|
|
||||||
return view.peers[createdPeerId] != nil
|
return additionalProcessingValue
|
||||||
|
|> map { _ -> Bool in }
|
||||||
|
|> castError(ConvertGroupToSupergroupError.self)
|
||||||
|
|> then(Signal<Bool, ConvertGroupToSupergroupError>.single(true))
|
||||||
|
|> mapToSignal { _ ->Signal<PeerId, ConvertGroupToSupergroupError> in
|
||||||
|
account.stateManager.addUpdates(updates)
|
||||||
|
|
||||||
|
return _internal_fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: createdPeerId, network: account.network, postbox: account.postbox)
|
||||||
|
|> castError(ConvertGroupToSupergroupError.self)
|
||||||
|
|> mapToSignal { _ -> Signal<PeerId, ConvertGroupToSupergroupError> in
|
||||||
|
return account.postbox.multiplePeersView([createdPeerId])
|
||||||
|
|> filter { view in
|
||||||
|
return view.peers[createdPeerId] != nil
|
||||||
|
}
|
||||||
|
|> take(1)
|
||||||
|
|> map { _ in
|
||||||
|
return createdPeerId
|
||||||
|
}
|
||||||
|
|> mapError { _ -> ConvertGroupToSupergroupError in
|
||||||
|
}
|
||||||
|
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|> take(1)
|
} else {
|
||||||
|> map { _ in
|
account.stateManager.addUpdates(updates)
|
||||||
return createdPeerId
|
return .fail(.generic)
|
||||||
}
|
|
||||||
|> mapError { _ -> ConvertGroupToSupergroupError in
|
|
||||||
}
|
|
||||||
|> timeout(5.0, queue: Queue.concurrentDefaultQueue(), alternate: .fail(.generic))
|
|
||||||
}
|
}
|
||||||
return .fail(.generic)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,8 +134,8 @@ public extension TelegramEngine {
|
|||||||
return _internal_chatOnlineMembers(postbox: self.account.postbox, network: self.account.network, peerId: peerId)
|
return _internal_chatOnlineMembers(postbox: self.account.postbox, network: self.account.network, peerId: peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func convertGroupToSupergroup(peerId: PeerId) -> Signal<PeerId, ConvertGroupToSupergroupError> {
|
public func convertGroupToSupergroup(peerId: PeerId, additionalProcessing: ((EnginePeer.Id) -> Signal<Never, NoError>)? = nil) -> Signal<PeerId, ConvertGroupToSupergroupError> {
|
||||||
return _internal_convertGroupToSupergroup(account: self.account, peerId: peerId)
|
return _internal_convertGroupToSupergroup(account: self.account, peerId: peerId, additionalProcessing: additionalProcessing)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func createGroup(title: String, peerIds: [PeerId], ttlPeriod: Int32?) -> Signal<CreateGroupResult?, CreateGroupError> {
|
public func createGroup(title: String, peerIds: [PeerId], ttlPeriod: Int32?) -> Signal<CreateGroupResult?, CreateGroupError> {
|
||||||
@ -1046,8 +1046,8 @@ public extension TelegramEngine {
|
|||||||
return _internal_editChatFolderLink(account: self.account, filterId: filterId, link: link, title: title, peerIds: peerIds, revoke: revoke)
|
return _internal_editChatFolderLink(account: self.account, filterId: filterId, link: link, title: title, peerIds: peerIds, revoke: revoke)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func revokeChatFolderLink(filterId: Int32, link: ExportedChatFolderLink) -> Signal<Never, RevokeChatFolderLinkError> {
|
public func deleteChatFolderLink(filterId: Int32, link: ExportedChatFolderLink) -> Signal<Never, RevokeChatFolderLinkError> {
|
||||||
return _internal_revokeChatFolderLink(account: self.account, filterId: filterId, link: link)
|
return _internal_deleteChatFolderLink(account: self.account, filterId: filterId, link: link)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func checkChatFolderLink(slug: String) -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> {
|
public func checkChatFolderLink(slug: String) -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> {
|
||||||
@ -1057,6 +1057,22 @@ public extension TelegramEngine {
|
|||||||
public func joinChatFolderLink(slug: String, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
|
public func joinChatFolderLink(slug: String, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
|
||||||
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> {
|
||||||
|
return _internal_getChatFolderUpdates(account: self.account, folderId: folderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
||||||
|
return _internal_hideChatFolderUpdates(account: self.account, folderId: folderId)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func leaveChatFolder(folderId: Int32, removePeerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {
|
||||||
|
return _internal_leaveChatFolder(account: self.account, folderId: folderId, removePeerIds: removePeerIds)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ swift_library(
|
|||||||
"//submodules/CheckNode",
|
"//submodules/CheckNode",
|
||||||
"//submodules/Markdown",
|
"//submodules/Markdown",
|
||||||
"//submodules/UndoUI",
|
"//submodules/UndoUI",
|
||||||
|
"//submodules/PremiumUI",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -7,6 +7,81 @@ import AccountContext
|
|||||||
import MultilineTextComponent
|
import MultilineTextComponent
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
|
||||||
|
final class BadgeComponent: Component {
|
||||||
|
let fillColor: UIColor
|
||||||
|
let content: AnyComponent<Empty>
|
||||||
|
|
||||||
|
init(
|
||||||
|
fillColor: UIColor,
|
||||||
|
content: AnyComponent<Empty>
|
||||||
|
) {
|
||||||
|
self.fillColor = fillColor
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: BadgeComponent, rhs: BadgeComponent) -> Bool {
|
||||||
|
if lhs.fillColor != rhs.fillColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.content != rhs.content {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIView {
|
||||||
|
private let backgroundLayer: SimpleLayer
|
||||||
|
private let content = ComponentView<Empty>()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.backgroundLayer = SimpleLayer()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.layer.addSublayer(self.backgroundLayer)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: BadgeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
let height: CGFloat = 20.0
|
||||||
|
let contentInset: CGFloat = 10.0
|
||||||
|
|
||||||
|
let contentSize = self.content.update(
|
||||||
|
transition: transition,
|
||||||
|
component: component.content,
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
|
)
|
||||||
|
let backgroundWidth: CGFloat = max(height, contentSize.width + contentInset)
|
||||||
|
let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: backgroundWidth, height: height))
|
||||||
|
|
||||||
|
transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame)
|
||||||
|
transition.setBackgroundColor(layer: self.backgroundLayer, color: component.fillColor)
|
||||||
|
transition.setCornerRadius(layer: self.backgroundLayer, cornerRadius: height / 2.0)
|
||||||
|
|
||||||
|
if let contentView = self.content.view {
|
||||||
|
if contentView.superview == nil {
|
||||||
|
self.addSubview(contentView)
|
||||||
|
}
|
||||||
|
transition.setFrame(view: contentView, frame: CGRect(origin: CGPoint(x: floor((backgroundFrame.width - contentSize.width) * 0.5), y: floor((backgroundFrame.height - contentSize.height) * 0.5)), size: contentSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
return backgroundFrame.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class ChatFolderLinkHeaderComponent: Component {
|
final class ChatFolderLinkHeaderComponent: Component {
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
let strings: PresentationStrings
|
let strings: PresentationStrings
|
||||||
@ -47,6 +122,8 @@ final class ChatFolderLinkHeaderComponent: Component {
|
|||||||
private let title = ComponentView<Empty>()
|
private let title = ComponentView<Empty>()
|
||||||
private let separatorLayer = SimpleLayer()
|
private let separatorLayer = SimpleLayer()
|
||||||
|
|
||||||
|
private var badge: ComponentView<Empty>?
|
||||||
|
|
||||||
private var component: ChatFolderLinkHeaderComponent?
|
private var component: ChatFolderLinkHeaderComponent?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
@ -71,6 +148,7 @@ final class ChatFolderLinkHeaderComponent: Component {
|
|||||||
|
|
||||||
let height: CGFloat = 60.0
|
let height: CGFloat = 60.0
|
||||||
let spacing: CGFloat = 16.0
|
let spacing: CGFloat = 16.0
|
||||||
|
let badgeSpacing: CGFloat = 6.0
|
||||||
|
|
||||||
if themeUpdated {
|
if themeUpdated {
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
@ -143,6 +221,39 @@ final class ChatFolderLinkHeaderComponent: Component {
|
|||||||
)
|
)
|
||||||
contentWidth += titleSize.width
|
contentWidth += titleSize.width
|
||||||
|
|
||||||
|
var badgeSize: CGSize?
|
||||||
|
if let badge = component.badge {
|
||||||
|
let badgeContainer: ComponentView<Empty>
|
||||||
|
var badgeTransition = transition
|
||||||
|
if let current = self.badge {
|
||||||
|
badgeContainer = current
|
||||||
|
} else {
|
||||||
|
badgeTransition = .immediate
|
||||||
|
badgeContainer = ComponentView()
|
||||||
|
self.badge = badgeContainer
|
||||||
|
}
|
||||||
|
let badgeSizeValue = badgeContainer.update(
|
||||||
|
transition: badgeTransition,
|
||||||
|
component: AnyComponent(BadgeComponent(
|
||||||
|
fillColor: component.theme.list.itemCheckColors.fillColor,
|
||||||
|
content: AnyComponent(Text(text: badge, font: Font.semibold(12.0), color: component.theme.list.itemCheckColors.foregroundColor))
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||||
|
)
|
||||||
|
badgeSize = badgeSizeValue
|
||||||
|
contentWidth += badgeSpacing + badgeSizeValue.width
|
||||||
|
} else {
|
||||||
|
if let badge = self.badge {
|
||||||
|
self.badge = nil
|
||||||
|
if let view = badge.view {
|
||||||
|
transition.setScale(view: view, scale: 0.0001, completion: { [weak view] _ in
|
||||||
|
view?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
contentWidth += spacing
|
contentWidth += spacing
|
||||||
if let rightImage = self.rightView.image {
|
if let rightImage = self.rightView.image {
|
||||||
contentWidth += rightImage.size.width
|
contentWidth += rightImage.size.width
|
||||||
@ -163,9 +274,35 @@ final class ChatFolderLinkHeaderComponent: Component {
|
|||||||
let titleFrame = CGRect(origin: CGPoint(x: contentOriginX, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
let titleFrame = CGRect(origin: CGPoint(x: contentOriginX, y: floor((height - titleSize.height) / 2.0)), size: titleSize)
|
||||||
transition.setFrame(view: titleView, frame: titleFrame)
|
transition.setFrame(view: titleView, frame: titleFrame)
|
||||||
|
|
||||||
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + 9.0), size: CGSize(width: titleFrame.width, height: 3.0)))
|
var separatorWidth = titleFrame.width
|
||||||
|
|
||||||
|
if let badgeSize, let badge = self.badge {
|
||||||
|
let badgeFrame = CGRect(origin: CGPoint(x: titleFrame.maxX + badgeSpacing, y: titleFrame.minY + 1.0 + floor((titleFrame.height - badgeSize.height) * 0.5)), size: badgeSize)
|
||||||
|
|
||||||
|
separatorWidth += badgeSpacing + badgeSize.width
|
||||||
|
|
||||||
|
if let badgeView = badge.view {
|
||||||
|
var badgeTransition = transition
|
||||||
|
var animateIn = false
|
||||||
|
if badgeView.superview == nil {
|
||||||
|
badgeTransition = .immediate
|
||||||
|
self.addSubview(badgeView)
|
||||||
|
animateIn = true
|
||||||
|
}
|
||||||
|
badgeTransition.setFrame(view: badgeView, frame: badgeFrame)
|
||||||
|
if animateIn {
|
||||||
|
transition.animateScale(view: badgeView, from: 0.0001, to: 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + 9.0), size: CGSize(width: separatorWidth, height: 3.0)))
|
||||||
}
|
}
|
||||||
contentOriginX += titleSize.width
|
contentOriginX += titleSize.width
|
||||||
|
if let badgeSize {
|
||||||
|
contentOriginX += badgeSpacing
|
||||||
|
contentOriginX += badgeSize.width
|
||||||
|
}
|
||||||
contentOriginX += spacing
|
contentOriginX += spacing
|
||||||
|
|
||||||
if let rightImage = self.rightView.image {
|
if let rightImage = self.rightView.image {
|
||||||
|
@ -15,29 +15,33 @@ import SolidRoundedButtonComponent
|
|||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
import Markdown
|
import Markdown
|
||||||
import UndoUI
|
import UndoUI
|
||||||
|
import PremiumUI
|
||||||
|
|
||||||
private final class ChatFolderLinkPreviewScreenComponent: Component {
|
private final class ChatFolderLinkPreviewScreenComponent: Component {
|
||||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let slug: String
|
let subject: ChatFolderLinkPreviewScreen.Subject
|
||||||
let linkContents: ChatFolderLinkContents?
|
let linkContents: ChatFolderLinkContents?
|
||||||
|
let completion: (() -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
slug: String,
|
subject: ChatFolderLinkPreviewScreen.Subject,
|
||||||
linkContents: ChatFolderLinkContents?
|
linkContents: ChatFolderLinkContents?,
|
||||||
|
completion: (() -> Void)?
|
||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.slug = slug
|
self.subject = subject
|
||||||
self.linkContents = linkContents
|
self.linkContents = linkContents
|
||||||
|
self.completion = completion
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: ChatFolderLinkPreviewScreenComponent, rhs: ChatFolderLinkPreviewScreenComponent) -> Bool {
|
static func ==(lhs: ChatFolderLinkPreviewScreenComponent, rhs: ChatFolderLinkPreviewScreenComponent) -> Bool {
|
||||||
if lhs.context !== rhs.context {
|
if lhs.context !== rhs.context {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.slug != rhs.slug {
|
if lhs.subject != rhs.subject {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.linkContents !== rhs.linkContents {
|
if lhs.linkContents !== rhs.linkContents {
|
||||||
@ -66,6 +70,11 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class AnimationHint {
|
||||||
|
init() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class View: UIView, UIScrollViewDelegate {
|
final class View: UIView, UIScrollViewDelegate {
|
||||||
private let dimView: UIView
|
private let dimView: UIView
|
||||||
private let backgroundLayer: SimpleLayer
|
private let backgroundLayer: SimpleLayer
|
||||||
@ -82,6 +91,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
private let actionButton = ComponentView<Empty>()
|
private let actionButton = ComponentView<Empty>()
|
||||||
|
|
||||||
private let listHeaderText = ComponentView<Empty>()
|
private let listHeaderText = ComponentView<Empty>()
|
||||||
|
private let listHeaderAction = ComponentView<Empty>()
|
||||||
private let itemContainerView: UIView
|
private let itemContainerView: UIView
|
||||||
private var items: [AnyHashable: ComponentView<Empty>] = [:]
|
private var items: [AnyHashable: ComponentView<Empty>] = [:]
|
||||||
|
|
||||||
@ -100,6 +110,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
|
|
||||||
private var joinDisposable: Disposable?
|
private var joinDisposable: Disposable?
|
||||||
|
|
||||||
|
private var inProgress: Bool = false
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.bottomOverscrollLimit = 200.0
|
self.bottomOverscrollLimit = 200.0
|
||||||
|
|
||||||
@ -240,7 +252,11 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: @escaping () -> Void) {
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
let animateOffset: CGFloat = self.backgroundLayer.frame.minY
|
if let controller = self.environment?.controller() {
|
||||||
|
controller.updateModalStyleOverlayTransitionFactor(0.0, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||||
|
}
|
||||||
|
|
||||||
|
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||||
|
|
||||||
self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
|
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
|
||||||
@ -254,6 +270,13 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func update(component: ChatFolderLinkPreviewScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
func update(component: ChatFolderLinkPreviewScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: Transition) -> CGSize {
|
||||||
|
let animationHint = transition.userData(AnimationHint.self)
|
||||||
|
|
||||||
|
var contentTransition = transition
|
||||||
|
if animationHint != nil {
|
||||||
|
contentTransition = .immediate
|
||||||
|
}
|
||||||
|
|
||||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
let themeUpdated = self.environment?.theme !== environment.theme
|
let themeUpdated = self.environment?.theme !== environment.theme
|
||||||
|
|
||||||
@ -282,7 +305,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
var contentHeight: CGFloat = 0.0
|
var contentHeight: CGFloat = 0.0
|
||||||
|
|
||||||
let leftButtonSize = self.leftButton.update(
|
let leftButtonSize = self.leftButton.update(
|
||||||
transition: transition,
|
transition: contentTransition,
|
||||||
component: AnyComponent(Button(
|
component: AnyComponent(Button(
|
||||||
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)),
|
content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)),
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
@ -304,10 +327,19 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let titleString: String
|
let titleString: String
|
||||||
|
var allChatsAdded = false
|
||||||
if let linkContents = component.linkContents {
|
if let linkContents = component.linkContents {
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
if linkContents.localFilterId != nil {
|
if case .remove = component.subject {
|
||||||
if self.selectedItems.count == 1 {
|
titleString = "Remove Folder"
|
||||||
|
} else if linkContents.localFilterId != nil {
|
||||||
|
if linkContents.alreadyMemberPeerIds == Set(linkContents.peers.map(\.id)) {
|
||||||
|
allChatsAdded = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if allChatsAdded {
|
||||||
|
titleString = "Add Folder"
|
||||||
|
} else if self.selectedItems.count == 1 {
|
||||||
titleString = "Add \(self.selectedItems.count) chat"
|
titleString = "Add \(self.selectedItems.count) chat"
|
||||||
} else {
|
} else {
|
||||||
titleString = "Add \(self.selectedItems.count) chats"
|
titleString = "Add \(self.selectedItems.count) chats"
|
||||||
@ -331,19 +363,24 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
if titleView.superview == nil {
|
if titleView.superview == nil {
|
||||||
self.navigationBarContainer.addSubview(titleView)
|
self.navigationBarContainer.addSubview(titleView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: titleView, frame: titleFrame)
|
contentTransition.setFrame(view: titleView, frame: titleFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentHeight += 44.0
|
contentHeight += 44.0
|
||||||
contentHeight += 14.0
|
contentHeight += 14.0
|
||||||
|
|
||||||
|
var topBadge: String?
|
||||||
|
if !allChatsAdded, let linkContents = component.linkContents, linkContents.localFilterId != nil {
|
||||||
|
topBadge = "+\(linkContents.peers.count)"
|
||||||
|
}
|
||||||
|
|
||||||
let topIconSize = self.topIcon.update(
|
let topIconSize = self.topIcon.update(
|
||||||
transition: transition,
|
transition: contentTransition,
|
||||||
component: AnyComponent(ChatFolderLinkHeaderComponent(
|
component: AnyComponent(ChatFolderLinkHeaderComponent(
|
||||||
theme: environment.theme,
|
theme: environment.theme,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
title: component.linkContents?.title ?? "Folder",
|
title: component.linkContents?.title ?? "Folder",
|
||||||
badge: nil
|
badge: topBadge
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: availableSize.width - sideInset, height: 1000.0)
|
containerSize: CGSize(width: availableSize.width - sideInset, height: 1000.0)
|
||||||
@ -353,7 +390,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
if topIconView.superview == nil {
|
if topIconView.superview == nil {
|
||||||
self.scrollContentView.addSubview(topIconView)
|
self.scrollContentView.addSubview(topIconView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: topIconView, frame: topIconFrame)
|
contentTransition.setFrame(view: topIconView, frame: topIconFrame)
|
||||||
topIconView.isHidden = component.linkContents == nil
|
topIconView.isHidden = component.linkContents == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,7 +399,11 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
|
|
||||||
let text: String
|
let text: String
|
||||||
if let linkContents = component.linkContents {
|
if let linkContents = component.linkContents {
|
||||||
if linkContents.localFilterId == nil {
|
if case .remove = component.subject {
|
||||||
|
text = "Do you want to quit the chats you joined when\nadding the folder \(linkContents.title ?? "Folder")?"
|
||||||
|
} else if allChatsAdded {
|
||||||
|
text = "You have already added this\nfolder and its chats."
|
||||||
|
} else if linkContents.localFilterId == nil {
|
||||||
text = "Do you want to add a new chat folder\nand join its groups and channels?"
|
text = "Do you want to add a new chat folder\nand join its groups and channels?"
|
||||||
} else {
|
} else {
|
||||||
let chatCountString: String
|
let chatCountString: String
|
||||||
@ -404,7 +445,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
if descriptionTextView.superview == nil {
|
if descriptionTextView.superview == nil {
|
||||||
self.scrollContentView.addSubview(descriptionTextView)
|
self.scrollContentView.addSubview(descriptionTextView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: descriptionTextView, frame: descriptionTextFrame)
|
contentTransition.setFrame(view: descriptionTextView, frame: descriptionTextFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentHeight += descriptionTextFrame.height
|
contentHeight += descriptionTextFrame.height
|
||||||
@ -446,15 +487,34 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
selectionState: .editing(isSelected: self.selectedItems.contains(peer.id), isTinted: linkContents.alreadyMemberPeerIds.contains(peer.id)),
|
selectionState: .editing(isSelected: self.selectedItems.contains(peer.id), isTinted: linkContents.alreadyMemberPeerIds.contains(peer.id)),
|
||||||
hasNext: i != linkContents.peers.count - 1,
|
hasNext: i != linkContents.peers.count - 1,
|
||||||
action: { [weak self] peer in
|
action: { [weak self] peer in
|
||||||
guard let self else {
|
guard let self, let component = self.component, let linkContents = component.linkContents, let controller = self.environment?.controller() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.selectedItems.contains(peer.id) {
|
|
||||||
self.selectedItems.remove(peer.id)
|
if case .remove = component.subject {
|
||||||
|
if self.selectedItems.contains(peer.id) {
|
||||||
|
self.selectedItems.remove(peer.id)
|
||||||
|
} else {
|
||||||
|
self.selectedItems.insert(peer.id)
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .easeInOut)))
|
||||||
|
} else if linkContents.alreadyMemberPeerIds.contains(peer.id) {
|
||||||
|
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let text: String
|
||||||
|
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||||
|
text = "You are already a member of this channel."
|
||||||
|
} else {
|
||||||
|
text = "You are already a member of this group."
|
||||||
|
}
|
||||||
|
controller.present(UndoOverlayController(presentationData: presentationData, content: .peers(context: component.context, peers: [peer], title: nil, text: text, customUndoText: nil), elevatedLayout: false, action: { _ in true }), in: .current)
|
||||||
} else {
|
} else {
|
||||||
self.selectedItems.insert(peer.id)
|
if self.selectedItems.contains(peer.id) {
|
||||||
|
self.selectedItems.remove(peer.id)
|
||||||
|
} else {
|
||||||
|
self.selectedItems.insert(peer.id)
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .easeInOut)))
|
||||||
}
|
}
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .easeInOut)))
|
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -487,13 +547,39 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let listHeaderTitle: String
|
let listHeaderTitle: String
|
||||||
if self.selectedItems.count == 1 {
|
if let linkContents = component.linkContents {
|
||||||
listHeaderTitle = "1 CHAT IN FOLDER TO JOIN"
|
if case .remove = component.subject {
|
||||||
|
if linkContents.peers.count == 1 {
|
||||||
|
listHeaderTitle = "1 CHAT TO QUIT"
|
||||||
|
} else {
|
||||||
|
listHeaderTitle = "\(linkContents.peers.count) CHATS TO QUIT"
|
||||||
|
}
|
||||||
|
} else if allChatsAdded {
|
||||||
|
if linkContents.peers.count == 1 {
|
||||||
|
listHeaderTitle = "1 CHAT IN THIS FOLDER"
|
||||||
|
} else {
|
||||||
|
listHeaderTitle = "\(linkContents.peers.count) CHATS IN THIS FOLDER"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if linkContents.peers.count == 1 {
|
||||||
|
listHeaderTitle = "1 CHAT IN FOLDER TO JOIN"
|
||||||
|
} else {
|
||||||
|
listHeaderTitle = "\(linkContents.peers.count) CHATS IN FOLDER TO JOIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
listHeaderTitle = "\(self.selectedItems.count) CHATS IN FOLDER TO JOIN"
|
listHeaderTitle = " "
|
||||||
|
}
|
||||||
|
|
||||||
|
let listHeaderActionTitle: String
|
||||||
|
if self.selectedItems.count == self.items.count {
|
||||||
|
listHeaderActionTitle = "DESELECT ALL"
|
||||||
|
} else {
|
||||||
|
listHeaderActionTitle = "SELECT ALL"
|
||||||
}
|
}
|
||||||
|
|
||||||
let listHeaderBody = MarkdownAttributeSet(font: Font.with(size: 13.0, design: .regular, traits: [.monospacedNumbers]), textColor: environment.theme.list.freeTextColor)
|
let listHeaderBody = MarkdownAttributeSet(font: Font.with(size: 13.0, design: .regular, traits: [.monospacedNumbers]), textColor: environment.theme.list.freeTextColor)
|
||||||
|
let listHeaderActionBody = MarkdownAttributeSet(font: Font.with(size: 13.0, design: .regular, traits: [.monospacedNumbers]), textColor: environment.theme.list.itemAccentColor)
|
||||||
|
|
||||||
let listHeaderTextSize = self.listHeaderText.update(
|
let listHeaderTextSize = self.listHeaderText.update(
|
||||||
transition: .immediate,
|
transition: .immediate,
|
||||||
@ -517,14 +603,60 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
self.scrollContentView.addSubview(listHeaderTextView)
|
self.scrollContentView.addSubview(listHeaderTextView)
|
||||||
}
|
}
|
||||||
let listHeaderTextFrame = CGRect(origin: CGPoint(x: sideInset + 15.0, y: contentHeight), size: listHeaderTextSize)
|
let listHeaderTextFrame = CGRect(origin: CGPoint(x: sideInset + 15.0, y: contentHeight), size: listHeaderTextSize)
|
||||||
transition.setPosition(view: listHeaderTextView, position: listHeaderTextFrame.origin)
|
contentTransition.setPosition(view: listHeaderTextView, position: listHeaderTextFrame.origin)
|
||||||
listHeaderTextView.bounds = CGRect(origin: CGPoint(), size: listHeaderTextFrame.size)
|
listHeaderTextView.bounds = CGRect(origin: CGPoint(), size: listHeaderTextFrame.size)
|
||||||
listHeaderTextView.isHidden = component.linkContents == nil
|
listHeaderTextView.isHidden = component.linkContents == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let listHeaderActionSize = self.listHeaderAction.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(Button(
|
||||||
|
content: AnyComponent(MultilineTextComponent(
|
||||||
|
text: .markdown(
|
||||||
|
text: listHeaderActionTitle,
|
||||||
|
attributes: MarkdownAttributes(
|
||||||
|
body: listHeaderActionBody,
|
||||||
|
bold: listHeaderActionBody,
|
||||||
|
link: listHeaderActionBody,
|
||||||
|
linkAttribute: { _ in nil }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)),
|
||||||
|
action: { [weak self] in
|
||||||
|
guard let self, let component = self.component, let linkContents = component.linkContents else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.selectedItems.count != linkContents.peers.count {
|
||||||
|
for peer in linkContents.peers {
|
||||||
|
self.selectedItems.insert(peer.id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.selectedItems.removeAll()
|
||||||
|
for peerId in linkContents.alreadyMemberPeerIds {
|
||||||
|
self.selectedItems.insert(peerId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0 - 15.0, height: 1000.0)
|
||||||
|
)
|
||||||
|
if let listHeaderActionView = self.listHeaderAction.view {
|
||||||
|
if listHeaderActionView.superview == nil {
|
||||||
|
listHeaderActionView.layer.anchorPoint = CGPoint(x: 1.0, y: 0.0)
|
||||||
|
self.scrollContentView.addSubview(listHeaderActionView)
|
||||||
|
}
|
||||||
|
let listHeaderActionFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - 15.0 - listHeaderActionSize.width, y: contentHeight), size: listHeaderActionSize)
|
||||||
|
contentTransition.setPosition(view: listHeaderActionView, position: CGPoint(x: listHeaderActionFrame.maxX, y: listHeaderActionFrame.minY))
|
||||||
|
listHeaderActionView.bounds = CGRect(origin: CGPoint(), size: listHeaderActionFrame.size)
|
||||||
|
listHeaderActionView.isHidden = component.linkContents == nil || allChatsAdded
|
||||||
|
}
|
||||||
|
|
||||||
contentHeight += listHeaderTextSize.height
|
contentHeight += listHeaderTextSize.height
|
||||||
contentHeight += 6.0
|
contentHeight += 6.0
|
||||||
|
|
||||||
transition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: itemsHeight)))
|
contentTransition.setFrame(view: self.itemContainerView, frame: CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: CGSize(width: availableSize.width - sideInset * 2.0, height: itemsHeight)))
|
||||||
|
|
||||||
var initialContentHeight = contentHeight
|
var initialContentHeight = contentHeight
|
||||||
initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5))
|
initialContentHeight += min(itemsHeight, floor(singleItemHeight * 2.5))
|
||||||
@ -534,9 +666,21 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
initialContentHeight += 24.0
|
initialContentHeight += 24.0
|
||||||
|
|
||||||
let actionButtonTitle: String
|
let actionButtonTitle: String
|
||||||
if let linkContents = component.linkContents {
|
if case .remove = component.subject {
|
||||||
|
if self.selectedItems.isEmpty {
|
||||||
|
actionButtonTitle = "Remove Folder"
|
||||||
|
} else {
|
||||||
|
actionButtonTitle = "Remove Folder and Chats"
|
||||||
|
}
|
||||||
|
} else if allChatsAdded {
|
||||||
|
actionButtonTitle = "OK"
|
||||||
|
} else if let linkContents = component.linkContents {
|
||||||
if linkContents.localFilterId != nil {
|
if linkContents.localFilterId != nil {
|
||||||
actionButtonTitle = "Join Chats"
|
if self.selectedItems.isEmpty {
|
||||||
|
actionButtonTitle = "Do Not Join Any Chats"
|
||||||
|
} else {
|
||||||
|
actionButtonTitle = "Join Chats"
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
actionButtonTitle = "Add Folder"
|
actionButtonTitle = "Add Folder"
|
||||||
}
|
}
|
||||||
@ -548,57 +692,81 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
transition: transition,
|
transition: transition,
|
||||||
component: AnyComponent(SolidRoundedButtonComponent(
|
component: AnyComponent(SolidRoundedButtonComponent(
|
||||||
title: actionButtonTitle,
|
title: actionButtonTitle,
|
||||||
badge: (self.selectedItems.isEmpty) ? nil : "\(self.selectedItems.count)",
|
badge: (self.selectedItems.isEmpty || allChatsAdded) ? nil : "\(self.selectedItems.count)",
|
||||||
theme: SolidRoundedButtonComponent.Theme(theme: environment.theme),
|
theme: SolidRoundedButtonComponent.Theme(theme: environment.theme),
|
||||||
font: .bold,
|
font: .bold,
|
||||||
fontSize: 17.0,
|
fontSize: 17.0,
|
||||||
height: 50.0,
|
height: 50.0,
|
||||||
cornerRadius: 11.0,
|
cornerRadius: 11.0,
|
||||||
gloss: false,
|
gloss: false,
|
||||||
isEnabled: !self.selectedItems.isEmpty,
|
isEnabled: !self.selectedItems.isEmpty || component.linkContents?.localFilterId != nil,
|
||||||
animationName: nil,
|
animationName: nil,
|
||||||
iconPosition: .right,
|
iconPosition: .right,
|
||||||
iconSpacing: 4.0,
|
iconSpacing: 4.0,
|
||||||
isLoading: component.linkContents == nil,
|
isLoading: self.inProgress,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
guard let self, let component = self.component else {
|
guard let self, let component = self.component, let controller = self.environment?.controller() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let _ = component.linkContents {
|
if case let .remove(folderId) = component.subject {
|
||||||
|
self.inProgress = true
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
|
component.completion?()
|
||||||
|
|
||||||
|
self.joinDisposable = (component.context.engine.peers.leaveChatFolder(folderId: folderId, removePeerIds: Array(self.selectedItems))
|
||||||
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
|
guard let self, let controller = self.environment?.controller() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.dismiss()
|
||||||
|
})
|
||||||
|
} else if allChatsAdded {
|
||||||
|
controller.dismiss()
|
||||||
|
} else if let _ = component.linkContents {
|
||||||
if self.joinDisposable == nil, !self.selectedItems.isEmpty {
|
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>
|
||||||
|> deliverOnMainQueue).start(completed: { [weak self] in
|
switch component.subject {
|
||||||
|
case .remove:
|
||||||
|
return
|
||||||
|
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.inProgress = true
|
||||||
|
self.state?.updated(transition: .immediate)
|
||||||
|
|
||||||
|
self.joinDisposable = (joinSignal
|
||||||
|
|> deliverOnMainQueue).start(error: { [weak self] error in
|
||||||
|
guard let self, let component = self.component, let controller = self.environment?.controller() else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch error {
|
||||||
|
case .generic:
|
||||||
|
controller.dismiss()
|
||||||
|
case let .dialogFilterLimitExceeded(limit, _):
|
||||||
|
let limitController = PremiumLimitScreen(context: component.context, subject: .folders, count: limit, action: {})
|
||||||
|
controller.push(limitController)
|
||||||
|
controller.dismiss()
|
||||||
|
case let .sharedFolderLimitExceeded(limit, _):
|
||||||
|
let limitController = PremiumLimitScreen(context: component.context, subject: .membershipInSharedFolders, count: limit, action: {})
|
||||||
|
controller.push(limitController)
|
||||||
|
controller.dismiss()
|
||||||
|
}
|
||||||
|
}, completed: { [weak self] in
|
||||||
guard let self, let controller = self.environment?.controller() else {
|
guard let self, let controller = self.environment?.controller() else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
controller.dismiss()
|
controller.dismiss()
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
controller.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if self.selectedItems.isEmpty {
|
|
||||||
controller.dismiss()
|
|
||||||
} else if let link = component.link {
|
|
||||||
let selectedPeers = component.peers.filter { self.selectedItems.contains($0.id) }
|
|
||||||
|
|
||||||
let _ = enqueueMessagesToMultiplePeers(account: component.context.account, peerIds: Array(self.selectedItems), threadIds: [:], messages: [.message(text: link, attributes: [], inlineStickers: [:], mediaReference: nil, replyToMessageId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]).start()
|
|
||||||
let text: String
|
|
||||||
if selectedPeers.count == 1 {
|
|
||||||
text = environment.strings.Conversation_ShareLinkTooltip_Chat_One(selectedPeers[0].displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
|
|
||||||
} else if selectedPeers.count == 2 {
|
|
||||||
text = environment.strings.Conversation_ShareLinkTooltip_TwoChats_One(selectedPeers[0].displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), selectedPeers[1].displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: "")).string
|
|
||||||
} else {
|
|
||||||
text = environment.strings.Conversation_ShareLinkTooltip_ManyChats_One(selectedPeers[0].displayTitle(strings: environment.strings, displayOrder: .firstLast).replacingOccurrences(of: "*", with: ""), "\(selectedPeers.count - 1)").string
|
|
||||||
}
|
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
controller.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, action: { _ in return false }), in: .window(.root))
|
|
||||||
|
|
||||||
controller.dismiss()
|
|
||||||
} else {
|
|
||||||
controller.dismiss()
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -613,6 +781,19 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
|
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let controller = environment.controller() {
|
||||||
|
let subLayout = ContainerViewLayout(
|
||||||
|
size: availableSize, metrics: environment.metrics, deviceMetrics: environment.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: sideInset - 12.0, bottom: bottomPanelHeight, right: sideInset),
|
||||||
|
safeInsets: UIEdgeInsets(),
|
||||||
|
additionalInsets: UIEdgeInsets(),
|
||||||
|
statusBarHeight: nil,
|
||||||
|
inputHeight: nil,
|
||||||
|
inputHeightIsInteractivellyChanging: false,
|
||||||
|
inVoiceOver: false
|
||||||
|
)
|
||||||
|
controller.presentationContext.containerLayoutUpdated(subLayout, transition: transition.containedViewLayoutTransition)
|
||||||
|
}
|
||||||
|
|
||||||
contentHeight += bottomPanelHeight
|
contentHeight += bottomPanelHeight
|
||||||
initialContentHeight += bottomPanelHeight
|
initialContentHeight += bottomPanelHeight
|
||||||
|
|
||||||
@ -660,30 +841,26 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer {
|
public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer {
|
||||||
|
public enum Subject: Equatable {
|
||||||
|
case slug(String)
|
||||||
|
case updates(ChatFolderUpdates)
|
||||||
|
case remove(folderId: Int32)
|
||||||
|
}
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var linkContents: ChatFolderLinkContents?
|
|
||||||
private var linkContentsDisposable: Disposable?
|
private var linkContentsDisposable: Disposable?
|
||||||
|
|
||||||
private var isDismissed: Bool = false
|
private var isDismissed: Bool = false
|
||||||
|
|
||||||
public init(context: AccountContext, slug: String) {
|
public init(context: AccountContext, subject: Subject, contents: ChatFolderLinkContents, completion: (() -> Void)? = nil) {
|
||||||
self.context = context
|
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, completion: completion), navigationBarAppearance: .none)
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = .Ignore
|
self.statusBar.statusBarStyle = .Ignore
|
||||||
self.navigationPresentation = .flatModal
|
self.navigationPresentation = .flatModal
|
||||||
self.blocksBackgroundWhenInOverlay = true
|
self.blocksBackgroundWhenInOverlay = true
|
||||||
|
self.automaticallyControlPresentationContextLayout = false
|
||||||
self.linkContentsDisposable = (context.engine.peers.checkChatFolderLink(slug: slug)
|
|
||||||
//|> 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: .immediate)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
required public init(coder aDecoder: NSCoder) {
|
||||||
@ -694,6 +871,10 @@ public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer {
|
|||||||
self.linkContentsDisposable?.dispose()
|
self.linkContentsDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
}
|
||||||
|
|
||||||
override public func viewDidAppear(_ animated: Bool) {
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
@ -265,6 +265,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||||||
}, openStorageManagement: {
|
}, openStorageManagement: {
|
||||||
}, openPasswordSetup: {
|
}, openPasswordSetup: {
|
||||||
}, openPremiumIntro: {
|
}, openPremiumIntro: {
|
||||||
|
}, openChatFolderUpdates: {
|
||||||
})
|
})
|
||||||
interaction.searchTextHighightState = searchQuery
|
interaction.searchTextHighightState = searchQuery
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
@ -753,7 +753,48 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
|||||||
}
|
}
|
||||||
case let .chatFolder(slug):
|
case let .chatFolder(slug):
|
||||||
if let navigationController = navigationController {
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -837,7 +837,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleResolvedUrl(.premiumOffer(reference: reference))
|
handleResolvedUrl(.premiumOffer(reference: reference))
|
||||||
} else if parsedUrl.host == "folder" {
|
} else if parsedUrl.host == "list" {
|
||||||
if let components = URLComponents(string: "/?" + query) {
|
if let components = URLComponents(string: "/?" + query) {
|
||||||
var slug: String?
|
var slug: String?
|
||||||
if let queryItems = components.queryItems {
|
if let queryItems = components.queryItems {
|
||||||
@ -850,7 +850,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let slug = slug {
|
if let slug = slug {
|
||||||
convertedUrl = "https://t.me/folder/\(slug)"
|
convertedUrl = "https://t.me/list/\(slug)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -350,40 +350,42 @@ enum PeerInfoMembersData: Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func peerInfoScreenInputData(context: AccountContext, peerId: EnginePeer.Id, isSettings: Bool) -> Signal<PeerInfoScreenInputData, NoError> {
|
private func peerInfoScreenInputData(context: AccountContext, peerId: EnginePeer.Id, isSettings: Bool) -> Signal<PeerInfoScreenInputData, NoError> {
|
||||||
return context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
return `deferred` {
|
||||||
|> map { peer -> PeerInfoScreenInputData in
|
return context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|
||||||
guard let peer = peer else {
|
|> mapToSignal { peer -> Signal<PeerInfoScreenInputData, NoError> in
|
||||||
return .none
|
guard let peer = peer else {
|
||||||
}
|
return .single(.none)
|
||||||
if case let .user(user) = peer {
|
}
|
||||||
if isSettings && user.id == context.account.peerId {
|
if case let .user(user) = peer {
|
||||||
return .settings
|
if isSettings && user.id == context.account.peerId {
|
||||||
} else {
|
return .single(.settings)
|
||||||
let kind: PeerInfoScreenInputUserKind
|
|
||||||
if user.flags.contains(.isSupport) {
|
|
||||||
kind = .support
|
|
||||||
} else if user.botInfo != nil {
|
|
||||||
kind = .bot
|
|
||||||
} else {
|
} else {
|
||||||
kind = .user
|
let kind: PeerInfoScreenInputUserKind
|
||||||
|
if user.flags.contains(.isSupport) {
|
||||||
|
kind = .support
|
||||||
|
} else if user.botInfo != nil {
|
||||||
|
kind = .bot
|
||||||
|
} else {
|
||||||
|
kind = .user
|
||||||
|
}
|
||||||
|
return .single(.user(userId: user.id, secretChatId: nil, kind: kind))
|
||||||
}
|
}
|
||||||
return .user(userId: user.id, secretChatId: nil, kind: kind)
|
} else if case let .channel(channel) = peer {
|
||||||
}
|
if case .group = channel.info {
|
||||||
} else if case let .channel(channel) = peer {
|
return .single(.group(groupId: channel.id))
|
||||||
if case .group = channel.info {
|
} else {
|
||||||
return .group(groupId: channel.id)
|
return .single(.channel)
|
||||||
|
}
|
||||||
|
} else if case let .legacyGroup(group) = peer {
|
||||||
|
return .single(.group(groupId: group.id))
|
||||||
|
} else if case let .secretChat(secretChat) = peer {
|
||||||
|
return .single(.user(userId: secretChat.regularPeerId, secretChatId: peer.id, kind: .user))
|
||||||
} else {
|
} else {
|
||||||
return .channel
|
return .single(.none)
|
||||||
}
|
}
|
||||||
} else if case let .legacyGroup(group) = peer {
|
|
||||||
return .group(groupId: group.id)
|
|
||||||
} else if case let .secretChat(secretChat) = peer {
|
|
||||||
return .user(userId: secretChat.regularPeerId, secretChatId: peer.id, kind: .user)
|
|
||||||
} else {
|
|
||||||
return .none
|
|
||||||
}
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
}
|
}
|
||||||
|> distinctUntilChanged
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<Never, NoError> {
|
func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<Never, NoError> {
|
||||||
@ -547,6 +549,8 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
|||||||
func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<PeerInfoScreenData, NoError> {
|
func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, isSettings: Bool, hintGroupInCommon: PeerId?, existingRequestsContext: PeerInvitationImportersContext?, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<PeerInfoScreenData, NoError> {
|
||||||
return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: isSettings)
|
return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: isSettings)
|
||||||
|> mapToSignal { inputData -> Signal<PeerInfoScreenData, NoError> in
|
|> mapToSignal { inputData -> Signal<PeerInfoScreenData, NoError> in
|
||||||
|
let wasUpgradedGroup = Atomic<Bool?>(value: nil)
|
||||||
|
|
||||||
switch inputData {
|
switch inputData {
|
||||||
case .none, .settings:
|
case .none, .settings:
|
||||||
return .single(PeerInfoScreenData(
|
return .single(PeerInfoScreenData(
|
||||||
@ -914,7 +918,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
threadData,
|
threadData,
|
||||||
context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|
||||||
)
|
)
|
||||||
|> map { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView -> PeerInfoScreenData in
|
|> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, threadData, preferencesView -> Signal<PeerInfoScreenData, NoError> in
|
||||||
var discussionPeer: Peer?
|
var discussionPeer: Peer?
|
||||||
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
|
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
|
||||||
discussionPeer = peer
|
discussionPeer = peer
|
||||||
@ -931,6 +935,11 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
|
|
||||||
var canManageInvitations = false
|
var canManageInvitations = false
|
||||||
if let group = peerViewMainPeer(peerView) as? TelegramGroup {
|
if let group = peerViewMainPeer(peerView) as? TelegramGroup {
|
||||||
|
let previousValue = wasUpgradedGroup.swap(group.migrationReference != nil)
|
||||||
|
if group.migrationReference != nil, let previousValue, !previousValue {
|
||||||
|
return .never()
|
||||||
|
}
|
||||||
|
|
||||||
if case .creator = group.role {
|
if case .creator = group.role {
|
||||||
canManageInvitations = true
|
canManageInvitations = true
|
||||||
} else if case let .admin(rights, _) = group.role, rights.rights.contains(.canInviteUsers) {
|
} else if case let .admin(rights, _) = group.role, rights.rights.contains(.canInviteUsers) {
|
||||||
@ -960,7 +969,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
|
|
||||||
let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue
|
let appConfiguration: AppConfiguration = preferencesView.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? .defaultValue
|
||||||
|
|
||||||
return PeerInfoScreenData(
|
return .single(PeerInfoScreenData(
|
||||||
peer: peerView.peers[groupId],
|
peer: peerView.peers[groupId],
|
||||||
chatPeer: peerView.peers[groupId],
|
chatPeer: peerView.peers[groupId],
|
||||||
cachedData: peerView.cachedData,
|
cachedData: peerView.cachedData,
|
||||||
@ -981,7 +990,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
|||||||
threadData: threadData,
|
threadData: threadData,
|
||||||
appConfiguration: appConfiguration,
|
appConfiguration: appConfiguration,
|
||||||
isPowerSavingEnabled: nil
|
isPowerSavingEnabled: nil
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1847,6 +1847,8 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||||||
let ItemAdmins = 105
|
let ItemAdmins = 105
|
||||||
let ItemMemberRequests = 106
|
let ItemMemberRequests = 106
|
||||||
let ItemReactions = 107
|
let ItemReactions = 107
|
||||||
|
let ItemTopics = 108
|
||||||
|
let ItemTopicsText = 109
|
||||||
|
|
||||||
var canViewAdminsAndBanned = false
|
var canViewAdminsAndBanned = false
|
||||||
|
|
||||||
@ -1876,6 +1878,33 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
|||||||
interaction.editingOpenPreHistorySetup()
|
interaction.editingOpenPreHistorySetup()
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
var canSetupTopics = false
|
||||||
|
if case .creator = group.role {
|
||||||
|
canSetupTopics = true
|
||||||
|
}
|
||||||
|
var topicsLimitedReason: TopicsLimitedReason?
|
||||||
|
if let appConfiguration = data.appConfiguration {
|
||||||
|
var minParticipants = 200
|
||||||
|
if let data = appConfiguration.data, let value = data["forum_upgrade_participants_min"] as? Double {
|
||||||
|
minParticipants = Int(value)
|
||||||
|
}
|
||||||
|
if Int(group.participantCount) < minParticipants {
|
||||||
|
topicsLimitedReason = .participants(minParticipants)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if canSetupTopics {
|
||||||
|
items[.peerPublicSettings]!.append(PeerInfoScreenSwitchItem(id: ItemTopics, text: presentationData.strings.PeerInfo_OptionTopics, value: false, icon: UIImage(bundleImageName: "Settings/Menu/Topics"), isLocked: topicsLimitedReason != nil, toggled: { value in
|
||||||
|
if let topicsLimitedReason = topicsLimitedReason {
|
||||||
|
interaction.displayTopicsLimited(topicsLimitedReason)
|
||||||
|
} else {
|
||||||
|
interaction.toggleForumTopics(value)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
items[.peerPublicSettings]!.append(PeerInfoScreenCommentItem(id: ItemTopicsText, text: presentationData.strings.PeerInfo_OptionTopicsText))
|
||||||
|
}
|
||||||
|
|
||||||
let label: String
|
let label: String
|
||||||
if let cachedData = data.cachedData as? CachedGroupData, case let .known(allowedReactions) = cachedData.allowedReactions {
|
if let cachedData = data.cachedData as? CachedGroupData, case let .known(allowedReactions) = cachedData.allowedReactions {
|
||||||
switch allowedReactions {
|
switch allowedReactions {
|
||||||
@ -2252,7 +2281,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = strongSelf.context.engine.peers.setChannelForumMode(id: strongSelf.peerId, isForum: value).start()
|
strongSelf.toggleForumTopics(isEnabled: value)
|
||||||
},
|
},
|
||||||
displayTopicsLimited: { [weak self] reason in
|
displayTopicsLimited: { [weak self] reason in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -6605,6 +6634,61 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
self.controller?.push(peerAllowedReactionListController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id))
|
self.controller?.push(peerAllowedReactionListController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func toggleForumTopics(isEnabled: Bool) {
|
||||||
|
guard let data = self.data, let peer = data.peer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if peer is TelegramGroup {
|
||||||
|
if isEnabled {
|
||||||
|
let context = self.context
|
||||||
|
let signal: Signal<EnginePeer.Id?, NoError> = self.context.engine.peers.convertGroupToSupergroup(peerId: self.peerId, additionalProcessing: { upgradedPeerId -> Signal<Never, NoError> in
|
||||||
|
return context.engine.peers.setChannelForumMode(id: upgradedPeerId, isForum: isEnabled)
|
||||||
|
})
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { [weak self] error -> Signal<PeerId?, NoError> in
|
||||||
|
switch error {
|
||||||
|
case .tooManyChannels:
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
self?.controller?.push(oldChannelsController(context: context, intent: .upgrade))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> mapToSignal { upgradedPeerId -> Signal<PeerId?, NoError> in
|
||||||
|
guard let upgradedPeerId = upgradedPeerId else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
return .single(upgradedPeerId)
|
||||||
|
}
|
||||||
|
|> deliverOnMainQueue
|
||||||
|
|
||||||
|
let _ = signal.start(next: { [weak self] resultPeerId in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let resultPeerId else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (self.context.engine.peers.setChannelForumMode(id: resultPeerId, isForum: isEnabled)
|
||||||
|
|> deliverOnMainQueue).start(completed: { [weak self] in
|
||||||
|
guard let self, let controller = self.controller else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/*if let navigationController = controller.navigationController as? NavigationController {
|
||||||
|
rebuildControllerStackAfterSupergroupUpgrade(controller: controller, navigationController: navigationController)
|
||||||
|
}*/
|
||||||
|
controller.dismiss()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = self.context.engine.peers.setChannelForumMode(id: self.peerId, isForum: isEnabled).start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func editingToggleMessageSignatures(value: Bool) {
|
private func editingToggleMessageSignatures(value: Bool) {
|
||||||
self.toggleShouldChannelMessagesSignaturesDisposable.set(self.context.engine.peers.toggleShouldChannelMessagesSignatures(peerId: self.peerId, enabled: value).start())
|
self.toggleShouldChannelMessagesSignaturesDisposable.set(self.context.engine.peers.toggleShouldChannelMessagesSignatures(peerId: self.peerId, enabled: value).start())
|
||||||
}
|
}
|
||||||
|
@ -1676,6 +1676,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
mappedSubject = .files
|
mappedSubject = .files
|
||||||
case .accounts:
|
case .accounts:
|
||||||
mappedSubject = .accounts
|
mappedSubject = .accounts
|
||||||
|
case .linksPerSharedFolder:
|
||||||
|
mappedSubject = .linksPerSharedFolder
|
||||||
|
case .membershipInSharedFolders:
|
||||||
|
mappedSubject = .membershipInSharedFolders
|
||||||
}
|
}
|
||||||
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, action: action)
|
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, action: action)
|
||||||
}
|
}
|
||||||
|
@ -1180,17 +1180,18 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
var contentHeight: CGFloat = 20.0
|
var contentHeight: CGFloat = 20.0
|
||||||
|
|
||||||
let margin: CGFloat = 12.0
|
let margin: CGFloat = 12.0
|
||||||
|
let leftMargin = 12.0 + layout.insets(options: []).left
|
||||||
|
|
||||||
let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
let buttonTextSize = self.undoButtonTextNode.updateLayout(CGSize(width: 200.0, height: .greatestFiniteMagnitude))
|
||||||
let buttonMinX: CGFloat
|
let buttonMinX: CGFloat
|
||||||
if self.undoButtonNode.supernode != nil {
|
if self.undoButtonNode.supernode != nil {
|
||||||
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset - buttonTextSize.width - margin * 2.0
|
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset - buttonTextSize.width - leftMargin * 2.0
|
||||||
} else {
|
} else {
|
||||||
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset
|
buttonMinX = layout.size.width - layout.safeInsets.left - rightInset
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleSize = self.titleNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - margin, height: .greatestFiniteMagnitude))
|
let titleSize = self.titleNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude))
|
||||||
let textSize = self.textNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - margin, height: .greatestFiniteMagnitude))
|
let textSize = self.textNode.updateLayout(CGSize(width: buttonMinX - 8.0 - leftInset - layout.safeInsets.left - leftMargin, height: .greatestFiniteMagnitude))
|
||||||
|
|
||||||
if !titleSize.width.isZero {
|
if !titleSize.width.isZero {
|
||||||
contentHeight += titleSize.height + 1.0
|
contentHeight += titleSize.height + 1.0
|
||||||
@ -1209,8 +1210,8 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var panelFrame = CGRect(origin: CGPoint(x: margin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
|
var panelFrame = CGRect(origin: CGPoint(x: leftMargin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
|
||||||
var panelWrapperFrame = CGRect(origin: CGPoint(x: margin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
|
var panelWrapperFrame = CGRect(origin: CGPoint(x: leftMargin + layout.safeInsets.left, y: layout.size.height - contentHeight - insets.bottom - margin), size: CGSize(width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight))
|
||||||
|
|
||||||
if case .top = self.placementPosition {
|
if case .top = self.placementPosition {
|
||||||
panelFrame.origin.y = insets.top + margin
|
panelFrame.origin.y = insets.top + margin
|
||||||
@ -1219,12 +1220,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
|
|
||||||
transition.updateFrame(node: self.panelNode, frame: panelFrame)
|
transition.updateFrame(node: self.panelNode, frame: panelFrame)
|
||||||
transition.updateFrame(node: self.panelWrapperNode, frame: panelWrapperFrame)
|
transition.updateFrame(node: self.panelWrapperNode, frame: panelWrapperFrame)
|
||||||
self.effectView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width - margin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight)
|
self.effectView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width - leftMargin * 2.0 - layout.safeInsets.left - layout.safeInsets.right, height: contentHeight)
|
||||||
|
|
||||||
let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - margin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize)
|
let buttonTextFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - leftMargin * 2.0, y: floor((contentHeight - buttonTextSize.height) / 2.0)), size: buttonTextSize)
|
||||||
transition.updateFrame(node: self.undoButtonTextNode, frame: buttonTextFrame)
|
transition.updateFrame(node: self.undoButtonTextNode, frame: buttonTextFrame)
|
||||||
|
|
||||||
let undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - margin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + margin, height: contentHeight))
|
let undoButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - rightInset - buttonTextSize.width - 8.0 - leftMargin * 2.0, y: 0.0), size: CGSize(width: layout.safeInsets.right + rightInset + buttonTextSize.width + 8.0 + leftMargin, height: contentHeight))
|
||||||
self.undoButtonNode.frame = undoButtonFrame
|
self.undoButtonNode.frame = undoButtonFrame
|
||||||
|
|
||||||
self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: undoButtonFrame.minX, height: contentHeight))
|
self.buttonNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: undoButtonFrame.minX, height: contentHeight))
|
||||||
|
@ -418,7 +418,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
return .wallpaper(parameter)
|
return .wallpaper(parameter)
|
||||||
} else if pathComponents[0] == "addtheme" {
|
} else if pathComponents[0] == "addtheme" {
|
||||||
return .theme(pathComponents[1])
|
return .theme(pathComponents[1])
|
||||||
} else if pathComponents[0] == "folder" {
|
} else if pathComponents[0] == "list" || pathComponents[0] == "folder" {
|
||||||
return .chatFolder(slug: pathComponents[1])
|
return .chatFolder(slug: pathComponents[1])
|
||||||
} else if pathComponents.count == 3 && pathComponents[0] == "c" {
|
} else if pathComponents.count == 3 && pathComponents[0] == "c" {
|
||||||
if let channelId = Int64(pathComponents[1]), let messageId = Int32(pathComponents[2]) {
|
if let channelId = Int64(pathComponents[1]), let messageId = Int32(pathComponents[2]) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"app": "9.5.3",
|
"app": "9.5.4",
|
||||||
"bazel": "5.3.1",
|
"bazel": "5.3.1",
|
||||||
"xcode": "14.2"
|
"xcode": "14.2"
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user