Folder improvements

This commit is contained in:
Ali 2023-03-28 15:54:38 +04:00
parent 8d79979422
commit 6c57ab1765
19 changed files with 597 additions and 294 deletions

View File

@ -9097,3 +9097,11 @@ Sorry for the inconvenience.";
"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";

View File

@ -896,6 +896,8 @@ public enum PremiumLimitSubject {
case pins
case files
case accounts
case linksPerSharedFolder
case membershipInSharedFolders
}
public protocol ComposeController: ViewController {

View File

@ -44,6 +44,7 @@ import ComponentDisplayAdapters
import ChatListHeaderComponent
import ChatListTitleView
import InviteLinksUI
import ChatFolderLinkPreviewScreen
private func fixListNodeScrolling(_ listNode: ListView, searchNode: NavigationBarSearchContentNode) -> Bool {
if listNode.scroller.isDragging {
@ -2735,7 +2736,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
}
let _ = (context.engine.peers.currentChatListFilters()
let _ = (self.context.engine.peers.currentChatListFilters()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] filters in
guard let self else {
@ -2745,17 +2746,55 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
return
}
if case let .filter(_, _, _, data) = filter, data.isShared {
let presentationData = self.presentationData
//TODO:localize
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Folder", text: "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.", actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
apply()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
]), in: .window(.root))
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
}
let presentationData = self.presentationData
//TODO:localize
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Folder", text: "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.", actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: { [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: {
})
]), in: .window(.root))
})
} else {
let actionSheet = ActionSheetController(presentationData: self.presentationData)

View File

@ -1334,7 +1334,14 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
switch error {
case .generic:
text = "An error occurred"
case .limitExceeded:
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 }

View File

@ -12,6 +12,7 @@ import ItemListPeerActionItem
import ChatListFilterSettingsHeaderItem
import PremiumUI
import UndoUI
import ChatFolderLinkPreviewScreen
private final class ChatListFilterPresetListControllerArguments {
let context: AccountContext
@ -389,22 +390,38 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if case let .filter(_, _, _, data) = filter, data.isShared {
//TODO:localize
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Folder", text: "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.", actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
let _ = (context.engine.peers.updateChatListFiltersInteractively { filters in
var filters = filters
if let index = filters.firstIndex(where: { $0.id == id }) {
filters.remove(at: index)
}
return filters
}
|> deliverOnMainQueue).start()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
]))
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 }
//TODO:localize
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Folder", text: "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.", actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
let previewScreen = ChatFolderLinkPreviewScreen(
context: 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()
)
)
pushControllerImpl?(previewScreen)
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
]))
})
} else {
let actionSheet = ActionSheetController(presentationData: presentationData)

View File

@ -125,6 +125,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
private let titleNode: TextNode
private let labelNode: TextNode
private let arrowNode: ASImageNode
private let sharedIconNode: ASImageNode
private let activateArea: AccessibilityAreaNode
@ -169,6 +170,11 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
self.arrowNode.displaysAsynchronously = false
self.arrowNode.isLayerBacked = true
self.sharedIconNode = ASImageNode()
self.sharedIconNode.displayWithoutProcessing = true
self.sharedIconNode.displaysAsynchronously = false
self.sharedIconNode.isLayerBacked = true
self.activateArea = AccessibilityAreaNode()
self.highlightedBackgroundNode = ASDisplayNode()
@ -180,6 +186,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
self.containerNode.addSubnode(self.titleNode)
self.containerNode.addSubnode(self.labelNode)
self.containerNode.addSubnode(self.arrowNode)
self.containerNode.addSubnode(self.sharedIconNode)
self.addSubnode(self.activateArea)
self.activateArea.activate = { [weak self] in
@ -199,6 +206,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
return { item, params, neighbors in
var updatedTheme: PresentationTheme?
var updateArrowImage: UIImage?
var updatedSharedIconImage: UIImage?
if currentItem?.presentationData.theme !== item.presentationData.theme || currentItem?.isDisabled != item.isDisabled {
updatedTheme = item.presentationData.theme
@ -207,6 +215,7 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
} else {
updateArrowImage = PresentationResourcesItemList.disclosureArrowImage(item.presentationData.theme)
}
updatedSharedIconImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Links/Share"), color: item.presentationData.theme.list.disclosureArrowColor)
}
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.arrowNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
transition.updateAlpha(node: strongSelf.sharedIconNode, alpha: reorderControlSizeAndApply != nil ? 0.0 : 1.0)
if let updateArrowImage = updateArrowImage {
strongSelf.arrowNode.image = updateArrowImage
}
if let updatedSharedIconImage {
strongSelf.sharedIconNode.image = updatedSharedIconImage
}
if let arrowImage = strongSelf.arrowNode.image {
var rightArrowInset = 0.0
@ -393,6 +406,15 @@ private final class ChatListFilterPresetListItemNode: ItemListRevealOptionsItemN
}
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.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
arrowFrame.origin.x = params.width - params.rightInset - 7.0 - arrowFrame.width + revealOffset
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() {

View File

@ -765,6 +765,36 @@ private final class LimitSheetContent: CombinedComponent {
badgeText = "\(limit)"
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:
let limit = state.limits.maxPinnedChatCount
let premiumLimit = state.premiumLimits.maxPinnedChatCount
@ -1048,6 +1078,8 @@ public class PremiumLimitScreen: ViewControllerComponentContainer {
case pins
case files
case accounts
case linksPerSharedFolder
case membershipInSharedFolders
}
public init(context: AccountContext, subject: PremiumLimitScreen.Subject, count: Int32, action: @escaping () -> Void) {

View File

@ -5,7 +5,6 @@ public enum Api {
public enum bots {}
public enum channels {}
public enum communities {}
public enum community {}
public enum contacts {}
public enum help {}
public enum messages {}
@ -998,11 +997,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1699676497] = { return Api.channels.ChannelParticipants.parse_channelParticipants($0) }
dict[-266911767] = { return Api.channels.ChannelParticipants.parse_channelParticipantsNotModified($0) }
dict[-191450938] = { return Api.channels.SendAsPeers.parse_sendAsPeers($0) }
dict[-557919187] = { return Api.communities.CommunityInvite.parse_communityInvite($0) }
dict[-1997845037] = { return Api.communities.CommunityInvite.parse_communityInviteAlready($0) }
dict[-414818125] = { return Api.communities.CommunityUpdates.parse_communityUpdates($0) }
dict[1805101290] = { return Api.communities.ExportedCommunityInvite.parse_exportedCommunityInvite($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[-513392236] = { return Api.contacts.Blocked.parse_blockedSlice($0) }
dict[-353862078] = { return Api.contacts.Contacts.parse_contacts($0) }
@ -1802,14 +1801,14 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.channels.SendAsPeers:
_1.serialize(buffer, boxed)
case let _1 as Api.communities.CommunityInvite:
_1.serialize(buffer, boxed)
case let _1 as Api.communities.CommunityUpdates:
_1.serialize(buffer, boxed)
case let _1 as Api.communities.ExportedCommunityInvite:
_1.serialize(buffer, boxed)
case let _1 as Api.communities.ExportedInvites:
_1.serialize(buffer, boxed)
case let _1 as Api.community.CommunityInvite:
_1.serialize(buffer, boxed)
case let _1 as Api.contacts.Blocked:
_1.serialize(buffer, boxed)
case let _1 as Api.contacts.Contacts:

View File

@ -894,6 +894,122 @@ public extension Api.channels {
}
}
public extension Api.communities {
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(-557919187)
}
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(-1997845037)
}
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.communities.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.communities.CommunityInvite.communityInviteAlready(filterId: _1!, missingPeers: _2!, chats: _3!, users: _4!)
}
else {
return nil
}
}
}
}
public extension Api.communities {
enum CommunityUpdates: TypeConstructorDescription {
case communityUpdates(missingPeers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
@ -1000,181 +1116,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
}
}
}
}

View File

@ -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 {
enum Blocked: TypeConstructorDescription {
case blocked(blocked: [Api.PeerBlocked], chats: [Api.Chat], users: [Api.User])

View File

@ -2953,15 +2953,15 @@ public extension Api.functions.channels {
}
}
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()
buffer.appendInt32(-1753956947)
buffer.appendInt32(161196517)
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)
var result: Api.community.CommunityInvite?
var result: Api.communities.CommunityInvite?
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
})
@ -3112,6 +3112,26 @@ public extension Api.functions.communities {
})
}
}
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 {
static func acceptContact(id: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()

View File

@ -15,6 +15,8 @@ public struct UserLimitsConfiguration: Equatable {
public let maxAboutLength: Int32
public let maxAnimatedEmojisInText: Int32
public let maxReactionsPerMessage: Int32
public let maxSharedFolderInviteLinks: Int32
public let maxSharedFolderJoin: Int32
public static var defaultValue: UserLimitsConfiguration {
return UserLimitsConfiguration(
@ -30,7 +32,9 @@ public struct UserLimitsConfiguration: Equatable {
maxUploadFileParts: 4000,
maxAboutLength: 70,
maxAnimatedEmojisInText: 10,
maxReactionsPerMessage: 1
maxReactionsPerMessage: 1,
maxSharedFolderInviteLinks: 3,
maxSharedFolderJoin: 2
)
}
@ -47,7 +51,9 @@ public struct UserLimitsConfiguration: Equatable {
maxUploadFileParts: Int32,
maxAboutLength: Int32,
maxAnimatedEmojisInText: Int32,
maxReactionsPerMessage: Int32
maxReactionsPerMessage: Int32,
maxSharedFolderInviteLinks: Int32,
maxSharedFolderJoin: Int32
) {
self.maxPinnedChatCount = maxPinnedChatCount
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
@ -62,6 +68,8 @@ public struct UserLimitsConfiguration: Equatable {
self.maxAboutLength = maxAboutLength
self.maxAnimatedEmojisInText = maxAnimatedEmojisInText
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.maxAnimatedEmojisInText = getGeneralValue("message_animated_emoji_max", orElse: defaultValue.maxAnimatedEmojisInText)
self.maxReactionsPerMessage = getValue("reactions_user_max", orElse: 1)
self.maxSharedFolderInviteLinks = getValue("community_invites_limit", orElse: 3)
self.maxSharedFolderJoin = getValue("communities_joined_limit", orElse: 2)
}
}

View File

@ -49,6 +49,8 @@ public enum EngineConfiguration {
public let maxAboutLength: Int32
public let maxAnimatedEmojisInText: Int32
public let maxReactionsPerMessage: Int32
public let maxSharedFolderInviteLinks: Int32
public let maxSharedFolderJoin: Int32
public static var defaultValue: UserLimits {
return UserLimits(UserLimitsConfiguration.defaultValue)
@ -67,7 +69,9 @@ public enum EngineConfiguration {
maxUploadFileParts: Int32,
maxAboutLength: Int32,
maxAnimatedEmojisInText: Int32,
maxReactionsPerMessage: Int32
maxReactionsPerMessage: Int32,
maxSharedFolderInviteLinks: Int32,
maxSharedFolderJoin: Int32
) {
self.maxPinnedChatCount = maxPinnedChatCount
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
@ -82,6 +86,8 @@ public enum EngineConfiguration {
self.maxAboutLength = maxAboutLength
self.maxAnimatedEmojisInText = maxAnimatedEmojisInText
self.maxReactionsPerMessage = maxReactionsPerMessage
self.maxSharedFolderInviteLinks = maxSharedFolderInviteLinks
self.maxSharedFolderJoin = maxSharedFolderJoin
}
}
}
@ -131,7 +137,9 @@ public extension EngineConfiguration.UserLimits {
maxUploadFileParts: userLimitsConfiguration.maxUploadFileParts,
maxAboutLength: userLimitsConfiguration.maxAboutLength,
maxAnimatedEmojisInText: userLimitsConfiguration.maxAnimatedEmojisInText,
maxReactionsPerMessage: userLimitsConfiguration.maxReactionsPerMessage
maxReactionsPerMessage: userLimitsConfiguration.maxReactionsPerMessage,
maxSharedFolderInviteLinks: userLimitsConfiguration.maxSharedFolderInviteLinks,
maxSharedFolderJoin: userLimitsConfiguration.maxSharedFolderJoin
)
}
}

View File

@ -20,7 +20,7 @@ public func canShareLinkToPeer(peer: EnginePeer) -> Bool {
public enum ExportChatFolderError {
case generic
case limitExceeded
case limitExceeded(limit: Int32, premiumLimit: Int32)
}
public struct ExportedChatFolderLink: Equatable {
@ -59,11 +59,24 @@ func _internal_exportChatFolder(account: Account, filterId: Int32, title: String
|> castError(ExportChatFolderError.self)
|> mapToSignal { inputPeers -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
return account.network.request(Api.functions.communities.exportCommunityInvite(community: .inputCommunityDialogFilter(filterId: filterId), title: title, peers: inputPeers))
|> mapError { error -> ExportChatFolderError in
|> `catch` { error -> Signal<Api.communities.ExportedCommunityInvite, ExportChatFolderError> in
if error.errorDescription == "INVITES_TOO_MUCH" {
return .limitExceeded
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 .generic
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
@ -335,7 +348,8 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
public enum JoinChatFolderLinkError {
case generic
case limitExceeded
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> {
@ -345,11 +359,39 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { inputPeers -> Signal<Never, JoinChatFolderLinkError> in
return account.network.request(Api.functions.communities.joinCommunityInvite(slug: slug, peers: inputPeers))
|> mapError { error -> JoinChatFolderLinkError in
if error.errorDescription.hasPrefix("DIALOG_FILTERS_TOO_MUCH") {
return .limitExceeded
|> `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 .generic
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<Never, JoinChatFolderLinkError> in
@ -442,11 +484,39 @@ func _internal_joinAvailableChatsInFolder(account: Account, updates: ChatFolderU
|> castError(JoinChatFolderLinkError.self)
|> mapToSignal { inputPeers -> Signal<Never, JoinChatFolderLinkError> in
return account.network.request(Api.functions.communities.joinCommunityUpdates(community: .inputCommunityDialogFilter(filterId: updates.folderId), peers: inputPeers))
|> mapError { error -> JoinChatFolderLinkError in
|> `catch` { error -> Signal<Api.Updates, JoinChatFolderLinkError> in
if error.errorDescription == "DIALOG_FILTERS_TOO_MUCH" {
return .limitExceeded
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 .generic
return .fail(.generic)
}
}
|> mapToSignal { result -> Signal<Never, JoinChatFolderLinkError> in
@ -464,3 +534,24 @@ func _internal_hideChatFolderUpdates(account: Account, folderId: Int32) -> Signa
}
|> 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
}
}
}

View File

@ -1061,6 +1061,10 @@ public extension TelegramEngine {
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)
}
}
}

View File

@ -23,15 +23,18 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let context: AccountContext
let subject: ChatFolderLinkPreviewScreen.Subject
let linkContents: ChatFolderLinkContents?
let completion: (() -> Void)?
init(
context: AccountContext,
subject: ChatFolderLinkPreviewScreen.Subject,
linkContents: ChatFolderLinkContents?
linkContents: ChatFolderLinkContents?,
completion: (() -> Void)?
) {
self.context = context
self.subject = subject
self.linkContents = linkContents
self.completion = completion
}
static func ==(lhs: ChatFolderLinkPreviewScreenComponent, rhs: ChatFolderLinkPreviewScreenComponent) -> Bool {
@ -107,6 +110,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
private var joinDisposable: Disposable?
private var inProgress: Bool = false
override init(frame: CGRect) {
self.bottomOverscrollLimit = 200.0
@ -322,10 +327,19 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
}
let titleString: String
var allChatsAdded = false
if let linkContents = component.linkContents {
//TODO:localize
if linkContents.localFilterId != nil {
if self.selectedItems.count == 1 {
if case .remove = component.subject {
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"
} else {
titleString = "Add \(self.selectedItems.count) chats"
@ -356,7 +370,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
contentHeight += 14.0
var topBadge: String?
if let linkContents = component.linkContents, linkContents.localFilterId != nil {
if !allChatsAdded, let linkContents = component.linkContents, linkContents.localFilterId != nil {
topBadge = "+\(linkContents.peers.count)"
}
@ -385,7 +399,11 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let text: String
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?"
} else {
let chatCountString: String
@ -473,7 +491,14 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
return
}
if linkContents.alreadyMemberPeerIds.contains(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 {
@ -522,10 +547,28 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
}
let listHeaderTitle: String
if self.selectedItems.count == 1 {
listHeaderTitle = "1 CHAT IN FOLDER TO JOIN"
if let linkContents = component.linkContents {
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 {
listHeaderTitle = "\(self.selectedItems.count) CHATS IN FOLDER TO JOIN"
listHeaderTitle = " "
}
let listHeaderActionTitle: String
@ -607,7 +650,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
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
listHeaderActionView.isHidden = component.linkContents == nil || allChatsAdded
}
contentHeight += listHeaderTextSize.height
@ -623,7 +666,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
initialContentHeight += 24.0
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 self.selectedItems.isEmpty {
actionButtonTitle = "Do Not Join Any Chats"
@ -641,7 +692,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
transition: transition,
component: AnyComponent(SolidRoundedButtonComponent(
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),
font: .bold,
fontSize: 17.0,
@ -652,22 +703,42 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
animationName: nil,
iconPosition: .right,
iconSpacing: 4.0,
isLoading: component.linkContents == nil,
isLoading: self.inProgress,
action: { [weak self] in
guard let self, let component = self.component, let controller = self.environment?.controller() else {
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 {
let joinSignal: Signal<Never, JoinChatFolderLinkError>
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 {
@ -677,9 +748,12 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
switch error {
case .generic:
controller.dismiss()
case .limitExceeded:
//TODO:localize
let limitController = PremiumLimitScreen(context: component.context, subject: .folders, count: 5, action: {})
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()
}
@ -693,29 +767,6 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
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: {},
@ -793,6 +844,7 @@ public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer {
public enum Subject: Equatable {
case slug(String)
case updates(ChatFolderUpdates)
case remove(folderId: Int32)
}
private let context: AccountContext
@ -800,31 +852,15 @@ public class ChatFolderLinkPreviewScreen: ViewControllerComponentContainer {
private var isDismissed: Bool = false
public init(context: AccountContext, subject: Subject, contents: ChatFolderLinkContents) {
public init(context: AccountContext, subject: Subject, contents: ChatFolderLinkContents, completion: (() -> Void)? = nil) {
self.context = context
super.init(context: context, component: ChatFolderLinkPreviewScreenComponent(context: context, subject: subject, linkContents: contents), navigationBarAppearance: .none)
super.init(context: context, component: ChatFolderLinkPreviewScreenComponent(context: context, subject: subject, linkContents: contents, completion: completion), navigationBarAppearance: .none)
self.statusBar.statusBarStyle = .Ignore
self.navigationPresentation = .flatModal
self.blocksBackgroundWhenInOverlay = true
self.automaticallyControlPresentationContextLayout = false
/*self.linkContentsDisposable = (context.engine.peers.checkChatFolderLink(subject: subject)
|> delay(0.2, queue: .mainQueue())
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let self else {
return
}
self.updateComponent(component: AnyComponent(ChatFolderLinkPreviewScreenComponent(context: context, subject: subject, linkContents: result)), transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)).withUserData(ChatFolderLinkPreviewScreenComponent.AnimationHint()))
}, error: { [weak self] _ in
guard let self else {
return
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.present(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "The folder link has expired."), elevatedLayout: false, action: { _ in true }), in: .window(.root))
self.dismiss()
})*/
}
required public init(coder aDecoder: NSCoder) {

View File

@ -837,7 +837,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
}
}
handleResolvedUrl(.premiumOffer(reference: reference))
} else if parsedUrl.host == "folder" {
} else if parsedUrl.host == "list" {
if let components = URLComponents(string: "/?" + query) {
var slug: String?
if let queryItems = components.queryItems {
@ -850,7 +850,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
}
}
if let slug = slug {
convertedUrl = "https://t.me/folder/\(slug)"
convertedUrl = "https://t.me/list/\(slug)"
}
}
}

View File

@ -1676,6 +1676,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSubject = .files
case .accounts:
mappedSubject = .accounts
case .linksPerSharedFolder:
mappedSubject = .linksPerSharedFolder
case .membershipInSharedFolders:
mappedSubject = .membershipInSharedFolders
}
return PremiumLimitScreen(context: context, subject: mappedSubject, count: count, action: action)
}

View File

@ -418,7 +418,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
return .wallpaper(parameter)
} else if pathComponents[0] == "addtheme" {
return .theme(pathComponents[1])
} else if pathComponents[0] == "folder" {
} else if pathComponents[0] == "list" || pathComponents[0] == "folder" {
return .chatFolder(slug: pathComponents[1])
} else if pathComponents.count == 3 && pathComponents[0] == "c" {
if let channelId = Int64(pathComponents[1]), let messageId = Int32(pathComponents[2]) {