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

This commit is contained in:
Ilya Laktyushin
2023-03-29 10:15:48 +04:00
21 changed files with 581 additions and 220 deletions

View File

@@ -872,14 +872,29 @@ private func loadAndStorePeerChatInfos(accountPeerId: PeerId, postbox: Postbox,
}
struct ChatListFiltersState: Codable, Equatable {
struct ChatListFilterUpdates: Codable, Equatable {
var folderId: Int32
var timestamp: Int32
var peerIds: [PeerId]
init(folderId: Int32, timestamp: Int32, peerIds: [PeerId]) {
self.folderId = folderId
self.timestamp = timestamp
self.peerIds = peerIds
}
}
var filters: [ChatListFilter]
var remoteFilters: [ChatListFilter]?
static var `default` = ChatListFiltersState(filters: [], remoteFilters: nil)
var updates: [ChatListFilterUpdates]
fileprivate init(filters: [ChatListFilter], remoteFilters: [ChatListFilter]?) {
static var `default` = ChatListFiltersState(filters: [], remoteFilters: nil, updates: [])
fileprivate init(filters: [ChatListFilter], remoteFilters: [ChatListFilter]?, updates: [ChatListFilterUpdates]) {
self.filters = filters
self.remoteFilters = remoteFilters
self.updates = updates
}
public init(from decoder: Decoder) throws {
@@ -887,6 +902,7 @@ struct ChatListFiltersState: Codable, Equatable {
self.filters = try container.decode([ChatListFilter].self, forKey: "filters")
self.remoteFilters = try container.decodeIfPresent([ChatListFilter].self, forKey: "remoteFilters")
self.updates = try container.decodeIfPresent([ChatListFilterUpdates].self, forKey: "updates") ?? []
}
func encode(to encoder: Encoder) throws {
@@ -894,6 +910,14 @@ struct ChatListFiltersState: Codable, Equatable {
try container.encode(self.filters, forKey: "filters")
try container.encodeIfPresent(self.remoteFilters, forKey: "remoteFilters")
try container.encode(self.updates, forKey: "updates")
}
mutating func normalize() {
if self.updates.isEmpty {
return
}
self.updates.removeAll(where: { update in !self.filters.contains(where: { $0.id == update.folderId }) })
}
}
@@ -918,6 +942,9 @@ func _internal_updateChatListFiltersInteractively(postbox: Postbox, _ f: @escapi
hasUpdates = true
}
updated = updatedFilters
state.normalize()
return PreferencesEntry(state)
})
if hasUpdates {
@@ -936,6 +963,7 @@ func _internal_updateChatListFiltersInteractively(transaction: Transaction, _ f:
state.filters = updatedFilters
hasUpdates = true
}
state.normalize()
return PreferencesEntry(state)
})
if hasUpdates {
@@ -943,7 +971,6 @@ func _internal_updateChatListFiltersInteractively(transaction: Transaction, _ f:
}
}
func _internal_updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilter], NoError> {
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|> map { preferences -> [ChatListFilter] in
@@ -953,6 +980,15 @@ func _internal_updatedChatListFilters(postbox: Postbox) -> Signal<[ChatListFilte
|> distinctUntilChanged
}
func _internal_updatedChatListFiltersState(postbox: Postbox) -> Signal<ChatListFiltersState, NoError> {
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|> map { preferences -> ChatListFiltersState in
let filtersState = preferences.values[PreferencesKeys.chatListFilters]?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default
return filtersState
}
|> distinctUntilChanged
}
func _internal_updatedChatListFiltersInfo(postbox: Postbox) -> Signal<(filters: [ChatListFilter], synchronized: Bool), NoError> {
return postbox.preferencesView(keys: [PreferencesKeys.chatListFilters])
|> map { preferences -> (filters: [ChatListFilter], synchronized: Bool) in
@@ -982,11 +1018,17 @@ func _internal_currentChatListFilters(transaction: Transaction) -> [ChatListFilt
return settings.filters
}
func _internal_currentChatListFiltersState(transaction: Transaction) -> ChatListFiltersState {
let settings = transaction.getPreferencesEntry(key: PreferencesKeys.chatListFilters)?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default
return settings
}
func updateChatListFiltersState(transaction: Transaction, _ f: (ChatListFiltersState) -> ChatListFiltersState) -> ChatListFiltersState {
var result: ChatListFiltersState?
transaction.updatePreferencesEntry(key: PreferencesKeys.chatListFilters, { entry in
let settings = entry?.get(ChatListFiltersState.self) ?? ChatListFiltersState.default
let updated = f(settings)
var updated = f(settings)
updated.normalize()
result = updated
return PreferencesEntry(updated)
})

View File

@@ -60,7 +60,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" {
if error.errorDescription == "INVITES_TOO_MUCH" || error.errorDescription == "FILTERS_TOO_MUCH" {
return account.postbox.transaction { transaction -> (AppConfiguration, Bool) in
return (currentAppConfiguration(transaction: transaction), transaction.getPeer(account.peerId)?.isPremium ?? false)
}
@@ -69,10 +69,18 @@ func _internal_exportChatFolder(account: Account, filterId: Int32, title: String
let userDefaultLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: false)
let userPremiumLimits = UserLimitsConfiguration(appConfiguration: appConfiguration, isPremium: true)
if isPremium {
return .fail(.limitExceeded(limit: userPremiumLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
if error.errorDescription == "FILTERS_TOO_MUCH" {
if isPremium {
return .fail(.limitExceeded(limit: userPremiumLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
} else {
return .fail(.limitExceeded(limit: userDefaultLimits.maxSharedFolderJoin, premiumLimit: userPremiumLimits.maxSharedFolderJoin))
}
} else {
return .fail(.limitExceeded(limit: userDefaultLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
if isPremium {
return .fail(.limitExceeded(limit: userPremiumLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
} else {
return .fail(.limitExceeded(limit: userDefaultLimits.maxSharedFolderInviteLinks, premiumLimit: userPremiumLimits.maxSharedFolderInviteLinks))
}
}
}
} else {
@@ -246,7 +254,9 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
|> mapToSignal { result -> Signal<ChatFolderLinkContents, CheckChatFolderLinkError> in
return account.postbox.transaction { transaction -> ChatFolderLinkContents in
switch result {
case let .communityInvite(title, peers, chats, users):
case let .communityInvite(_, title, emoticon, peers, chats, users):
let _ = emoticon
var allPeers: [Peer] = []
var peerPresences: [PeerId: Api.User] = [:]
@@ -321,7 +331,7 @@ func _internal_checkChatFolderLink(account: Account, slug: String) -> Signal<Cha
if let peerValue = transaction.getPeer(peer.peerId) {
resultPeers.append(EnginePeer(peerValue))
if transaction.getPeerChatListIndex(peer.peerId) != nil {
if currentFilterPeers.contains(where: { $0 == peer.peerId }) && transaction.getPeerChatListIndex(peer.peerId) != nil {
alreadyMemberPeerIds.insert(peer.peerId)
}
}
@@ -407,74 +417,148 @@ 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]
fileprivate let missingPeers: [EnginePeer]
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())
return ChatFolderLinkContents(localFilterId: self.folderId, title: self.title, peers: self.missingPeers, alreadyMemberPeerIds: Set())
}
fileprivate init(
folderId: Int32,
title: String,
missingPeers: [Api.Peer],
chats: [Api.Chat],
users: [Api.User]
missingPeers: [EnginePeer]
) {
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) {
if lhs.missingPeers.map(\.id) != rhs.missingPeers.map(\.id) {
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)
func _internal_pollChatFolderUpdatesOnce(account: Account, folderId: Int32) -> Signal<Never, NoError> {
return account.postbox.transaction { transaction -> ChatListFiltersState in
return _internal_currentChatListFiltersState(transaction: transaction)
}
|> mapToSignal { result -> Signal<ChatFolderUpdates?, NoError> in
guard let result = result else {
|> 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()
}
}
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)
}
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)
}
|> mapToSignal { result -> Signal<Never, NoError> in
guard let result = result else {
return account.postbox.transaction { transaction -> Void in
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.updates.removeAll(where: { $0.folderId == folderId })
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: []))
return state
})
}
return nil
|> ignoreValues
}
switch result {
case let .communityUpdates(missingPeers, chats, users):
return account.postbox.transaction { transaction -> Void in
var peers: [Peer] = []
var peerPresences: [PeerId: Api.User] = [:]
for user in users {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
peerPresences[telegramUser.id] = user
}
for chat in chats {
if let peer = parseTelegramGroupOrChannel(chat: chat) {
peers.append(peer)
}
}
updatePeers(transaction: transaction, peers: peers, update: { _, updated -> Peer in
return updated
})
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: peerPresences)
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.updates.removeAll(where: { $0.folderId == folderId })
state.updates.append(ChatListFiltersState.ChatListFilterUpdates(folderId: folderId, timestamp: Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970), peerIds: missingPeers.map(\.peerId)))
return state
})
}
|> ignoreValues
}
}
}
}
func _internal_subscribedChatFolderUpdates(account: Account, folderId: Int32) -> Signal<ChatFolderUpdates?, NoError> {
struct InternalData: Equatable {
var title: String
var peerIds: [EnginePeer.Id]
}
return _internal_updatedChatListFiltersState(postbox: account.postbox)
|> map { state -> InternalData? in
guard let update = state.updates.first(where: { $0.folderId == folderId }) else {
return nil
}
guard let folder = state.filters.first(where: { $0.id == folderId }) else {
return nil
}
guard case let .filter(_, title, _, data) = folder, data.isShared else {
return nil
}
let filteredPeerIds: [PeerId] = update.peerIds.filter { !data.includePeers.peers.contains($0) }
return InternalData(title: title, peerIds: filteredPeerIds)
}
|> distinctUntilChanged
|> mapToSignal { internalData -> Signal<ChatFolderUpdates?, NoError> in
guard let internalData = internalData else {
return .single(nil)
}
if internalData.peerIds.isEmpty {
return .single(nil)
}
return account.postbox.transaction { transaction -> ChatFolderUpdates? in
var peers: [EnginePeer] = []
for peerId in internalData.peerIds {
if let peer = transaction.getPeer(peerId) {
peers.append(EnginePeer(peer))
}
}
return ChatFolderUpdates(folderId: folderId, title: internalData.title, missingPeers: peers)
}
}
}
@@ -530,11 +614,22 @@ func _internal_joinAvailableChatsInFolder(account: Account, updates: ChatFolderU
}
func _internal_hideChatFolderUpdates(account: Account, folderId: Int32) -> Signal<Never, NoError> {
return account.network.request(Api.functions.communities.hideCommunityUpdates(community: .inputCommunityDialogFilter(filterId: folderId)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
return account.postbox.transaction { transaction -> Void in
let _ = updateChatListFiltersState(transaction: transaction, { state in
var state = state
state.updates.removeAll(where: { $0.folderId == folderId })
return state
})
}
|> mapToSignal { _ -> Signal<Never, NoError> in
return account.network.request(Api.functions.communities.hideCommunityUpdates(community: .inputCommunityDialogFilter(filterId: folderId)))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> ignoreValues
}
|> ignoreValues
}
func _internal_leaveChatFolder(account: Account, folderId: Int32, removePeerIds: [EnginePeer.Id]) -> Signal<Never, NoError> {

View File

@@ -1058,8 +1058,20 @@ public extension TelegramEngine {
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 pollChatFolderUpdates(folderId: Int32) -> Signal<Never, NoError> {
let signal = _internal_pollChatFolderUpdatesOnce(account: self.account, folderId: folderId)
return (
signal
|> then(
Signal<Never, NoError>.complete()
|> delay(10.0, queue: .concurrentDefaultQueue())
)
)
|> restart
}
public func subscribedChatFolderUpdates(folderId: Int32) -> Signal<ChatFolderUpdates?, NoError> {
return _internal_subscribedChatFolderUpdates(account: self.account, folderId: folderId)
}
public func joinAvailableChatsInFolder(updates: ChatFolderUpdates, peerIds: [EnginePeer.Id]) -> Signal<Never, JoinChatFolderLinkError> {