Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2023-03-30 20:59:30 +04:00
commit c851ec3046
6 changed files with 250 additions and 120 deletions

View File

@ -2701,15 +2701,12 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
private func shareFolder(filterId: Int32, data: ChatListFilterData, title: String) {
openCreateChatListFolderLink(context: self.context, folderId: filterId, title: title, peerIds: data.includePeers.peers, pushController: { [weak self] c in
openCreateChatListFolderLink(context: self.context, folderId: filterId, checkIfExists: true, title: title, peerIds: data.includePeers.peers, pushController: { [weak self] c in
self?.push(c)
}, presentController: { [weak self] c in
self?.present(c, in: .window(.root))
}, linkUpdated: { _ in
})
/*self.push(folderInviteLinkListController(context: self.context, filterId: filterId, title: title, allPeerIds: data.includePeers.peers, currentInvitation: nil, linkUpdated: { _ in
}))*/
}
private func askForFilterRemoval(id: Int32) {
@ -2754,53 +2751,69 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
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:)))
let _ = (combineLatest(
self.context.engine.data.get(
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
),
self.context.engine.peers.getExportedChatFolderLinks(id: id)
)
|> deliverOnMainQueue).start(next: { [weak self] peers in
|> deliverOnMainQueue).start(next: { [weak self] peers, links 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: {
})
var hasLinks = false
if let links, !links.isEmpty {
hasLinks = true
}
let confirmDeleteFolder: () -> Void = { [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
}
)
self.push(previewScreen)
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
]), in: .window(.root))
if self.chatListDisplayNode.mainContainerNode.currentItemNode.chatListFilter?.id == id {
self.chatListDisplayNode.mainContainerNode.switchToFilter(id: .all, completion: {
})
}
}
)
self.push(previewScreen)
}
if hasLinks {
//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: {
confirmDeleteFolder()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
]), in: .window(.root))
} else {
confirmDeleteFolder()
}
})
} else {
let actionSheet = ActionSheetController(presentationData: self.presentationData)

View File

@ -1297,7 +1297,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
return
}
openCreateChatListFolderLink(context: context, folderId: currentPreset.id, title: currentPreset.title, peerIds: state.additionallyIncludePeers, pushController: { c in
openCreateChatListFolderLink(context: context, folderId: currentPreset.id, checkIfExists: false, title: currentPreset.title, peerIds: state.additionallyIncludePeers, pushController: { c in
pushControllerImpl?(c)
}, presentController: { c in
presentControllerImpl?(c, nil)
@ -1504,42 +1504,91 @@ func chatListFilterPresetController(context: AccountContext, currentPreset: Chat
return controller
}
func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, title: String, peerIds: [EnginePeer.Id], pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController) -> Void, linkUpdated: @escaping (ExportedChatFolderLink?) -> Void) {
func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, checkIfExists: Bool, title: String, peerIds: [EnginePeer.Id], pushController: @escaping (ViewController) -> Void, presentController: @escaping (ViewController) -> Void, linkUpdated: @escaping (ExportedChatFolderLink?) -> Void) {
if peerIds.isEmpty {
return
}
let _ = (context.engine.data.get(
EngineDataList(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
)
|> deliverOnMainQueue).start(next: { peers in
let peers = peers.compactMap({ $0 })
if peers.allSatisfy({ !canShareLinkToPeer(peer: $0) }) {
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peerIds, currentInvitation: nil, linkUpdated: linkUpdated))
} else {
let _ = (context.engine.peers.exportChatFolder(filterId: folderId, title: "", peerIds: peerIds)
|> deliverOnMainQueue).start(next: { link in
linkUpdated(link)
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: link.peerIds, currentInvitation: link, linkUpdated: linkUpdated))
}, error: { error in
//TODO:localize
let text: String
switch error {
case .generic:
text = "An error occurred"
case let .limitExceeded(limit, premiumLimit):
if limit < premiumLimit {
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .linksPerSharedFolder, count: limit, action: {
})
pushController(limitController)
return
}
text = "You can't create more links."
let existingLink: Signal<ExportedChatFolderLink?, NoError>
if checkIfExists {
existingLink = combineLatest(
context.engine.peers.getExportedChatFolderLinks(id: folderId),
context.engine.data.get(
EngineDataList(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
)
)
|> map { result, peers -> ExportedChatFolderLink? in
var enabledPeerIds: [EnginePeer.Id] = []
for peer in peers {
if let peer, canShareLinkToPeer(peer: peer) {
enabledPeerIds.append(peer.id)
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
})
}
guard let result else {
return nil
}
for link in result {
if Set(link.peerIds) == Set(enabledPeerIds) {
return link
}
}
return nil
}
} else {
existingLink = .single(nil)
}
let _ = (existingLink
|> deliverOnMainQueue).start(next: { existingLink in
if let existingLink {
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peerIds, currentInvitation: existingLink, linkUpdated: linkUpdated))
return
}
let _ = (context.engine.data.get(
EngineDataList(peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
)
|> deliverOnMainQueue).start(next: { peers in
let peers = peers.compactMap({ $0 })
if peers.allSatisfy({ !canShareLinkToPeer(peer: $0) }) {
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peerIds, currentInvitation: nil, linkUpdated: linkUpdated))
} else {
var enabledPeerIds: [EnginePeer.Id] = []
for peer in peers {
if canShareLinkToPeer(peer: peer) {
enabledPeerIds.append(peer.id)
}
}
let _ = (context.engine.peers.exportChatFolder(filterId: folderId, title: "", peerIds: enabledPeerIds)
|> deliverOnMainQueue).start(next: { link in
linkUpdated(link)
pushController(folderInviteLinkListController(context: context, filterId: folderId, title: title, allPeerIds: peerIds, currentInvitation: link, linkUpdated: linkUpdated))
}, error: { error in
//TODO:localize
let text: String
switch error {
case .generic:
text = "An error occurred"
case let .limitExceeded(limit, premiumLimit):
if limit < premiumLimit {
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .linksPerSharedFolder, count: limit, action: {
})
pushController(limitController)
return
}
text = "You can't create more links."
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
})
}
})
})
}

View File

@ -391,36 +391,52 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
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:)))
let _ = (combineLatest(
context.engine.data.get(
EngineDataList(data.includePeers.peers.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:)))
),
context.engine.peers.getExportedChatFolderLinks(id: id)
)
|> deliverOnMainQueue).start(next: { peers in
|> deliverOnMainQueue).start(next: { peers, links 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()
)
var hasLinks = false
if let links, !links.isEmpty {
hasLinks = true
}
let confirmDeleteFolder: () -> Void = {
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: {
})
]))
)
pushControllerImpl?(previewScreen)
}
if hasLinks {
//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: {
confirmDeleteFolder()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
})
]))
} else {
confirmDeleteFolder()
}
})
} else {
let actionSheet = ActionSheetController(presentationData: presentationData)

View File

@ -840,6 +840,9 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
self.subtitleNode.alpha = 0.0
self.subtitleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
self.badgeNode?.alpha = 0.0
self.badgeNode?.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
self.shimmerView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.borderShimmerView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
@ -873,6 +876,9 @@ public final class SolidRoundedButtonNode: ASDisplayNode {
self.subtitleNode.alpha = 1.0
self.subtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.badgeNode?.alpha = 1.0
self.badgeNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.shimmerView?.layer.removeAllAnimations()
self.shimmerView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.borderShimmerView?.layer.removeAllAnimations()
@ -1282,6 +1288,9 @@ public final class SolidRoundedButtonView: UIView {
self.subtitleNode.alpha = 0.0
self.subtitleNode.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
self.badgeNode?.alpha = 0.0
self.badgeNode?.layer.animateAlpha(from: 0.55, to: 0.0, duration: 0.2)
self.shimmerView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
self.borderShimmerView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
}
@ -1315,6 +1324,9 @@ public final class SolidRoundedButtonView: UIView {
self.subtitleNode.alpha = 1.0
self.subtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.badgeNode?.alpha = 1.0
self.badgeNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.shimmerView?.layer.removeAllAnimations()
self.shimmerView?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.borderShimmerView?.layer.removeAllAnimations()
@ -1475,6 +1487,7 @@ public final class SolidRoundedButtonView: UIView {
badgeNode = current
} else {
badgeNode = BadgeNode(fillColor: self.theme.foregroundColor, strokeColor: .clear, textColor: self.theme.backgroundColor)
badgeNode.alpha = self.titleNode.alpha
self.badgeNode = badgeNode
self.addSubnode(badgeNode)
}

View File

@ -12,6 +12,10 @@ public func canShareLinkToPeer(peer: EnginePeer) -> Bool {
} else if channel.username != nil {
isEnabled = true
}
case let .legacyGroup(group):
if !group.hasBannedPermission(.banAddMembers) {
isEnabled = true
}
default:
break
}
@ -60,7 +64,7 @@ func _internal_exportChatFolder(account: Account, filterId: Int32, title: String
|> mapToSignal { inputPeers -> Signal<ExportedChatFolderLink, ExportChatFolderError> in
return account.network.request(Api.functions.communities.exportCommunityInvite(community: .inputCommunityDialogFilter(filterId: filterId), title: title, peers: inputPeers))
|> `catch` { error -> Signal<Api.communities.ExportedCommunityInvite, ExportChatFolderError> in
if error.errorDescription == "INVITES_TOO_MUCH" || error.errorDescription == "FILTERS_TOO_MUCH" {
if error.errorDescription == "INVITES_TOO_MUCH" || error.errorDescription == "COMMUNITIES_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
@ -69,7 +73,7 @@ func _internal_exportChatFolder(account: Account, filterId: Int32, title: String
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
if error.errorDescription == "FILTERS_TOO_MUCH" {
if error.errorDescription == "COMMUNITIES_TOO_MUCH" {
if isPremium {
return .fail(.limitExceeded(limit: userPremiumLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
} else {
@ -362,6 +366,7 @@ public enum JoinChatFolderLinkError {
case generic
case dialogFilterLimitExceeded(limit: Int32, premiumLimit: Int32)
case sharedFolderLimitExceeded(limit: Int32, premiumLimit: Int32)
case tooManyChannels(limit: Int32, premiumLimit: Int32)
}
func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {
@ -372,7 +377,22 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
|> mapToSignal { inputPeers -> Signal<Never, JoinChatFolderLinkError> in
return account.network.request(Api.functions.communities.joinCommunityInvite(slug: slug, peers: inputPeers))
|> `catch` { error -> Signal<Api.Updates, JoinChatFolderLinkError> in
if error.errorDescription == "DIALOG_FILTERS_TOO_MUCH" {
if error.errorDescription == "USER_CHANNELS_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(.tooManyChannels(limit: userPremiumLimits.maxFolderChatsCount, premiumLimit: userPremiumLimits.maxFolderChatsCount))
} else {
return .fail(.tooManyChannels(limit: userDefaultLimits.maxFolderChatsCount, premiumLimit: userPremiumLimits.maxFolderChatsCount))
}
}
} else 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)
}
@ -387,7 +407,7 @@ func _internal_joinChatFolderLink(account: Account, slug: String, peerIds: [Engi
return .fail(.dialogFilterLimitExceeded(limit: userDefaultLimits.maxFoldersCount, premiumLimit: userPremiumLimits.maxFoldersCount))
}
}
} else if error.errorDescription == "FILTERS_TOO_MUCH" {
} else if error.errorDescription == "COMMUNITIES_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
@ -448,23 +468,36 @@ public final class ChatFolderUpdates: Equatable {
}
}
private struct FirstTimeFolderUpdatesKey: Hashable {
var accountId: AccountRecordId
var folderId: Int32
}
private var firstTimeFolderUpdates = Set<FirstTimeFolderUpdatesKey>()
func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> ChatListFiltersState in
return _internal_currentChatListFiltersState(transaction: transaction)
}
|> mapToSignal { state -> Signal<Never, NoError> in
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
if let current = state.updates.first(where: { $0.folderId == folderId }) {
let updateInterval: Int32
#if DEBUG
updateInterval = 5
#else
updateInterval = 60 * 60
#endif
if current.timestamp + updateInterval >= timestamp {
return .complete()
let key = FirstTimeFolderUpdatesKey(accountId: account.id, folderId: folderId)
if firstTimeFolderUpdates.contains(key) {
if let current = state.updates.first(where: { $0.folderId == folderId }) {
let updateInterval: Int32
#if DEBUG
updateInterval = 5
#else
updateInterval = 60 * 60
#endif
if current.timestamp + updateInterval >= timestamp {
return .complete()
}
}
} else {
firstTimeFolderUpdates.insert(key)
}
return account.network.request(Api.functions.communities.getCommunityUpdates(community: .inputCommunityDialogFilter(filterId: folderId)))

View File

@ -188,11 +188,13 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
}
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
topOffset = max(0.0, topOffset)
if topOffset < topOffsetDistance {
targetContentOffset.pointee.y = scrollView.contentOffset.y
scrollView.setContentOffset(CGPoint(x: 0.0, y: itemLayout.topInset), animated: true)
if topOffset > 0.0 {
topOffset = max(0.0, topOffset)
if topOffset < topOffsetDistance {
targetContentOffset.pointee.y = scrollView.contentOffset.y
scrollView.setContentOffset(CGPoint(x: 0.0, y: itemLayout.topInset), animated: true)
}
}
}
@ -757,6 +759,10 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let limitController = PremiumLimitScreen(context: component.context, subject: .membershipInSharedFolders, count: limit, action: {})
controller.push(limitController)
controller.dismiss()
case let .tooManyChannels(limit, _):
let limitController = PremiumLimitScreen(context: component.context, subject: .chatsPerFolder, count: limit, action: {})
controller.push(limitController)
controller.dismiss()
}
}, completed: { [weak self] in
guard let self, let controller = self.environment?.controller() else {