Conflicts:
	TelegramCore/ChannelAdmins.swift
	TelegramCore/ChannelBlacklist.swift
	TelegramCore/ChannelMembers.swift
	TelegramCore/ChannelParticipants.swift
This commit is contained in:
overtake 2017-06-13 22:00:49 +03:00
commit 16c84c3d2a
24 changed files with 832 additions and 104 deletions

View File

@ -296,6 +296,8 @@
D073CEA11DCBF3D3007511FD /* StickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DE1DB539FC00C6B04F /* StickerPack.swift */; };
D073CEA41DCBF3EA007511FD /* MultipartUpload.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03C53761DAFF20F004C17B3 /* MultipartUpload.swift */; };
D073CEA51DCBF3F5007511FD /* StickerManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0E11DB5401A00C6B04F /* StickerManagement.swift */; };
D0754D2A1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */; };
D0754D2B1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */; };
D07827BB1E00451F00071108 /* SearchPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827BA1E00451F00071108 /* SearchPeers.swift */; };
D07827C91E02F59C00071108 /* InstantPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827C81E02F59C00071108 /* InstantPage.swift */; };
D07827CB1E02F5B200071108 /* RichText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827CA1E02F5B200071108 /* RichText.swift */; };
@ -315,6 +317,10 @@
D08F4A671E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08F4A651E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift */; };
D08F4A691E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */; };
D08F4A6A1E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */; };
D099D7461EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; };
D099D7471EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */; };
D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */; };
D099D74A1EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */; };
D099EA1C1DE72867001AF5A8 /* PeerCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */; };
D09A2FE61D7CD4940018FB72 /* TelegramChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A2FE51D7CD4940018FB72 /* TelegramChannel.swift */; };
D09A2FEB1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A2FEA1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift */; };
@ -703,6 +709,7 @@
D067066E1D512AEB00DED3E3 /* MtProtoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MtProtoKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/MtProtoKit.framework"; sourceTree = "<group>"; };
D073CE5C1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForwardSourceInfoAttribute.swift; sourceTree = "<group>"; };
D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageInfoAttribute.swift; sourceTree = "<group>"; };
D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BotPaymentForm.swift; sourceTree = "<group>"; };
D07827BA1E00451F00071108 /* SearchPeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchPeers.swift; sourceTree = "<group>"; };
D07827C81E02F59C00071108 /* InstantPage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantPage.swift; sourceTree = "<group>"; };
D07827CA1E02F5B200071108 /* RichText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RichText.swift; sourceTree = "<group>"; };
@ -715,6 +722,8 @@
D08CAA8B1ED81EDF0000FDA8 /* Localizations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localizations.swift; sourceTree = "<group>"; };
D08F4A651E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeInstalledStickerPacksOperations.swift; sourceTree = "<group>"; };
D08F4A681E79CECB00A2AA15 /* ManagedSynchronizeInstalledStickerPacksOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeInstalledStickerPacksOperations.swift; sourceTree = "<group>"; };
D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelMessageStateVersionAttribute.swift; sourceTree = "<group>"; };
D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistoryViewChannelStateValidation.swift; sourceTree = "<group>"; };
D099EA1B1DE72867001AF5A8 /* PeerCommands.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerCommands.swift; sourceTree = "<group>"; };
D09A2FE51D7CD4940018FB72 /* TelegramChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramChannel.swift; sourceTree = "<group>"; };
D09A2FEA1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerAccessRestrictionInfo.swift; sourceTree = "<group>"; };
@ -1044,6 +1053,7 @@
D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */,
D0458C871E69B4AB00FB34C1 /* OutgoingContentInfoMessageAttribute.swift */,
D00D343E1E6ED6E50057B307 /* ConsumableContentMessageAttribute.swift */,
D099D7451EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift */,
);
name = Attributes;
sourceTree = "<group>";
@ -1166,6 +1176,7 @@
D0BEAF5C1E54941B00BD963D /* Authorization.swift */,
D0BEAF5F1E54ACF900BD963D /* AccountManager.swift */,
D0528E591E658B3600E2FEF5 /* ManagedLocalInputActivities.swift */,
D099D7481EEF418D00A3128C /* HistoryViewChannelStateValidation.swift */,
);
name = Account;
sourceTree = "<group>";
@ -1255,6 +1266,14 @@
name = Frameworks;
sourceTree = "<group>";
};
D0754D281EEE10D800884F6E /* Bot Payments */ = {
isa = PBXGroup;
children = (
D0754D291EEE10FC00884F6E /* BotPaymentForm.swift */,
);
name = "Bot Payments";
sourceTree = "<group>";
};
D08CAA821ED816290000FDA8 /* Localization */ = {
isa = PBXGroup;
children = (
@ -1309,6 +1328,7 @@
D03B0D691D631A9200955575 /* Contacts */,
D03B0D6E1D631AA900955575 /* Messages */,
D0DF0C881D819C5F008AEB01 /* Peers */,
D0754D281EEE10D800884F6E /* Bot Payments */,
D0C50E2F1E93A83B00F62E39 /* Calls */,
D021E0E01DB5400200C6B04F /* Sticker Management */,
D05A32DF1E6F096B002760B4 /* Settings */,
@ -1661,6 +1681,7 @@
D00DBBDA1E64E67E00DB5485 /* UpdateSecretChat.swift in Sources */,
D0E23DD51E8042F500B9B6D2 /* FeaturedStickerPack.swift in Sources */,
D0FA8B981E1E955C001E855B /* SecretChatOutgoingOperation.swift in Sources */,
D0754D2A1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */,
D0DF0C911D81A857008AEB01 /* ImageRepresentationsUtils.swift in Sources */,
D08CAA8C1ED81EDF0000FDA8 /* Localizations.swift in Sources */,
D0E6521F1E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */,
@ -1686,6 +1707,7 @@
D049EAD51E43D98500A2CD3A /* RecentMediaItem.swift in Sources */,
D0C50E341E93A86600F62E39 /* CallSessionManager.swift in Sources */,
D00D34421E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */,
D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */,
C23BC3871E9BE3CA00D79F92 /* ImportContact.swift in Sources */,
D03B0D0A1D62255C00955575 /* Holes.swift in Sources */,
D0B843CB1DA7FF30005F29E1 /* NBPhoneNumberUtil.m in Sources */,
@ -1774,6 +1796,7 @@
D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */,
D0FA8B9E1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */,
D0561DEA1E5754FA00E6B9E9 /* ChannelAdmins.swift in Sources */,
D099D7461EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */,
D0613FCA1E60440600202CDB /* InvitationLinks.swift in Sources */,
D03B0D721D631ABA00955575 /* SearchMessages.swift in Sources */,
D0DC35501DE36900000195EB /* ChatContextResult.swift in Sources */,
@ -1889,6 +1912,7 @@
D0F7B1E91E045C87007EB8A5 /* PeerCommands.swift in Sources */,
D0B477741EBF54A20033A0AB /* RecentCalls.swift in Sources */,
D00D97C81E32901700E5C2B6 /* PeerInputActivity.swift in Sources */,
D0754D2B1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */,
D0B844311DAB91E0005F29E1 /* NBPhoneMetaData.m in Sources */,
C22EE61C1E67418000334C38 /* ToggleChannelSignatures.swift in Sources */,
D0B418AC1D7E0597004562A4 /* Network.swift in Sources */,
@ -1956,6 +1980,7 @@
D0E35A131DE4C69100BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */,
D0B418961D7E0580004562A4 /* TelegramMediaFile.swift in Sources */,
D08CAA881ED81DD40000FDA8 /* LocalizationInfo.swift in Sources */,
D099D74A1EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */,
D001F3EC1E128A1C007A8C60 /* Holes.swift in Sources */,
D0B4189B1D7E0580004562A4 /* TelegramMediaWebpage.swift in Sources */,
D00C7CE11E3785710080C3D5 /* MarkMessageContentAsConsumedInteractively.swift in Sources */,
@ -2056,6 +2081,7 @@
D0F7B1E41E045C7B007EB8A5 /* InstantPage.swift in Sources */,
D03E5E0D1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */,
D0B418AD1D7E0597004562A4 /* Serialization.swift in Sources */,
D099D7471EEF0C2700A3128C /* ChannelMessageStateVersionAttribute.swift in Sources */,
D058E0D21E8AD65C00A442DE /* StandaloneSendMessage.swift in Sources */,
D03C536F1DAD5CA9004C17B3 /* BotInfo.swift in Sources */,
D0FA8BBA1E2240B4001E855B /* SecretChatIncomingDecryptedOperation.swift in Sources */,

View File

@ -230,6 +230,7 @@ private var declaredEncodables: Void = {
declareEncodable(LocalizationSettings.self, f: { LocalizationSettings(decoder: $0) })
declareEncodable(SuggestedLocalizationEntry.self, f: { SuggestedLocalizationEntry(decoder: $0) })
declareEncodable(SynchronizeLocalizationUpdatesOperation.self, f: { SynchronizeLocalizationUpdatesOperation(decoder: $0) })
declareEncodable(ChannelMessageStateVersionAttribute.self, f: { ChannelMessageStateVersionAttribute(decoder: $0) })
return
}()

View File

@ -17,8 +17,9 @@ final class AccountInitialState {
let peerNotificationSettings: [PeerId: PeerNotificationSettings]
let peerIdsWithNewMessages: Set<PeerId>
let locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]
let cloudReadStates: [PeerId: PeerReadState]
init(state: AuthorizedAccountState.State, peerIds: Set<PeerId>, messageIds: Set<MessageId>, peerIdsWithNewMessages: Set<PeerId>, channelStates: [PeerId: ChannelState], peerNotificationSettings: [PeerId: PeerNotificationSettings], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]]) {
init(state: AuthorizedAccountState.State, peerIds: Set<PeerId>, messageIds: Set<MessageId>, peerIdsWithNewMessages: Set<PeerId>, channelStates: [PeerId: ChannelState], peerNotificationSettings: [PeerId: PeerNotificationSettings], locallyGeneratedMessageTimestamps: [PeerId: [(MessageId.Namespace, Int32)]], cloudReadStates: [PeerId: PeerReadState]) {
self.state = state
self.peerIds = peerIds
self.messageIds = messageIds
@ -26,6 +27,7 @@ final class AccountInitialState {
self.peerIdsWithNewMessages = peerIdsWithNewMessages
self.peerNotificationSettings = peerNotificationSettings
self.locallyGeneratedMessageTimestamps = locallyGeneratedMessageTimestamps
self.cloudReadStates = cloudReadStates
}
}

View File

@ -284,6 +284,7 @@ private func initialStateWithPeerIds(_ modifier: Modifier, peerIds: Set<PeerId>,
var peerNotificationSettings: [PeerId: PeerNotificationSettings] = [:]
var readInboxMaxIds: [PeerId: MessageId] = [:]
var cloudReadStates: [PeerId: PeerReadState] = [:]
for peerId in peerIdsWithNewMessages {
if let notificationSettings = modifier.getPeerNotificationSettings(peerId) {
@ -292,6 +293,7 @@ private func initialStateWithPeerIds(_ modifier: Modifier, peerIds: Set<PeerId>,
if let readStates = modifier.getPeerReadStates(peerId) {
for (namespace, state) in readStates {
if namespace == Namespaces.Message.Cloud {
cloudReadStates[peerId] = state
switch state {
case let .idBased(maxIncomingReadId, _, _, _):
readInboxMaxIds[peerId] = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: maxIncomingReadId)
@ -304,7 +306,7 @@ private func initialStateWithPeerIds(_ modifier: Modifier, peerIds: Set<PeerId>,
}
}
return AccountMutableState(initialState: AccountInitialState(state: (modifier.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, messageIds: associatedMessageIds, peerIdsWithNewMessages: peerIdsWithNewMessages, channelStates: channelStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps), initialPeers: peers, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp)
return AccountMutableState(initialState: AccountInitialState(state: (modifier.getState() as? AuthorizedAccountState)!.state!, peerIds: peerIds, messageIds: associatedMessageIds, peerIdsWithNewMessages: peerIdsWithNewMessages, channelStates: channelStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: locallyGeneratedMessageTimestamps, cloudReadStates: cloudReadStates), initialPeers: peers, initialStoredMessages: storedMessages, initialReadInboxMaxIds: readInboxMaxIds, storedMessagesByPeerIdAndTimestamp: storedMessagesByPeerIdAndTimestamp)
}
func initialStateWithUpdateGroups(_ account: Account, groups: [UpdateGroup]) -> Signal<AccountMutableState, NoError> {
@ -620,7 +622,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
//Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) skip old delete update")
} else if previousState.pts + ptsCount == pts {
updatedState.deleteMessages(messages.map({ MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }))
updatedState.updateChannelState(peerId, state: previousState.setPts(pts))
updatedState.updateChannelState(peerId, state: previousState.withUpdatedPts(pts))
} else {
if !channelsToPoll.contains(peerId) {
Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) delete pts hole")
@ -646,8 +648,10 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
updatedState.addPreCachedResource(resource, data: data)
}
}
updatedState.editMessage(messageId, message: message)
updatedState.updateChannelState(peerId, state: previousState.setPts(pts))
var attributes = message.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
updatedState.editMessage(messageId, message: message.withUpdatedAttributes(attributes))
updatedState.updateChannelState(peerId, state: previousState.withUpdatedPts(pts))
} else {
if !channelsToPoll.contains(peerId) {
Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) edit message pts hole")
@ -678,7 +682,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
}
}
updatedState.updateChannelState(peerId, state: previousState.setPts(pts))
updatedState.updateChannelState(peerId, state: previousState.withUpdatedPts(pts))
} else {
if !channelsToPoll.contains(peerId) {
Logger.shared.log("State", "channel \(peerId) (\((updatedState.peers[peerId] as? TelegramChannel)?.title ?? "nil")) updateWebPage pts hole")
@ -712,8 +716,10 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
updatedState.addPreCachedResource(resource, data: data)
}
}
updatedState.addMessages([message], location: .UpperHistoryBlock)
updatedState.updateChannelState(message.id.peerId, state: previousState.setPts(pts))
var attributes = message.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: pts))
updatedState.addMessages([message.withUpdatedAttributes(attributes)], location: .UpperHistoryBlock)
updatedState.updateChannelState(message.id.peerId, state: previousState.withUpdatedPts(pts))
} else {
if !channelsToPoll.contains(message.id.peerId) {
Logger.shared.log("State", "channel \(message.id.peerId) (\((updatedState.peers[message.id.peerId] as? TelegramChannel)?.title ?? "nil")) message pts hole")
@ -919,7 +925,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
case let .updateUserStatus(userId, status):
updatedState.mergePeerPresences([PeerId(namespace: Namespaces.Peer.CloudUser, id: userId): TelegramUserPresence(apiStatus: status)])
case let .updateUserName(userId, firstName, lastName, username):
//TODO add contact checing for apply first and last name
//TODO add contact checking for apply first and last name
updatedState.updatePeer(PeerId(namespace: Namespaces.Peer.CloudUser, id: userId), { peer in
if let user = peer as? TelegramUser {
return user.withUpdatedUsername(username)
@ -1021,8 +1027,11 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
return resolveAssociatedMessages(account: account, state: finalState)
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
return resolveMissingPeerNotificationSettings(account: account, state: resultingState)
|> map { resultingState -> AccountFinalState in
return AccountFinalState(state: resultingState, shouldPoll: shouldPoll || hadError, incomplete: missingUpdates)
|> mapToSignal { resultingState -> Signal<AccountFinalState, NoError> in
return resolveMissingPeerCloudReadStates(account: account, state: resultingState)
|> map { resultingState -> AccountFinalState in
return AccountFinalState(state: resultingState, shouldPoll: shouldPoll || hadError, incomplete: missingUpdates)
}
}
}
}
@ -1136,6 +1145,47 @@ private func resolveMissingPeerNotificationSettings(account: Account, state: Acc
}
}
private func resolveMissingPeerCloudReadStates(account: Account, state: AccountMutableState) -> Signal<AccountMutableState, NoError> {
var missingPeers: [PeerId: Api.InputPeer] = [:]
for peerId in state.initialState.peerIdsWithNewMessages {
if state.initialState.cloudReadStates[peerId] == nil && (peerId.namespace == Namespaces.Peer.CloudUser || peerId.namespace == Namespaces.Peer.CloudGroup) {
if let peer = state.peers[peerId], let inputPeer = apiInputPeer(peer) {
missingPeers[peerId] = inputPeer
} else {
Logger.shared.log("State", "can't fetch notification settings for peer \(peerId): can't create inputPeer")
}
}
}
if missingPeers.isEmpty {
return .single(state)
} else {
Logger.shared.log("State", "will fetch cloud read states for \(missingPeers.count) peers")
var signals: [Signal<(PeerId, PeerReadState)?, NoError>] = []
for (peerId, inputPeer) in missingPeers {
let fetchSettings = fetchPeerCloudReadState(network: account.network, postbox: account.postbox, peerId: peerId, inputPeer: inputPeer)
|> map { state -> (PeerId, PeerReadState)? in
return state.flatMap { (peerId, $0) }
}
signals.append(fetchSettings)
}
return combineLatest(signals)
|> map { peersAndSettings -> AccountMutableState in
var updatedState = state
for pair in peersAndSettings {
if let (peerId, state) = pair {
if case let .idBased(maxIncomingReadId, maxOutgoingReadId, maxKnownId, count) = state {
updatedState.resetReadState(peerId, namespace: Namespaces.Message.Cloud, maxIncomingReadId: maxIncomingReadId, maxOutgoingReadId: maxOutgoingReadId, maxKnownId: maxKnownId, count: count)
}
}
}
return updatedState
}
}
}
func keepPollingChannel(account: Account, peerId: PeerId, stateManager: AccountStateManager) -> Signal<Void, NoError> {
return account.postbox.modify { modifier -> Signal<Void, NoError> in
if let accountState = (modifier.getState() as? AuthorizedAccountState)?.state, let peer = modifier.getPeer(peerId) {
@ -1148,7 +1198,7 @@ func keepPollingChannel(account: Account, peerId: PeerId, stateManager: AccountS
if let notificationSettings = modifier.getPeerNotificationSettings(peerId) as? TelegramPeerNotificationSettings {
peerNotificationSettings[peerId] = notificationSettings
}
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), messageIds: Set(), peerIdsWithNewMessages: Set(), channelStates: channelStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: [:]), initialPeers: initialPeers, initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
let initialState = AccountMutableState(initialState: AccountInitialState(state: accountState, peerIds: Set(), messageIds: Set(), peerIdsWithNewMessages: Set(), channelStates: channelStates, peerNotificationSettings: peerNotificationSettings, locallyGeneratedMessageTimestamps: [:], cloudReadStates: [:]), initialPeers: initialPeers, initialStoredMessages: Set(), initialReadInboxMaxIds: [:], storedMessagesByPeerIdAndTimestamp: [:])
return pollChannel(account, peer: peer, state: initialState)
|> mapToSignal { (finalState, _, timeout) -> Signal<Void, NoError> in
return resolveAssociatedMessages(account: account, state: finalState)
@ -1173,7 +1223,11 @@ func keepPollingChannel(account: Account, peerId: PeerId, stateManager: AccountS
private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableState) -> Signal<(AccountMutableState, Bool, Int32?), NoError> {
if let inputChannel = apiInputChannel(peer) {
return (account.network.request(Api.functions.updates.getChannelDifference(flags: 0, channel: inputChannel, filter: .channelMessagesFilterEmpty, pts: state.channelStates[peer.id]?.pts ?? 1, limit: 20))
var limit: Int32 = 20
#if (arch(i386) || arch(x86_64)) && os(iOS)
limit = 3
#endif
return (account.network.request(Api.functions.updates.getChannelDifference(flags: 0, channel: inputChannel, filter: .channelMessagesFilterEmpty, pts: state.channelStates[peer.id]?.pts ?? 1, limit: limit))
|> map { Optional($0) }
|> `catch` { error -> Signal<Api.updates.ChannelDifference?, MTRpcError> in
if error.errorDescription == "CHANNEL_PRIVATE" {
@ -1192,9 +1246,9 @@ private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableSt
apiTimeout = timeout
let channelState: ChannelState
if let previousState = updatedState.channelStates[peer.id] {
channelState = previousState.setPts(pts)
channelState = previousState.withUpdatedPts(pts)
} else {
channelState = ChannelState(pts: pts)
channelState = ChannelState(pts: pts, invalidatedPts: nil)
}
updatedState.updateChannelState(peer.id, state: channelState)
@ -1225,20 +1279,15 @@ private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableSt
let channelState: ChannelState
if let previousState = updatedState.channelStates[peer.id] {
channelState = previousState.setPts(pts)
channelState = previousState.withUpdatedPts(pts)
} else {
channelState = ChannelState(pts: pts)
channelState = ChannelState(pts: pts, invalidatedPts: nil)
}
updatedState.updateChannelState(peer.id, state: channelState)
case let .channelDifferenceTooLong(_, pts, timeout, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, messages, chats, users):
apiTimeout = timeout
let channelState: ChannelState
if let previousState = updatedState.channelStates[peer.id] {
channelState = previousState.setPts(pts)
} else {
channelState = ChannelState(pts: pts)
}
let channelState = ChannelState(pts: pts, invalidatedPts: pts)
updatedState.updateChannelState(peer.id, state: channelState)
updatedState.mergeChats(chats)

View File

@ -133,6 +133,16 @@ private func fetchWebpage(account: Account, messageId: MessageId) -> Signal<Void
}
}
private func wrappedHistoryViewAdditionalData(peerId: PeerId, additionalData: [AdditionalMessageHistoryViewData]) -> [AdditionalMessageHistoryViewData] {
var result = additionalData
if peerId.namespace == Namespaces.Peer.CloudChannel {
if result.index(where: { if case .peerChatState = $0 { return true } else { return false } }) == nil {
result.append(.peerChatState(peerId))
}
}
return result
}
private final class PeerCachedDataContext {
var viewIds = Set<Int32>()
var timestamp: Double?
@ -196,8 +206,12 @@ public final class AccountViewTracker {
private var channelPollingContexts: [PeerId: ChannelPollingContext] = [:]
private var featuredStickerPacksContext: FeaturedStickerPacksContext?
private let historyViewChannelStateValidationContexts: HistoryViewChannelStateValidationContexts
init(account: Account) {
self.account = account
self.historyViewChannelStateValidationContexts = HistoryViewChannelStateValidationContexts(queue: self.queue, postbox: account.postbox, network: account.network)
}
deinit {
@ -482,10 +496,16 @@ public final class AccountViewTracker {
if let strongSelf = self {
let messageIds = pendingWebpages(entries: next.0.entries)
strongSelf.updatePendingWebpages(viewId: viewId, messageIds: messageIds)
if peerId.namespace == Namespaces.Peer.CloudChannel {
strongSelf.historyViewChannelStateValidationContexts.updateView(id: viewId, view: next.0)
}
}
}, disposed: { [weak self] viewId in
if let strongSelf = self {
strongSelf.updatePendingWebpages(viewId: viewId, messageIds: [])
if peerId.namespace == Namespaces.Peer.CloudChannel {
strongSelf.historyViewChannelStateValidationContexts.updateView(id: viewId, view: nil)
}
}
})
@ -511,7 +531,7 @@ public final class AccountViewTracker {
public func aroundUnreadMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
if let account = self.account {
let signal = account.postbox.aroundUnreadMessageHistoryViewForPeerId(peerId, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData)
let signal = account.postbox.aroundUnreadMessageHistoryViewForPeerId(peerId, count: count, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(peerId: peerId, additionalData: additionalData))
return wrappedMessageHistorySignal(peerId: peerId, signal: signal)
} else {
return .never()
@ -520,7 +540,7 @@ public final class AccountViewTracker {
public func aroundIdMessageHistoryViewForPeerId(_ peerId: PeerId, count: Int, messageId: MessageId, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
if let account = self.account {
let signal = account.postbox.aroundIdMessageHistoryViewForPeerId(peerId, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData)
let signal = account.postbox.aroundIdMessageHistoryViewForPeerId(peerId, count: count, messageId: messageId, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(peerId: peerId, additionalData: additionalData))
return wrappedMessageHistorySignal(peerId: peerId, signal: signal)
} else {
return .never()
@ -529,7 +549,7 @@ public final class AccountViewTracker {
public func aroundMessageHistoryViewForPeerId(_ peerId: PeerId, index: MessageIndex, count: Int, anchorIndex: MessageIndex, fixedCombinedReadState: CombinedPeerReadState?, tagMask: MessageTags? = nil, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
if let account = self.account {
let signal = account.postbox.aroundMessageHistoryViewForPeerId(peerId, index: index, count: count, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: additionalData)
let signal = account.postbox.aroundMessageHistoryViewForPeerId(peerId, index: index, count: count, anchorIndex: anchorIndex, fixedCombinedReadState: fixedCombinedReadState, topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud], tagMask: tagMask, orderStatistics: orderStatistics, additionalData: wrappedHistoryViewAdditionalData(peerId: peerId, additionalData: additionalData))
return wrappedMessageHistorySignal(peerId: peerId, signal: signal)
} else {
return .never()

View File

@ -52,6 +52,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[145955919] = { return Api.PageBlock.parse_pageBlockCollage($0) }
dict[319588707] = { return Api.PageBlock.parse_pageBlockSlideshow($0) }
dict[-283684427] = { return Api.PageBlock.parse_pageBlockChannel($0) }
dict[834148991] = { return Api.PageBlock.parse_pageBlockAudio($0) }
dict[-614138572] = { return Api.account.TmpPassword.parse_tmpPassword($0) }
dict[590459437] = { return Api.Photo.parse_photoEmpty($0) }
dict[-1836524247] = { return Api.Photo.parse_photo($0) }
@ -352,8 +353,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1182234929] = { return Api.InputUser.parse_inputUserEmpty($0) }
dict[-138301121] = { return Api.InputUser.parse_inputUserSelf($0) }
dict[-668391402] = { return Api.InputUser.parse_inputUser($0) }
dict[-1913754556] = { return Api.Page.parse_pagePart($0) }
dict[-677274263] = { return Api.Page.parse_pageFull($0) }
dict[-1908433218] = { return Api.Page.parse_pagePart($0) }
dict[1433323434] = { return Api.Page.parse_pageFull($0) }
dict[157948117] = { return Api.upload.File.parse_file($0) }
dict[352864346] = { return Api.upload.File.parse_fileCdnRedirect($0) }
dict[182649427] = { return Api.MessageRange.parse_messageRange($0) }
@ -2872,6 +2873,7 @@ public struct Api {
case pageBlockCollage(items: [Api.PageBlock], caption: Api.RichText)
case pageBlockSlideshow(items: [Api.PageBlock], caption: Api.RichText)
case pageBlockChannel(channel: Api.Chat)
case pageBlockAudio(audioId: Int64, caption: Api.RichText)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) -> Swift.Bool {
switch self {
@ -3045,6 +3047,13 @@ public struct Api {
}
channel.serialize(buffer, true)
break
case .pageBlockAudio(let audioId, let caption):
if boxed {
buffer.appendInt32(834148991)
}
serializeInt64(audioId, buffer: buffer, boxed: false)
caption.serialize(buffer, true)
break
}
return true
}
@ -3391,6 +3400,22 @@ public struct Api {
return nil
}
}
fileprivate static func parse_pageBlockAudio(_ reader: BufferReader) -> PageBlock? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Api.RichText?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.RichText
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.PageBlock.pageBlockAudio(audioId: _1!, caption: _2!)
}
else {
return nil
}
}
public var description: String {
get {
@ -3439,6 +3464,8 @@ public struct Api {
return "(pageBlockSlideshow items: \(items), caption: \(caption))"
case .pageBlockChannel(let channel):
return "(pageBlockChannel channel: \(channel))"
case .pageBlockAudio(let audioId, let caption):
return "(pageBlockAudio audioId: \(audioId), caption: \(caption))"
}
}
}
@ -10795,14 +10822,14 @@ public struct Api {
}
public enum Page: CustomStringConvertible {
case pagePart(blocks: [Api.PageBlock], photos: [Api.Photo], videos: [Api.Document])
case pageFull(blocks: [Api.PageBlock], photos: [Api.Photo], videos: [Api.Document])
case pagePart(blocks: [Api.PageBlock], photos: [Api.Photo], documents: [Api.Document])
case pageFull(blocks: [Api.PageBlock], photos: [Api.Photo], documents: [Api.Document])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) -> Swift.Bool {
switch self {
case .pagePart(let blocks, let photos, let videos):
case .pagePart(let blocks, let photos, let documents):
if boxed {
buffer.appendInt32(-1913754556)
buffer.appendInt32(-1908433218)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(blocks.count))
@ -10815,14 +10842,14 @@ public struct Api {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(videos.count))
for item in videos {
buffer.appendInt32(Int32(documents.count))
for item in documents {
item.serialize(buffer, true)
}
break
case .pageFull(let blocks, let photos, let videos):
case .pageFull(let blocks, let photos, let documents):
if boxed {
buffer.appendInt32(-677274263)
buffer.appendInt32(1433323434)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(blocks.count))
@ -10835,8 +10862,8 @@ public struct Api {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(videos.count))
for item in videos {
buffer.appendInt32(Int32(documents.count))
for item in documents {
item.serialize(buffer, true)
}
break
@ -10861,7 +10888,7 @@ public struct Api {
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.Page.pagePart(blocks: _1!, photos: _2!, videos: _3!)
return Api.Page.pagePart(blocks: _1!, photos: _2!, documents: _3!)
}
else {
return nil
@ -10884,7 +10911,7 @@ public struct Api {
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.Page.pageFull(blocks: _1!, photos: _2!, videos: _3!)
return Api.Page.pageFull(blocks: _1!, photos: _2!, documents: _3!)
}
else {
return nil
@ -10894,10 +10921,10 @@ public struct Api {
public var description: String {
get {
switch self {
case .pagePart(let blocks, let photos, let videos):
return "(pagePart blocks: \(blocks), photos: \(photos), videos: \(videos))"
case .pageFull(let blocks, let photos, let videos):
return "(pageFull blocks: \(blocks), photos: \(photos), videos: \(videos))"
case .pagePart(let blocks, let photos, let documents):
return "(pagePart blocks: \(blocks), photos: \(photos), documents: \(documents))"
case .pageFull(let blocks, let photos, let documents):
return "(pageFull blocks: \(blocks), photos: \(photos), documents: \(documents))"
}
}
}

View File

@ -0,0 +1,168 @@
import Foundation
#if os(macOS)
import PostboxMac
import MtProtoKitMac
import SwiftSignalKitMac
#else
import Postbox
import MtProtoKitDynamic
import SwiftSignalKit
#endif
public struct BotPaymentInvoiceFields: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let name = BotPaymentInvoiceFields(rawValue: 1 << 0)
public static let phone = BotPaymentInvoiceFields(rawValue: 1 << 1)
public static let email = BotPaymentInvoiceFields(rawValue: 1 << 2)
public static let shippingAddress = BotPaymentInvoiceFields(rawValue: 1 << 3)
public static let flexibleShipping = BotPaymentInvoiceFields(rawValue: 1 << 4)
}
public struct BotPaymentPrice {
public let label: String
public let amount: Int64
public init(label: String, amount: Int64) {
self.label = label
self.amount = amount
}
}
public struct BotPaymentInvoice {
public let isTest: Bool
public let requestedFields: BotPaymentInvoiceFields
public let currency: String
public let prices: [BotPaymentPrice]
}
public struct BotPaymentNativeProvider {
public let name: String
public let params: String
}
public struct BotPaymentShippingAddress {
public let streetLine1: String
public let streetLine2: String
public let city: String
public let state: String
public let countryIso2: String
public let postCode: String
public init(streetLine1: String, streetLine2: String, city: String, state: String, countryIso2: String, postCode: String) {
self.streetLine1 = streetLine1
self.streetLine2 = streetLine2
self.city = city
self.state = state
self.countryIso2 = countryIso2
self.postCode = postCode
}
}
public struct BotPaymentRequestedInfo {
public let name: String?
public let phone: String?
public let email: String?
public let shippingAddress: BotPaymentShippingAddress?
public init(name: String?, phone: String?, email: String?, shippingAddress: BotPaymentShippingAddress?) {
self.name = name
self.phone = phone
self.email = email
self.shippingAddress = shippingAddress
}
}
public enum BotPaymentSavedCredentials {
case card(id: String, title: String)
}
public struct BotPaymentForm {
public let canSaveCredentials: Bool
public let passwordMissing: Bool
public let invoice: BotPaymentInvoice
public let providerId: Int32
public let url: String
public let nativeProvider: BotPaymentNativeProvider?
public let savedInfo: BotPaymentRequestedInfo?
public let savedCredentials: BotPaymentSavedCredentials?
}
public enum BotPaymentFormReuestError {
case generic
}
public func fetchBotPaymentForm(postbox: Postbox, network: Network, messageId: MessageId) -> Signal<BotPaymentForm, BotPaymentFormReuestError> {
return network.request(Api.functions.payments.getPaymentForm(msgId: messageId.id))
|> `catch` { _ -> Signal<Api.payments.PaymentForm, BotPaymentFormReuestError> in
return .fail(.generic)
}
|> map { result -> BotPaymentForm in
switch result {
case let .paymentForm(flags, _, invoice, providerId, url, nativeProvider, nativeParams, savedInfo, savedCredentials, _):
let parsedInvoice: BotPaymentInvoice
switch invoice {
case let .invoice(flags, currency, prices):
var fields = BotPaymentInvoiceFields()
if (flags & (1 << 1)) != 0 {
fields.insert(.name)
}
if (flags & (1 << 2)) != 0 {
fields.insert(.phone)
}
if (flags & (1 << 3)) != 0 {
fields.insert(.email)
}
if (flags & (1 << 4)) != 0 {
fields.insert(.shippingAddress)
}
if (flags & (1 << 5)) != 0 {
fields.insert(.flexibleShipping)
}
parsedInvoice = BotPaymentInvoice(isTest: (flags & (1 << 0)) != 0, requestedFields: fields, currency: currency, prices: prices.map {
switch $0 {
case let .labeledPrice(label, amount):
return BotPaymentPrice(label: label, amount: amount)
}
})
}
var parsedNativeProvider: BotPaymentNativeProvider?
if let nativeProvider = nativeProvider, let nativeParams = nativeParams {
switch nativeParams {
case let .dataJSON(data):
parsedNativeProvider = BotPaymentNativeProvider(name: nativeProvider, params: data)
}
}
var parsedSavedInfo: BotPaymentRequestedInfo?
if let savedInfo = savedInfo {
switch savedInfo {
case let .paymentRequestedInfo(_, name, phone, email, shippingAddress):
var parsedShippingAddress: BotPaymentShippingAddress?
if let shippingAddress = shippingAddress {
switch shippingAddress {
case let .postAddress(streetLine1, streetLine2, city, state, countryIso2, postCode):
parsedShippingAddress = BotPaymentShippingAddress(streetLine1: streetLine1, streetLine2: streetLine2, city: city, state: state, countryIso2: countryIso2, postCode: postCode)
}
}
parsedSavedInfo = BotPaymentRequestedInfo(name: name, phone: phone, email: email, shippingAddress: parsedShippingAddress)
}
}
var parsedSavedCredentials: BotPaymentSavedCredentials?
if let savedCredentials = savedCredentials {
switch savedCredentials {
case let .paymentSavedCredentialsCard(id, title):
parsedSavedCredentials = .card(id: id, title: title)
}
}
return BotPaymentForm(canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, providerId: providerId, url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials)
}
}
}

View File

@ -31,7 +31,7 @@ public func channelAdmins(account: Account, peerId: PeerId) -> Signal<[RenderedC
for participant in CachedChannelParticipants(apiParticipants: participants).participants {
if let peer = peers[participant.peerId] {
items.append(RenderedChannelParticipant(participant: participant, peer: peer, presence: status[peer.id]))
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presence: status[peer.id]))
}
}

View File

@ -42,7 +42,7 @@ private func fetchChannelBlacklist(account: Account, peerId: PeerId, filter: Cha
for participant in CachedChannelParticipants(apiParticipants: participants).participants {
if let peer = peers[participant.peerId] {
items.append(RenderedChannelParticipant(participant: participant, peer: peer, presence: status[peer.id]))
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presence: status[peer.id]))
}
}

View File

@ -42,7 +42,7 @@ public func channelMembers(account: Account, peerId: PeerId, filter: ChannelMemb
for participant in CachedChannelParticipants(apiParticipants: participants).participants {
if let peer = peers[participant.peerId] {
items.append(RenderedChannelParticipant(participant: participant, peer: peer, presence: status[peer.id]))
items.append(RenderedChannelParticipant(participant: participant, peer: peer, peers: peers, presence: status[peer.id]))
}
}

View File

@ -0,0 +1,22 @@
import Foundation
#if os(macOS)
import PostboxMac
#else
import Postbox
#endif
public class ChannelMessageStateVersionAttribute: MessageAttribute {
public let pts: Int32
public init(pts: Int32) {
self.pts = pts
}
required public init(decoder: Decoder) {
self.pts = decoder.decodeInt32ForKey("p", orElse: 0)
}
public func encode(_ encoder: Encoder) {
encoder.encodeInt32(self.pts, forKey: "p")
}
}

View File

@ -12,24 +12,25 @@ import Foundation
public struct RenderedChannelParticipant: Equatable {
public let participant: ChannelParticipant
public let peer: Peer
public let presence: PeerPresence?
public init(participant: ChannelParticipant, peer: Peer, presence: PeerPresence? = nil) {
public let peers: [PeerId: Peer]
public let presence:PeerPresence?
public init(participant: ChannelParticipant, peer: Peer, peers: [PeerId: Peer] = [:], presence: PeerPresence? = nil) {
self.participant = participant
self.peer = peer
self.peers = peers
self.presence = presence
}
public static func ==(lhs: RenderedChannelParticipant, rhs: RenderedChannelParticipant) -> Bool {
if let lhsStatus = lhs.presence, let rhsStatus = rhs.presence {
if !lhsStatus.isEqual(to: rhsStatus) {
if let lhsPresence = lhs.presence, let rhsPresence = rhs.presence {
if !lhsPresence.isEqual(to: rhsPresence) {
return false
}
} else if (lhs.presence != nil) != (rhs.presence != nil) {
} else if(lhs.presence != nil) != (rhs.presence != nil) {
return false
}
return lhs.participant == rhs.participant && lhs.peer.isEqual(rhs.peer)
}
}
func updateChannelParticipantsSummary(account: Account, peerId: PeerId) -> Signal<Void, NoError> {

View File

@ -7,21 +7,33 @@ import Foundation
final class ChannelState: PeerChatState, Equatable, CustomStringConvertible {
let pts: Int32
let invalidatedPts: Int32?
init(pts: Int32) {
init(pts: Int32, invalidatedPts: Int32?) {
self.pts = pts
self.invalidatedPts = invalidatedPts
}
init(decoder: Decoder) {
self.pts = decoder.decodeInt32ForKey("pts", orElse: 0)
self.invalidatedPts = decoder.decodeOptionalInt32ForKey("ipts")
}
func encode(_ encoder: Encoder) {
encoder.encodeInt32(self.pts, forKey: "pts")
if let invalidatedPts = self.invalidatedPts {
encoder.encodeInt32(invalidatedPts, forKey: "ipts")
} else {
encoder.encodeNil(forKey: "ipts")
}
}
func setPts(_ pts: Int32) -> ChannelState {
return ChannelState(pts: pts)
func withUpdatedPts(_ pts: Int32) -> ChannelState {
return ChannelState(pts: pts, invalidatedPts: self.invalidatedPts)
}
func withUpdatedInvalidatedPts(_ invalidatedPts: Int32?) -> ChannelState {
return ChannelState(pts: self.pts, invalidatedPts: invalidatedPts)
}
func equals(_ other: PeerChatState) -> Bool {
@ -37,7 +49,7 @@ final class ChannelState: PeerChatState, Equatable, CustomStringConvertible {
}
func ==(lhs: ChannelState, rhs: ChannelState) -> Bool {
return lhs.pts == rhs.pts
return lhs.pts == rhs.pts && lhs.invalidatedPts == rhs.invalidatedPts
}
struct ChannelUpdate {

View File

@ -0,0 +1,286 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
import MtProtoKitMac
#else
import Postbox
import SwiftSignalKit
import MtProtoKitDynamic
#endif
private final class ChannelStateValidationBatch {
private let disposable: Disposable
let invalidatedPts: Int32
var cancelledMessageIds = Set<MessageId>()
init(disposable: Disposable, invalidatedPts: Int32) {
self.disposable = disposable
self.invalidatedPts = invalidatedPts
}
deinit {
disposable.dispose()
}
}
private final class ChannelStateValidationContext {
var batchReferences: [MessageId: ChannelStateValidationBatch] = [:]
}
final class HistoryViewChannelStateValidationContexts {
private let queue: Queue
private let postbox: Postbox
private let network: Network
private var contexts: [Int32: ChannelStateValidationContext] = [:]
init(queue: Queue, postbox: Postbox, network: Network) {
self.queue = queue
self.postbox = postbox
self.network = network
}
func updateView(id: Int32, view: MessageHistoryView?) {
if let view = view {
var channelState: ChannelState?
for entry in view.additionalData {
if case let .peerChatState(_, chatState) = entry {
if let chatState = chatState as? ChannelState {
channelState = chatState
}
break
}
}
if let invalidatedPts = channelState?.invalidatedPts {
var invalidatedMessageIds: [MessageId] = []
var minValidatedPts: Int32?
for entry in view.entries {
switch entry {
case let .MessageEntry(message, _, _, _):
if message.id.namespace == Namespaces.Message.Cloud {
var messagePts: Int32?
inner: for attribute in message.attributes {
if let attribute = attribute as? ChannelMessageStateVersionAttribute {
messagePts = attribute.pts
break inner
}
}
if let messagePts = messagePts {
if messagePts < invalidatedPts {
if minValidatedPts == nil || minValidatedPts! > messagePts {
minValidatedPts = messagePts
}
invalidatedMessageIds.append(message.id)
}
} else {
invalidatedMessageIds.append(message.id)
}
}
default:
break
}
}
if !invalidatedMessageIds.isEmpty {
let context: ChannelStateValidationContext
if let current = self.contexts[id] {
context = current
} else {
context = ChannelStateValidationContext()
self.contexts[id] = context
}
var messageIdsForBatch: [MessageId] = []
for messageId in invalidatedMessageIds {
if let batch = context.batchReferences[messageId] {
if batch.invalidatedPts < invalidatedPts {
batch.cancelledMessageIds.insert(messageId)
messageIdsForBatch.append(messageId)
}
} else {
messageIdsForBatch.append(messageId)
}
}
if !messageIdsForBatch.isEmpty {
let disposable = MetaDisposable()
let batch = ChannelStateValidationBatch(disposable: disposable, invalidatedPts: invalidatedPts)
for messageId in messageIdsForBatch {
context.batchReferences[messageId] = batch
}
disposable.set((validateBatch(postbox: self.postbox, network: self.network, messageIds: messageIdsForBatch, minValidatedPts: minValidatedPts)
|> deliverOn(self.queue)).start(completed: { [weak self, weak batch] in
if let strongSelf = self, let context = strongSelf.contexts[id], let batch = batch {
var completedMessageIds: [MessageId] = []
for (messageId, messageBatch) in context.batchReferences {
if messageBatch === batch {
completedMessageIds.append(messageId)
}
}
for messageId in completedMessageIds {
context.batchReferences.removeValue(forKey: messageId)
}
}
}))
}
}
if let context = self.contexts[id] {
let messageIds = Set(invalidatedMessageIds)
var removeIds: [MessageId] = []
for batchMessageId in context.batchReferences.keys {
if !messageIds.contains(batchMessageId) {
removeIds.append(batchMessageId)
}
}
for messageId in removeIds {
context.batchReferences.removeValue(forKey: messageId)
}
}
}
} else if self.contexts[id] != nil {
self.contexts.removeValue(forKey: id)
}
}
}
private func validateBatch(postbox: Postbox, network: Network, messageIds: [MessageId], minValidatedPts: Int32?) -> Signal<Void, NoError> {
guard let peerId = messageIds.first?.peerId else {
return .never()
}
return postbox.modify { modifier -> Signal<Void, NoError> in
if let peer = modifier.getPeer(peerId), let inputChannel = apiInputChannel(peer) {
var ranges: [Api.MessageRange] = []
var currentRange: (Int32, Int32)?
for id in messageIds.sorted() {
if let (minId, maxId) = currentRange {
if maxId == id.id - 1 {
currentRange = (minId, id.id)
} else {
ranges.append(Api.MessageRange.messageRange(minId: minId - 1, maxId: maxId + 1))
currentRange = (id.id, id.id)
}
} else {
currentRange = (id.id, id.id)
}
}
if let (minId, maxId) = currentRange {
ranges.append(Api.MessageRange.messageRange(minId: minId - 1, maxId: maxId + 1))
}
return network.request(Api.functions.updates.getChannelDifference(flags: 0, channel: inputChannel, filter: .channelMessagesFilter(flags: 1 << 1, ranges: ranges), pts: minValidatedPts ?? 1, limit: 100))
|> `catch` { _ -> Signal<Api.updates.ChannelDifference, NoError> in
return .never()
}
|> mapToSignal { result -> Signal<Void, NoError> in
return postbox.modify { modifier -> Void in
let finalPts: Int32
var deletedMessageIds: [MessageId] = []
var updatedMessages: [MessageId: StoreMessage] = [:]
var apiChats: [Api.Chat] = []
var apiUsers: [Api.User] = []
switch result {
case let .channelDifference(_, pts, _, newMessages, otherUpdates, chats, users):
finalPts = pts
apiChats = chats
apiUsers = users
for message in newMessages {
if let message = StoreMessage(apiMessage: message), case let .Id(id) = message.id {
updatedMessages[id] = message
}
}
for update in otherUpdates {
switch update {
case let .updateDeleteChannelMessages(_, messages, _, _):
for messageId in messages {
deletedMessageIds.append(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: messageId))
}
case let .updateNewChannelMessage(message, _, _):
if let message = StoreMessage(apiMessage: message), case let .Id(id) = message.id {
updatedMessages[id] = message
}
case let .updateEditChannelMessage(message, _, _):
if let message = StoreMessage(apiMessage: message), case let .Id(id) = message.id {
updatedMessages[id] = message
}
default:
break
}
}
case let .channelDifferenceEmpty(_, pts, _):
finalPts = pts
case let .channelDifferenceTooLong(_, pts, _, _, _, _, _, _, _, _):
finalPts = pts
}
var peers: [Peer] = []
var peerPresences: [PeerId: PeerPresence] = [:]
for chat in apiChats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
peers.append(groupOrChannel)
}
}
for user in apiUsers {
let telegramUser = TelegramUser(user: user)
peers.append(telegramUser)
if let presence = TelegramUserPresence(apiUser: user) {
peerPresences[telegramUser.id] = presence
}
}
updatePeers(modifier: modifier, peers: peers, update: { _, updated -> Peer in
return updated
})
modifier.updatePeerPresences(peerPresences)
if !deletedMessageIds.isEmpty {
modifier.deleteMessages(deletedMessageIds)
}
for (messageId, message) in updatedMessages {
modifier.updateMessage(messageId, update: { _ in
var attributes = message.attributes
for j in 0 ..< attributes.count {
if let _ = attributes[j] as? ChannelMessageStateVersionAttribute {
attributes.remove(at: j)
break
}
}
attributes.append(ChannelMessageStateVersionAttribute(pts: finalPts))
return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, forwardInfo: message.forwardInfo, authorId: message.authorId, text: message.text, attributes: attributes, media: message.media))
})
}
for messageId in messageIds {
if updatedMessages[messageId] == nil {
modifier.updateMessage(messageId, update: { currentMessage in
var storeForwardInfo: StoreMessageForwardInfo?
if let forwardInfo = currentMessage.forwardInfo {
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date)
}
var attributes = currentMessage.attributes
for j in 0 ..< attributes.count {
if let _ = attributes[j] as? ChannelMessageStateVersionAttribute {
attributes.remove(at: j)
break
}
}
attributes.append(ChannelMessageStateVersionAttribute(pts: finalPts))
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
}
}
}
} else {
return .never()
}
} |> switchToLatest
}

View File

@ -80,6 +80,7 @@ func fetchMessageHistoryHole(network: Network, postbox: Postbox, hole: MessageHi
let messages: [Api.Message]
let chats: [Api.Chat]
let users: [Api.User]
var channelPts: Int32?
switch result {
case let .messages(messages: apiMessages, chats: apiChats, users: apiUsers):
messages = apiMessages
@ -89,17 +90,24 @@ func fetchMessageHistoryHole(network: Network, postbox: Postbox, hole: MessageHi
messages = apiMessages
chats = apiChats
users = apiUsers
case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers):
case let .channelMessages(_, pts, _, apiMessages, apiChats, apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
channelPts = pts
}
return postbox.modify { modifier in
var storeMessages: [StoreMessage] = []
for message in messages {
if let storeMessage = StoreMessage(apiMessage: message) {
storeMessages.append(storeMessage)
if let channelPts = channelPts {
var attributes = storeMessage.attributes
attributes.append(ChannelMessageStateVersionAttribute(pts: channelPts))
storeMessages.append(storeMessage.withUpdatedAttributes(attributes))
} else {
storeMessages.append(storeMessage)
}
}
}
@ -218,7 +226,7 @@ func fetchChatListHole(network: Network, postbox: Postbox, hole: ChatListHole) -
readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount)
if let apiChannelPts = apiChannelPts {
chatStates[peerId] = ChannelState(pts: apiChannelPts)
chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil)
}
notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings)
@ -340,7 +348,7 @@ func fetchChatListHole(network: Network, postbox: Postbox, hole: ChatListHole) -
readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount)
if let apiChannelPts = apiChannelPts {
chatStates[peerId] = ChannelState(pts: apiChannelPts)
chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil)
}
notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings)
@ -391,7 +399,15 @@ func fetchChatListHole(network: Network, postbox: Postbox, hole: ChatListHole) -
modifier.resetIncomingReadStates(readStates)
for (peerId, chatState) in chatStates {
modifier.setPeerChatState(peerId, state: chatState)
if let chatState = chatState as? ChannelState {
if let current = modifier.getPeerChatState(peerId) as? ChannelState {
modifier.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts))
} else {
modifier.setPeerChatState(peerId, state: chatState)
}
} else {
modifier.setPeerChatState(peerId, state: chatState)
}
}
if let replacePinnedPeerIds = replacePinnedPeerIds {

View File

@ -28,6 +28,7 @@ private enum InstantPageBlockType: Int32 {
case collage = 19
case slideshow = 20
case channelBanner = 21
case audio = 22
}
public indirect enum InstantPageBlock: Coding, Equatable {
@ -47,8 +48,9 @@ public indirect enum InstantPageBlock: Coding, Equatable {
case pullQuote(text: RichText, caption: RichText)
case image(id: MediaId, caption: RichText)
case video(id: MediaId, caption: RichText, autoplay: Bool, loop: Bool)
case audio(id: MediaId, caption: RichText)
case cover(InstantPageBlock)
case webEmbed(url: String?, html: String?, dimensions: CGSize, caption: RichText, stretchToWidth: Bool, allowScrolling: Bool)
case webEmbed(url: String?, html: String?, dimensions: CGSize, caption: RichText, stretchToWidth: Bool, allowScrolling: Bool, coverId: MediaId?)
case postEmbed(url: String, webpageId: MediaId?, avatarId: MediaId?, author: String, date: Int32, blocks: [InstantPageBlock], caption: RichText)
case collage(items: [InstantPageBlock], caption: RichText)
case slideshow(items: [InstantPageBlock], caption: RichText)
@ -91,7 +93,11 @@ public indirect enum InstantPageBlock: Coding, Equatable {
case InstantPageBlockType.cover.rawValue:
self = .cover(decoder.decodeObjectForKey("c", decoder: { InstantPageBlock(decoder: $0) }) as! InstantPageBlock)
case InstantPageBlockType.webEmbed.rawValue:
self = .webEmbed(url: decoder.decodeOptionalStringForKey("u"), html: decoder.decodeOptionalStringForKey("h"), dimensions: CGSize(width: CGFloat(decoder.decodeInt32ForKey("sw", orElse: 0)), height: CGFloat(decoder.decodeInt32ForKey("sh", orElse: 0))), caption: decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText, stretchToWidth: decoder.decodeInt32ForKey("st", orElse: 0) != 0, allowScrolling: decoder.decodeInt32ForKey("as", orElse: 0) != 0)
var coverId: MediaId?
if let coverIdNamespace = decoder.decodeOptionalInt32ForKey("ci.n"), let coverIdId = decoder.decodeOptionalInt64ForKey("ci.i") {
coverId = MediaId(namespace: coverIdNamespace, id: coverIdId)
}
self = .webEmbed(url: decoder.decodeOptionalStringForKey("u"), html: decoder.decodeOptionalStringForKey("h"), dimensions: CGSize(width: CGFloat(decoder.decodeInt32ForKey("sw", orElse: 0)), height: CGFloat(decoder.decodeInt32ForKey("sh", orElse: 0))), caption: decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText, stretchToWidth: decoder.decodeInt32ForKey("st", orElse: 0) != 0, allowScrolling: decoder.decodeInt32ForKey("as", orElse: 0) != 0, coverId: coverId)
case InstantPageBlockType.postEmbed.rawValue:
var avatarId: MediaId?
let avatarIdNamespace: Int32? = decoder.decodeOptionalInt32ForKey("av.n")
@ -106,6 +112,8 @@ public indirect enum InstantPageBlock: Coding, Equatable {
self = .slideshow(items: decoder.decodeObjectArrayWithDecoderForKey("b"), caption: decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText)
case InstantPageBlockType.channelBanner.rawValue:
self = .channelBanner(decoder.decodeObjectForKey("c") as? TelegramChannel)
case InstantPageBlockType.audio.rawValue:
self = .audio(id: MediaId(namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt64ForKey("i.i", orElse: 0)), caption: decoder.decodeObjectForKey("c", decoder: { RichText(decoder: $0) }) as! RichText)
default:
self = .unsupported
}
@ -172,8 +180,15 @@ public indirect enum InstantPageBlock: Coding, Equatable {
case let .cover(block):
encoder.encodeInt32(InstantPageBlockType.cover.rawValue, forKey: "r")
encoder.encodeObject(block, forKey: "c")
case let .webEmbed(url, html, dimensions, caption, stretchToWidth, allowScrolling):
case let .webEmbed(url, html, dimensions, caption, stretchToWidth, allowScrolling, coverId):
encoder.encodeInt32(InstantPageBlockType.webEmbed.rawValue, forKey: "r")
if let coverId = coverId {
encoder.encodeInt32(coverId.namespace, forKey: "ci.n")
encoder.encodeInt64(coverId.id, forKey: "ci.i")
} else {
encoder.encodeNil(forKey: "ci.n")
encoder.encodeNil(forKey: "ci.i")
}
if let url = url {
encoder.encodeString(url, forKey: "u")
} else {
@ -225,6 +240,11 @@ public indirect enum InstantPageBlock: Coding, Equatable {
} else {
encoder.encodeNil(forKey: "c")
}
case let .audio(id, caption):
encoder.encodeInt32(InstantPageBlockType.audio.rawValue, forKey: "r")
encoder.encodeInt32(id.namespace, forKey: "i.n")
encoder.encodeInt64(id.id, forKey: "i.i")
encoder.encodeObject(caption, forKey: "c")
}
}
@ -332,8 +352,8 @@ public indirect enum InstantPageBlock: Coding, Equatable {
} else {
return false
}
case let .webEmbed(lhsUrl, lhsHtml, lhsDimensions, lhsCaption, lhsStretchToWidth, lhsAllowScrolling):
if case let .webEmbed(rhsUrl, rhsHtml, rhsDimensions, rhsCaption, rhsStretchToWidth, rhsAllowScrolling) = rhs, lhsUrl == rhsUrl && lhsHtml == rhsHtml && lhsDimensions == rhsDimensions && lhsCaption == rhsCaption && lhsStretchToWidth == rhsStretchToWidth && lhsAllowScrolling == rhsAllowScrolling {
case let .webEmbed(lhsUrl, lhsHtml, lhsDimensions, lhsCaption, lhsStretchToWidth, lhsAllowScrolling, lhsCoverId):
if case let .webEmbed(rhsUrl, rhsHtml, rhsDimensions, rhsCaption, rhsStretchToWidth, rhsAllowScrolling, rhsCoverId) = rhs, lhsUrl == rhsUrl && lhsHtml == rhsHtml && lhsDimensions == rhsDimensions && lhsCaption == rhsCaption && lhsStretchToWidth == rhsStretchToWidth && lhsAllowScrolling == rhsAllowScrolling && lhsCoverId == rhsCoverId {
return true
} else {
return false
@ -369,6 +389,12 @@ public indirect enum InstantPageBlock: Coding, Equatable {
} else {
return false
}
case let .audio(id, caption):
if case .audio(id, caption) = rhs {
return true
} else {
return false
}
}
}
}
@ -387,7 +413,9 @@ private final class MediaDictionary: Coding {
var dict: [MediaId: Media] = [:]
assert(mediaIds.count == medias.count)
for i in 0 ..< mediaIds.count {
dict[mediaIds[i]] = medias[i] as! Media
if let media = medias[i] as? Media {
dict[mediaIds[i]] = media
}
}
self.dict = dict
}
@ -489,11 +517,11 @@ extension InstantPageBlock {
case let .pageBlockPhoto(photoId, caption):
self = .image(id: MediaId(namespace: Namespaces.Media.CloudImage, id: photoId), caption: RichText(apiText: caption))
case let .pageBlockVideo(flags, videoId, caption):
self = .video(id: MediaId(namespace: Namespaces.Media.CloudVideo, id: videoId), caption: RichText(apiText: caption), autoplay: (flags & (1 << 0)) != 0, loop: (flags & (1 << 1)) != 0)
self = .video(id: MediaId(namespace: Namespaces.Media.CloudFile, id: videoId), caption: RichText(apiText: caption), autoplay: (flags & (1 << 0)) != 0, loop: (flags & (1 << 1)) != 0)
case let .pageBlockCover(cover):
self = .cover(InstantPageBlock(apiBlock: cover))
case let .pageBlockEmbed(flags, url, html, posterPhotoId, w, h, caption):
self = .webEmbed(url: url, html: html, dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), caption: RichText(apiText: caption), stretchToWidth: (flags & (1 << 0)) != 0, allowScrolling: (flags & (1 << 3)) != 0)
self = .webEmbed(url: url, html: html, dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), caption: RichText(apiText: caption), stretchToWidth: (flags & (1 << 0)) != 0, allowScrolling: (flags & (1 << 3)) != 0, coverId: posterPhotoId.flatMap { MediaId(namespace: Namespaces.Media.CloudImage, id: $0) })
case let .pageBlockEmbedPost(url, webpageId, authorPhotoId, author, date, blocks, caption):
self = .postEmbed(url: url, webpageId: webpageId == 0 ? nil : MediaId(namespace: Namespaces.Media.CloudWebpage, id: webpageId), avatarId: authorPhotoId == 0 ? nil : MediaId(namespace: Namespaces.Media.CloudImage, id: authorPhotoId), author: author, date: date, blocks: blocks.map({ InstantPageBlock(apiBlock: $0) }), caption: RichText(apiText: caption))
case let .pageBlockCollage(items, caption):
@ -502,6 +530,8 @@ extension InstantPageBlock {
self = .slideshow(items: items.map({ InstantPageBlock(apiBlock: $0) }), caption: RichText(apiText: caption))
case let .pageBlockChannel(channel: apiChat):
self = .channelBanner(parseTelegramGroupOrChannel(chat: apiChat) as? TelegramChannel)
case let .pageBlockAudio(audioId, caption):
self = .audio(id: MediaId(namespace: Namespaces.Media.CloudFile, id: audioId), caption: RichText(apiText: caption))
}
}
}

View File

@ -178,7 +178,7 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ
readStates[peerId]![Namespaces.Message.Cloud] = .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount)
if let apiChannelPts = apiChannelPts {
chatStates[peerId] = ChannelState(pts: apiChannelPts)
chatStates[peerId] = ChannelState(pts: apiChannelPts, invalidatedPts: nil)
}
notificationSettings[peerId] = TelegramPeerNotificationSettings(apiSettings: apiNotificationSettings)
@ -234,7 +234,15 @@ private func synchronizePinnedChats(modifier: Modifier, postbox: Postbox, networ
modifier.resetIncomingReadStates(readStates)
for (peerId, chatState) in chatStates {
modifier.setPeerChatState(peerId, state: chatState)
if let chatState = chatState as? ChannelState {
if let current = modifier.getPeerChatState(peerId) as? ChannelState {
modifier.setPeerChatState(peerId, state: current.withUpdatedPts(chatState.pts))
} else {
modifier.setPeerChatState(peerId, state: chatState)
}
} else {
modifier.setPeerChatState(peerId, state: chatState)
}
}
if remotePeerIds == resultingPeerIds {

View File

@ -14,7 +14,6 @@ public struct Namespaces {
public struct Media {
public static let CloudImage: Int32 = 0
public static let CloudVideo: Int32 = 1
public static let CloudAudio: Int32 = 2
public static let CloudContact: Int32 = 3
public static let CloudMap: Int32 = 4

View File

@ -128,7 +128,9 @@ public func outgoingMessageWithChatContextResult(_ results: ChatContextResultCol
}
switch result {
case let .internalReference(id, type, title, description, image, file, message):
if let image = image {
if type == "game" {
return .message(text: "", attributes: attributes, media: TelegramMediaGame(gameId: 0, accessHash: 0, name: "", title: title ?? "", description: description ?? "", image: image, file: file), replyToMessageId: nil)
} else if let image = image {
return .message(text: caption, attributes: attributes, media: image, replyToMessageId: nil)
} else if let file = file {
return .message(text: caption, attributes: attributes, media: file, replyToMessageId: nil)

View File

@ -268,8 +268,15 @@ func textAndMediaFromApiMedia(_ media: Api.MessageMedia?, _ peerId:PeerId) -> (S
break
case let .messageMediaGame(game):
return (nil, TelegramMediaGame(apiGame: game))
case let .messageMediaInvoice(_, title, description, photo, receiptMsgId, currency, totalAmount, startParam):
return (nil, TelegramMediaInvoice(title: title, description: description, photo: photo != nil ? TelegramMediaWebFile(photo!) : nil, receiptMessageId: receiptMsgId != nil ? MessageId(peerId: peerId, namespace: 0, id: receiptMsgId!) : nil, currency: currency, totalAmount: totalAmount, startParam: startParam))
case let .messageMediaInvoice(flags, title, description, photo, receiptMsgId, currency, totalAmount, startParam):
var parsedFlags = TelegramMediaInvoiceFlags()
if (flags & (1 << 3)) != 0 {
parsedFlags.insert(.isTest)
}
if (flags & (1 << 1)) != 0 {
parsedFlags.insert(.shippingAddressRequested)
}
return (nil, TelegramMediaInvoice(title: title, description: description, photo: photo.flatMap(TelegramMediaWebFile.init), receiptMessageId: receiptMsgId.flatMap { MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }, currency: currency, totalAmount: totalAmount, startParam: startParam, flags: parsedFlags))
}
}

View File

@ -63,6 +63,35 @@ private func dialogTopMessage(network: Network, postbox: Postbox, peerId: PeerId
}
}
func fetchPeerCloudReadState(network: Network, postbox: Postbox, peerId: PeerId, inputPeer: Api.InputPeer) -> Signal<PeerReadState?, NoError> {
return network.request(Api.functions.messages.getPeerDialogs(peers: [inputPeer]))
|> map { result -> PeerReadState? in
switch result {
case let .peerDialogs(dialogs, _, _, _, _):
if let dialog = dialogs.filter({ $0.peerId == peerId }).first {
let apiTopMessage: Int32
let apiReadInboxMaxId: Int32
let apiReadOutboxMaxId: Int32
let apiUnreadCount: Int32
switch dialog {
case let .dialog(_, _, topMessage, readInboxMaxId, readOutboxMaxId, unreadCount, _, _, _):
apiTopMessage = topMessage
apiReadInboxMaxId = readInboxMaxId
apiReadOutboxMaxId = readOutboxMaxId
apiUnreadCount = unreadCount
}
return .idBased(maxIncomingReadId: apiReadInboxMaxId, maxOutgoingReadId: apiReadOutboxMaxId, maxKnownId: apiTopMessage, count: apiUnreadCount)
} else {
return nil
}
}
}
|> `catch` { _ -> Signal<PeerReadState?, NoError> in
return .single(nil)
}
}
private func dialogReadState(network: Network, postbox: Postbox, peerId: PeerId) -> Signal<(PeerReadState, PeerReadStateMarker), VerifyReadStateError> {
return dialogTopMessage(network: network, postbox: postbox, peerId: peerId)
|> mapToSignal { topMessage -> Signal<(PeerReadState, PeerReadStateMarker), VerifyReadStateError> in

View File

@ -136,6 +136,20 @@ public enum TelegramMediaFileAttribute: Coding {
}
}
func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> CGSize? {
for attribute in attributes {
switch attribute {
case let .Video(_, size, _):
return size
case let .ImageSize(size):
return size
default:
break
}
}
return nil
}
public final class TelegramMediaFile: Media, Equatable {
public let fileId: MediaId
public let resource: TelegramMediaResource
@ -255,17 +269,7 @@ public final class TelegramMediaFile: Media, Equatable {
}
public var dimensions: CGSize? {
for attribute in self.attributes {
switch attribute {
case let .Video(_, size, _):
return size
case let .ImageSize(size):
return size
default:
break
}
}
return nil
return dimensionsForFileAttributes(self.attributes)
}
public func isEqual(_ other: Media) -> Bool {

View File

@ -5,6 +5,21 @@ import Foundation
import Postbox
#endif
public struct TelegramMediaInvoiceFlags: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let isTest = TelegramMediaInvoiceFlags(rawValue: 1 << 0)
public static let shippingAddressRequested = TelegramMediaInvoiceFlags(rawValue: 1 << 1)
}
//flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String
//messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
@ -13,16 +28,16 @@ public final class TelegramMediaInvoice: Media {
public var id: MediaId? = nil
public let title:String
public let description:String
// public let photo:
public let receiptMessageId:MessageId?
public let currency:String
public let totalAmount:Int64
public let startParam:String
public let photo:TelegramMediaWebFile?
public let title: String
public let description: String
public let receiptMessageId: MessageId?
public let currency: String
public let totalAmount: Int64
public let startParam: String
public let photo: TelegramMediaWebFile?
public let flags: TelegramMediaInvoiceFlags
public init(title:String, description:String, photo:TelegramMediaWebFile?, receiptMessageId:MessageId?, currency:String, totalAmount:Int64, startParam:String) {
public init(title: String, description: String, photo: TelegramMediaWebFile?, receiptMessageId: MessageId?, currency: String, totalAmount: Int64, startParam: String, flags: TelegramMediaInvoiceFlags) {
self.title = title
self.description = description
self.photo = photo
@ -30,9 +45,9 @@ public final class TelegramMediaInvoice: Media {
self.currency = currency
self.totalAmount = totalAmount
self.startParam = startParam
self.flags = flags
}
public init(decoder: Decoder) {
self.title = decoder.decodeStringForKey("t", orElse: "")
self.description = decoder.decodeStringForKey("d", orElse: "")
@ -40,6 +55,7 @@ public final class TelegramMediaInvoice: Media {
self.totalAmount = decoder.decodeInt64ForKey("ta", orElse: 0)
self.startParam = decoder.decodeStringForKey("sp", orElse: "")
self.photo = decoder.decodeObjectForKey("p") as? TelegramMediaWebFile
self.flags = TelegramMediaInvoiceFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0))
if let receiptMessageIdPeerId = decoder.decodeOptionalInt64ForKey("r.p") as Int64?, let receiptMessageIdNamespace = decoder.decodeOptionalInt32ForKey("r.n") as Int32?, let receiptMessageIdId = decoder.decodeOptionalInt32ForKey("r.i") as Int32? {
self.receiptMessageId = MessageId(peerId: PeerId(receiptMessageIdPeerId), namespace: receiptMessageIdNamespace, id: receiptMessageIdId)
@ -54,6 +70,7 @@ public final class TelegramMediaInvoice: Media {
encoder.encodeString(self.currency, forKey: "nc")
encoder.encodeInt64(self.totalAmount, forKey: "ta")
encoder.encodeString(self.startParam, forKey: "sp")
encoder.encodeInt32(self.flags.rawValue, forKey: "f")
if let photo = photo {
encoder.encodeObject(photo, forKey: "p")
@ -101,6 +118,10 @@ public final class TelegramMediaInvoice: Media {
return false
}
if self.flags != other.flags {
return false
}
return true
}

View File

@ -6,10 +6,6 @@ import Foundation
#endif
public class TelegramMediaWebFile: Media {
public let resource: TelegramMediaResource
public let mimeType: String
public let size: Int32
@ -61,14 +57,16 @@ public class TelegramMediaWebFile: Media {
return true
}
public var dimensions: CGSize? {
return dimensionsForFileAttributes(self.attributes)
}
}
extension TelegramMediaWebFile {
convenience init(_ document:Api.WebDocument) {
switch document {
case let .webDocument(data):
self.init(resource: WebFileReferenceMediaResource(url: data.url, size: data.size, datacenterId: data.dcId, accessHash: data.accessHash), mimeType: data.mimeType, size: data.size, attributes: telegramMediaFileAttributesFromApiAttributes(data.attributes))
case let .webDocument(data):
self.init(resource: WebFileReferenceMediaResource(url: data.url, size: data.size, datacenterId: data.dcId, accessHash: data.accessHash), mimeType: data.mimeType, size: data.size, attributes: telegramMediaFileAttributesFromApiAttributes(data.attributes))
}
}
}