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

This commit is contained in:
Ilya Laktyushin 2019-06-19 00:33:44 +02:00
commit 7d5815f329
56 changed files with 3526 additions and 3497 deletions

View file

@ -188,7 +188,6 @@
09FDAEE62140477F00BF856F /* MtProtoKitDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09FDAEE52140477F00BF856F /* MtProtoKitDynamic.framework */; };
D000CACF21FB6E380011B15D /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D000CAC821FB6E370011B15D /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
D001D5AA1F878DA300DF975A /* PhoneCountries.txt in Resources */ = {isa = PBXBuildFile; fileRef = D001D5A91F878DA300DF975A /* PhoneCountries.txt */; };
D008184E22B5796E008A895F /* BuildConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D008184D22B5796E008A895F /* BuildConfig.framework */; };
D008185022B5797A008A895F /* BuildConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D008184F22B5797A008A895F /* BuildConfig.framework */; };
D008185222B57986008A895F /* BuildConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D008185122B57986008A895F /* BuildConfig.framework */; };
D008185422B57994008A895F /* BuildConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D008185322B57994008A895F /* BuildConfig.framework */; };
@ -205,7 +204,6 @@
D015E04D225D2D8F00CB9E8A /* WebP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D015E04C225D2D8F00CB9E8A /* WebP.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
D015E050225D303F00CB9E8A /* WebP.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D015E04C225D2D8F00CB9E8A /* WebP.framework */; };
D015E051225D303F00CB9E8A /* WebP.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D015E04C225D2D8F00CB9E8A /* WebP.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
D01A47551F4DBED700383CC1 /* HockeySDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D01A47541F4DBED700383CC1 /* HockeySDK.framework */; };
D021D4D9219CAEDD0064BEBA /* Config-Fork.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = D021D4D8219CAEDD0064BEBA /* Config-Fork.xcconfig */; };
D02CF5FD215D9ABF00E0F56A /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0AA1A671D568BA400152314 /* UserNotifications.framework */; };
D02CF5FE215D9ABF00E0F56A /* UserNotificationsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0AA1A691D568BA400152314 /* UserNotificationsUI.framework */; };
@ -1099,11 +1097,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D008184E22B5796E008A895F /* BuildConfig.framework in Frameworks */,
D0CAD6A421C03BEB001E3055 /* FFMpeg.framework in Frameworks */,
09C50E88217385CF009E676F /* WatchConnectivity.framework in Frameworks */,
09FDAEE62140477F00BF856F /* MtProtoKitDynamic.framework in Frameworks */,
D01A47551F4DBED700383CC1 /* HockeySDK.framework in Frameworks */,
D0A18D631E149043004C6734 /* PushKit.framework in Frameworks */,
D0B844601DACF561005F29E1 /* libc++.tbd in Frameworks */,
D06706621D5118F500DED3E3 /* TelegramCore.framework in Frameworks */,

View file

@ -27,6 +27,8 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@ -36,8 +38,8 @@
ReferencedContainer = "container:Telegram-iOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "DebugAppStoreLLC"
@ -59,9 +61,11 @@
ReferencedContainer = "container:Telegram-iOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "ReleaseAppStoreLLC"
buildConfiguration = "DebugAppStoreLLC"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"

View file

@ -41,15 +41,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D008599B1B28189D00EAF753"
BuildableName = "Telegram.app"
BlueprintName = "Telegram-iOS"
ReferencedContainer = "container:Telegram-iOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
@ -72,6 +63,17 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "D008599B1B28189D00EAF753"
BuildableName = "Telegram.app"
BlueprintName = "Telegram-iOS"
ReferencedContainer = "container:Telegram-iOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "DebugHockeyapp"
@ -102,6 +104,8 @@
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "DebugHockeyapp"

View file

@ -1614,7 +1614,10 @@
"ShareMenu.Send" = "Send";
"Conversation.ReportSpam" = "Report Spam";
"Conversation.ReportSpamAndLeave" = "Report Spam and Leave";
"Conversation.ReportSpamConfirmation" = "Are you sure you want to report spam from this user?";
"Conversation.ReportSpamGroupConfirmation" = "Are you sure you want to report spam from this group?";
"Conversation.ReportSpamChannelConfirmation" = "Are you sure you want to report spam from this channel?";
"SharedMedia.EmptyMusicText" = "All music shared in this chat will appear here.";
"ChatSettings.AutoPlayAnimations" = "Autoplay GIFs";
@ -4348,6 +4351,7 @@ Any member of this group will be able to see messages in the channel.";
"Appearance.ThemeCarouselNight" = "Monochrome";
"Notification.Exceptions.DeleteAll" = "Delete All";
"Notification.Exceptions.DeleteAllConfirmation" = "Are you sure you want to delete all exceptions?";
"Notification.Exceptions.Add" = "Add";
"Exceptions.AddToExceptions" = "ADD TO EXCEPTIONS";
@ -4358,6 +4362,7 @@ Any member of this group will be able to see messages in the channel.";
"Conversation.Block" = "Block";
"Conversation.BlockUser" = "Block User";
"Conversation.ShareMyPhoneNumber" = "Share My Phone Number";
"Conversation.ShareMyPhoneNumberConfirmation" = "Are you sure you want to share your phone number %1$@ with %2$@?";
"Conversation.AddToContacts" = "Add to Contacts";
"Conversation.AddNameToContacts" = "Add %@ to Contacts";

View file

@ -759,7 +759,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
return additionalInverseTopInset
}
private func snapToBounds(snapTopItem: Bool, stackFromBottom: Bool, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, scrollToItem: ListViewScrollToItem? = nil) -> (snappedTopInset: CGFloat, offset: CGFloat) {
private func snapToBounds(snapTopItem: Bool, stackFromBottom: Bool, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, scrollToItem: ListViewScrollToItem? = nil, isExperimentalSnapToScrollToItem: Bool = false) -> (snappedTopInset: CGFloat, offset: CGFloat) {
if self.itemNodes.count == 0 {
return (0.0, 0.0)
}
@ -819,7 +819,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
var transition: ContainedViewLayoutTransition = .immediate
if let updateSizeAndInsets = updateSizeAndInsets {
if !updateSizeAndInsets.duration.isZero {
if !updateSizeAndInsets.duration.isZero && !isExperimentalSnapToScrollToItem {
switch updateSizeAndInsets.curve {
case let .Spring(duration):
transition = .animated(duration: duration, curve: .spring)
@ -828,15 +828,17 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
}
}
} else if let scrollToItem = scrollToItem {
switch scrollToItem.curve {
case let .Spring(duration):
transition = .animated(duration: duration, curve: .spring)
case let .Default(duration):
if let duration = duration, duration.isZero {
transition = .immediate
} else {
transition = .animated(duration: duration ?? 0.3, curve: .easeInOut)
}
if scrollToItem.animated {
switch scrollToItem.curve {
case let .Spring(duration):
transition = .animated(duration: duration, curve: .spring)
case let .Default(duration):
if let duration = duration, duration.isZero {
transition = .immediate
} else {
transition = .animated(duration: duration ?? 0.3, curve: .easeInOut)
}
}
}
}
@ -2484,7 +2486,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
itemNode.position = CGPoint(x: position.x, y: position.y + offsetFix)
}
let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets)
let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, isExperimentalSnapToScrollToItem: isExperimentalSnapToScrollToItem)
if !snappedTopInset.isZero && (previousVisibleSize.height.isZero || previousApparentFrames.isEmpty) {
offsetFix += snappedTopInset
@ -2504,7 +2506,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
sizeAndInsetsOffset = offsetFix
completeOffset += snapToBoundsOffset
if !updateSizeAndInsets.duration.isZero {
if !updateSizeAndInsets.duration.isZero && !isExperimentalSnapToScrollToItem {
let animation: CABasicAnimation
switch updateSizeAndInsets.curve {
case let .Spring(duration):

View file

@ -54,6 +54,5 @@ final class CachedPeerDataTable: Table {
}
self.updatedPeerIds.removeAll()
self.cachedDatas.removeAll()
}
}

View file

@ -10,7 +10,7 @@ public struct MessageId: Hashable, Comparable, CustomStringConvertible {
public var description: String {
get {
return "\(namespace):\(id)"
return "\(namespace)_\(id)"
}
}

View file

@ -478,36 +478,6 @@ final class MutableMessageHistoryView {
self.sampledState = self.state.sample(postbox: postbox)
}
/*for operationSet in operations {
for operation in operationSet {
switch operation {
case let .InsertMessage(intermediateMessage):
for i in 0 ..< self.entries.count {
switch self.entries[i] {
case let .MessageEntry(value):
let message = value.message
if message.associatedMessageIds.count != message.associatedMessages.count {
if message.associatedMessageIds.contains(intermediateMessage.id) && message.associatedMessages[intermediateMessage.id] == nil {
var updatedAssociatedMessages = message.associatedMessages
let renderedMessage = renderIntermediateMessage(intermediateMessage)
updatedAssociatedMessages[intermediateMessage.id] = renderedMessage
let updatedMessage = Message(stableId: message.stableId, stableVersion: message.stableVersion, id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, groupInfo: message.groupInfo, timestamp: message.timestamp, flags: message.flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: message.forwardInfo, author: message.author, text: message.text, attributes: message.attributes, media: message.media, peers: message.peers, associatedMessages: updatedAssociatedMessages, associatedMessageIds: message.associatedMessageIds)
self.entries[i] = .MessageEntry(MessageHistoryMessageEntry(message: updatedMessage, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes))
hasChanges = true
}
}
break
default:
break
}
}
break
default:
break
}
}
}*/
for operationSet in operations {
for operation in operationSet {
switch operation {
@ -786,150 +756,6 @@ public final class MessageHistoryView {
self.isLoading = isLoading
}
/*var entries: [MessageHistoryEntry] = []
if let transientReadStates = mutableView.transientReadStates, case let .peer(states) = transientReadStates {
for entry in mutableView.entries {
switch entry {
case let .MessageEntry(value):
let read: Bool
if value.message.flags.contains(.Incoming) {
read = false
} else if let readState = states[value.message.id.peerId] {
read = readState.isOutgoingMessageIndexRead(value.message.index)
} else {
read = false
}
entries.append(MessageHistoryEntry(message: value.message, isRead: read, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes))
case .IntermediateMessageEntry:
assertionFailure("unexpected IntermediateMessageEntry in MessageHistoryView.init()")
}
}
} else {
for entry in mutableView.entries {
switch entry {
case let .MessageEntry(value):
entries.append(MessageHistoryEntry(message: value.message, isRead: false, location: value.location, monthLocation: value.monthLocation, attributes: value.attributes))
case .IntermediateMessageEntry:
assertionFailure("unexpected IntermediateMessageEntry in MessageHistoryView.init()")
}
}
}
var holeEarlier = false
var holeLater = false
if mutableView.clipHoles {
if entries.isEmpty {
if !mutableView.holes.isEmpty {
holeEarlier = true
holeLater = true
}
} else {
var clearAllEntries = false
if case let .message(index) = self.anchorIndex {
for (holeKey, indices) in mutableView.holes {
if holeKey.peerId == index.id.peerId && holeKey.namespace == index.id.namespace && indices.contains(Int(index.id.id)) {
entries.removeAll()
earlierId = nil
holeEarlier = true
laterId = nil
holeLater = true
clearAllEntries = true
break
}
}
}
if !clearAllEntries {
var referenceIndex = entries.count - 1
for i in 0 ..< entries.count {
if self.anchorIndex.isLessOrEqual(to: entries[i].index) {
referenceIndex = i
break
}
}
var groupStart: (Int, MessageGroupInfo)?
for i in referenceIndex ..< entries.count {
let id = entries[i].message.id
if let holeIndices = mutableView.holes[PeerIdAndNamespace(peerId: id.peerId, namespace: id.namespace)] {
if holeIndices.contains(Int(id.id)) {
if let groupStart = groupStart {
entries.removeSubrange(groupStart.0 ..< entries.count)
} else {
entries.removeSubrange(i ..< entries.count)
}
laterId = nil
holeLater = true
break
}
}
/*if let groupStart = groupStart {
entries.removeSubrange(groupStart.0 ..< entries.count)
laterId = nil
} else {
if i != entries.count - 1 {
entries.removeSubrange(i + 1 ..< entries.count)
laterId = nil
}
}*/
if let groupInfo = entries[i].message.groupInfo {
if let groupStart = groupStart, groupStart.1 == groupInfo {
} else {
groupStart = (i, groupInfo)
}
} else {
groupStart = nil
}
}
if let groupStart = groupStart, laterId != nil {
entries.removeSubrange(groupStart.0 ..< entries.count)
}
groupStart = nil
if !entries.isEmpty {
for i in (0 ... min(referenceIndex, entries.count - 1)).reversed() {
let id = entries[i].message.id
if let holeIndices = mutableView.holes[PeerIdAndNamespace(peerId: id.peerId, namespace: id.namespace)] {
if holeIndices.contains(Int(id.id)) {
if let groupStart = groupStart {
entries.removeSubrange(0 ..< groupStart.0 + 1)
} else {
entries.removeSubrange(0 ... i)
}
earlierId = nil
holeEarlier = true
break
}
}
/*if let groupStart = groupStart {
entries.removeSubrange(0 ..< groupStart.0 + 1)
earlierId = nil
} else {
if i != 0 {
entries.removeSubrange(0 ..< i)
earlierId = nil
}
}
break
*/
if let groupInfo = entries[i].message.groupInfo {
if let groupStart = groupStart, groupStart.1 == groupInfo {
} else {
groupStart = (i, groupInfo)
}
} else {
groupStart = nil
}
}
if let groupStart = groupStart, earlierId != nil {
entries.removeSubrange(0 ..< groupStart.0 + 1)
}
}
}
}
}
self.holeEarlier = holeEarlier
self.holeLater = holeLater
self.entries = entries*/
var topTaggedMessages: [Message] = []
for (_, message) in mutableView.topTaggedMessages {
if let message = message {

View file

@ -5,6 +5,13 @@ struct PeerIdAndNamespace: Hashable {
let namespace: MessageId.Namespace
}
private func canContainHoles(_ peerIdAndNamespace: PeerIdAndNamespace, seedConfiguration: SeedConfiguration) -> Bool {
guard let messageNamespaces = seedConfiguration.messageHoles[peerIdAndNamespace.peerId.namespace] else {
return false
}
return messageNamespaces[peerIdAndNamespace.namespace] != nil
}
private struct MessageMonthIndex: Equatable {
let year: Int32
let month: Int32
@ -279,7 +286,7 @@ private func isIndex(index: MessageIndex, closerTo anchor: HistoryViewAnchor, th
}
}
private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, tag: MessageTags?, halfLimit: Int) -> (clipRanges: [ClosedRange<MessageIndex>], sampledHole: SampledHistoryViewHole?) {
private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries], holes: HistoryViewHoles, anchor: HistoryViewAnchor, tag: MessageTags?, halfLimit: Int, seedConfiguration: SeedConfiguration) -> (clipRanges: [ClosedRange<MessageIndex>], sampledHole: SampledHistoryViewHole?) {
var clipRanges: [ClosedRange<MessageIndex>] = []
var sampledHole: (distanceFromAnchor: Int?, hole: SampledHistoryViewHole)?
@ -287,6 +294,7 @@ private func sampleHoleRanges(orderedEntriesBySpace: [PeerIdAndNamespace: Ordere
if indices.isEmpty {
continue
}
assert(canContainHoles(space, seedConfiguration: seedConfiguration))
switch anchor {
case .lowerBound, .upperBound:
break
@ -628,11 +636,11 @@ struct OrderedHistoryViewEntries {
var lowerOrAtAnchor: [MutableMessageHistoryEntry]
var higherThanAnchor: [MutableMessageHistoryEntry]
mutating func fixMonotonity() {
mutating func fixMonotony() {
if self.lowerOrAtAnchor.count > 1 {
for i in 1 ..< self.lowerOrAtAnchor.count {
if self.lowerOrAtAnchor[i].index < self.lowerOrAtAnchor[i - 1].index {
//assertionFailure()
assertionFailure()
break
}
}
@ -640,7 +648,7 @@ struct OrderedHistoryViewEntries {
if self.higherThanAnchor.count > 1 {
for i in 1 ..< self.higherThanAnchor.count {
if self.higherThanAnchor[i].index < self.higherThanAnchor[i - 1].index {
// assertionFailure()
assertionFailure()
break
}
}
@ -664,7 +672,7 @@ struct OrderedHistoryViewEntries {
}
}
if fix {
//assertionFailure()
assertionFailure()
self.lowerOrAtAnchor.sort(by: { $0.index.id.id < $1.index.id.id })
self.higherThanAnchor.sort(by: { $0.index.id.id < $1.index.id.id })
}
@ -742,6 +750,7 @@ final class HistoryViewLoadedState {
let tag: MessageTags?
let statistics: MessageHistoryViewOrderStatistics
let halfLimit: Int
let seedConfiguration: SeedConfiguration
var orderedEntriesBySpace: [PeerIdAndNamespace: OrderedHistoryViewEntries]
var holes: HistoryViewHoles
var spacesWithRemovals = Set<PeerIdAndNamespace>()
@ -752,6 +761,7 @@ final class HistoryViewLoadedState {
self.tag = tag
self.statistics = statistics
self.halfLimit = halfLimit
self.seedConfiguration = postbox.seedConfiguration
self.orderedEntriesBySpace = [:]
self.holes = holes
@ -805,7 +815,7 @@ final class HistoryViewLoadedState {
if lowerOrAtAnchorMessages.count < self.halfLimit {
let nextLowerIndex: (index: MessageIndex, includeFrom: Bool)
if let lastMessage = lowerOrAtAnchorMessages.last {
if let lastMessage = lowerOrAtAnchorMessages.min(by: { $0.index < $1.index }) {
nextLowerIndex = (lastMessage.index, false)
} else {
nextLowerIndex = (anchorIndex, true)
@ -814,7 +824,7 @@ final class HistoryViewLoadedState {
}
if higherThanAnchorMessages.count < self.halfLimit {
let nextHigherIndex: MessageIndex
if let lastMessage = higherThanAnchorMessages.last {
if let lastMessage = higherThanAnchorMessages.max(by: { $0.index < $1.index }) {
nextHigherIndex = lastMessage.index
} else {
nextHigherIndex = anchorIndex
@ -872,15 +882,19 @@ final class HistoryViewLoadedState {
}
}
entries.fixMonotonity()
if canContainHoles(space, seedConfiguration: self.seedConfiguration) {
entries.fixMonotony()
}
self.orderedEntriesBySpace[space] = entries
}
func insertHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
assert(canContainHoles(space, seedConfiguration: self.seedConfiguration))
return self.holes.insertHole(space: space, range: range)
}
func removeHole(space: PeerIdAndNamespace, range: ClosedRange<MessageId.Id>) -> Bool {
assert(canContainHoles(space, seedConfiguration: self.seedConfiguration))
return self.holes.removeHole(space: space, range: range)
}
@ -1097,7 +1111,7 @@ final class HistoryViewLoadedState {
self.spacesWithRemovals.removeAll()
}
let combinedSpacesAndIndicesByDirection = sampleEntries(orderedEntriesBySpace: self.orderedEntriesBySpace, anchor: self.anchor, halfLimit: self.halfLimit)
let (clipRanges, sampledHole) = sampleHoleRanges(orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, tag: self.tag, halfLimit: self.halfLimit)
let (clipRanges, sampledHole) = sampleHoleRanges(orderedEntriesBySpace: self.orderedEntriesBySpace, holes: self.holes, anchor: self.anchor, tag: self.tag, halfLimit: self.halfLimit, seedConfiguration: self.seedConfiguration)
var holesToLower = false
var holesToHigher = false
@ -1164,20 +1178,22 @@ private func fetchHoles(postbox: Postbox, locations: MessageHistoryViewPeerIds,
var holesBySpace: [PeerIdAndNamespace: IndexSet] = [:]
var peerIds: [PeerId] = []
switch locations {
case let .single(peerId):
peerIds.append(peerId)
case let .associated(peerId, associatedId):
peerIds.append(peerId)
if let associatedId = associatedId {
peerIds.append(associatedId.peerId)
}
case let .single(peerId):
peerIds.append(peerId)
case let .associated(peerId, associatedId):
peerIds.append(peerId)
if let associatedId = associatedId {
peerIds.append(associatedId.peerId)
}
}
let holeSpace = tag.flatMap(MessageHistoryHoleSpace.tag) ?? .everywhere
for peerId in peerIds {
for namespace in postbox.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: holeSpace) {
let indices = postbox.messageHistoryHoleIndexTable.closest(peerId: peerId, namespace: namespace, space: holeSpace, range: 1 ... (Int32.max - 1))
if !indices.isEmpty {
holesBySpace[PeerIdAndNamespace(peerId: peerId, namespace: namespace)] = indices
let peerIdAndNamespace = PeerIdAndNamespace(peerId: peerId, namespace: namespace)
assert(canContainHoles(peerIdAndNamespace, seedConfiguration: postbox.seedConfiguration))
holesBySpace[peerIdAndNamespace] = indices
}
}
}

View file

@ -1560,6 +1560,9 @@ public final class Postbox {
fileprivate func clearHistory(_ peerId: PeerId) {
self.messageHistoryTable.clearHistory(peerId: peerId, operationsByPeerId: &self.currentOperationsByPeerId, updatedMedia: &self.currentUpdatedMedia, unsentMessageOperations: &currentUnsentOperations, updatedPeerReadStateOperations: &self.currentUpdatedSynchronizeReadStateOperations, globalTagsOperations: &self.currentGlobalTagsOperations, pendingActionsOperations: &self.currentPendingMessageActionsOperations, updatedMessageActionsSummaries: &self.currentUpdatedMessageActionsSummaries, updatedMessageTagSummaries: &self.currentUpdatedMessageTagSummaries, invalidateMessageTagSummaries: &self.currentInvalidateMessageTagSummaries, localTagsOperations: &self.currentLocalTagsOperations)
for namespace in self.messageHistoryHoleIndexTable.existingNamespaces(peerId: peerId, holeSpace: .everywhere) {
self.messageHistoryHoleIndexTable.remove(peerId: peerId, namespace: namespace, space: .everywhere, range: 1 ... Int32.max - 1, operations: &self.currentPeerHoleOperations)
}
}
fileprivate func removeAllMessagesWithAuthor(_ peerId: PeerId, authorId: PeerId, namespace: MessageId.Namespace) {

View file

@ -2424,6 +2424,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
updatePeers(transaction: transaction, peers: peers, update: { _, updated in
return updated
})
updateContacts(transaction: transaction, apiUsers: users)
case let .UpdatePeer(id, f):
if let peer = f(transaction.getPeer(id)) {
updatePeers(transaction: transaction, peers: [peer], update: { _, updated in

View file

@ -32,87 +32,95 @@ func imageRepresentationsForApiChatPhoto(_ photo: Api.ChatPhoto) -> [TelegramMed
func parseTelegramGroupOrChannel(chat: Api.Chat) -> Peer? {
switch chat {
case let .chat(flags, id, title, photo, participantsCount, date, version, migratedTo, adminRights, defaultBannedRights):
let left = (flags & ((1 << 1) | (1 << 2))) != 0
var migrationReference: TelegramGroupToChannelMigrationReference?
if let migratedTo = migratedTo {
switch migratedTo {
case let .inputChannel(channelId, accessHash):
migrationReference = TelegramGroupToChannelMigrationReference(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), accessHash: accessHash)
case .inputChannelEmpty:
break
}
case let .chat(flags, id, title, photo, participantsCount, date, version, migratedTo, adminRights, defaultBannedRights):
let left = (flags & ((1 << 1) | (1 << 2))) != 0
var migrationReference: TelegramGroupToChannelMigrationReference?
if let migratedTo = migratedTo {
switch migratedTo {
case let .inputChannel(channelId, accessHash):
migrationReference = TelegramGroupToChannelMigrationReference(peerId: PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId), accessHash: accessHash)
case .inputChannelEmpty:
break
}
var groupFlags = TelegramGroupFlags()
var role: TelegramGroupRole = .member
if (flags & (1 << 0)) != 0 {
role = .creator
} else if let adminRights = adminRights {
role = .admin(TelegramChatAdminRights(apiAdminRights: adminRights))
}
var groupFlags = TelegramGroupFlags()
var role: TelegramGroupRole = .member
if (flags & (1 << 0)) != 0 {
role = .creator
} else if let adminRights = adminRights {
role = .admin(TelegramChatAdminRights(apiAdminRights: adminRights))
}
if (flags & (1 << 5)) != 0 {
groupFlags.insert(.deactivated)
}
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: title, photo: imageRepresentationsForApiChatPhoto(photo), participantCount: Int(participantsCount), role: role, membership: left ? .Left : .Member, flags: groupFlags, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init(apiBannedRights:)), migrationReference: migrationReference, creationDate: date, version: Int(version))
case let .chatEmpty(id):
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: "", photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
case let .chatForbidden(id, title):
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
case let .channel(flags, id, accessHash, title, username, photo, date, version, restrictionReason, adminRights, bannedRights, defaultBannedRights, _):
let isMin = (flags & (1 << 20)) != 0
let participationStatus: TelegramChannelParticipationStatus
if (flags & Int32(1 << 1)) != 0 {
participationStatus = .kicked
} else if (flags & Int32(1 << 2)) != 0 {
participationStatus = .left
} else {
participationStatus = .member
}
let info: TelegramChannelInfo
if (flags & Int32(1 << 8)) != 0 {
let infoFlags = TelegramChannelGroupFlags()
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
} else {
var infoFlags = TelegramChannelBroadcastFlags()
if (flags & Int32(1 << 11)) != 0 {
infoFlags.insert(.messagesShouldHaveSignatures)
}
if (flags & (1 << 5)) != 0 {
groupFlags.insert(.deactivated)
if (flags & Int32(1 << 20)) != 0 {
infoFlags.insert(.hasDiscussionGroup)
}
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: title, photo: imageRepresentationsForApiChatPhoto(photo), participantCount: Int(participantsCount), role: role, membership: left ? .Left : .Member, flags: groupFlags, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init(apiBannedRights:)), migrationReference: migrationReference, creationDate: date, version: Int(version))
case let .chatEmpty(id):
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: "", photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
case let .chatForbidden(id, title):
return TelegramGroup(id: PeerId(namespace: Namespaces.Peer.CloudGroup, id: id), title: title, photo: [], participantCount: 0, role: .member, membership: .Removed, flags: [], defaultBannedRights: nil, migrationReference: nil, creationDate: 0, version: 0)
case let .channel(flags, id, accessHash, title, username, photo, date, version, restrictionReason, adminRights, bannedRights, defaultBannedRights, _):
let isMin = (flags & (1 << 20)) != 0
let participationStatus: TelegramChannelParticipationStatus
if (flags & Int32(1 << 1)) != 0 {
participationStatus = .kicked
} else if (flags & Int32(1 << 2)) != 0 {
participationStatus = .left
info = .broadcast(TelegramChannelBroadcastInfo(flags: infoFlags))
}
var channelFlags = TelegramChannelFlags()
if (flags & Int32(1 << 0)) != 0 {
channelFlags.insert(.isCreator)
}
if (flags & Int32(1 << 7)) != 0 {
channelFlags.insert(.isVerified)
}
if (flags & Int32(1 << 19)) != 0 {
channelFlags.insert(.isScam)
}
let restrictionInfo: PeerAccessRestrictionInfo?
if let restrictionReason = restrictionReason {
restrictionInfo = PeerAccessRestrictionInfo(reason: restrictionReason)
} else {
restrictionInfo = nil
}
let accessHashValue = accessHash.flatMap { value -> TelegramPeerAccessHash in
if isMin {
return .genericPublic(value)
} else {
participationStatus = .member
return .personal(value)
}
let info: TelegramChannelInfo
if (flags & Int32(1 << 8)) != 0 {
let infoFlags = TelegramChannelGroupFlags()
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
} else {
var infoFlags = TelegramChannelBroadcastFlags()
if (flags & Int32(1 << 11)) != 0 {
infoFlags.insert(.messagesShouldHaveSignatures)
}
if (flags & Int32(1 << 20)) != 0 {
infoFlags.insert(.hasDiscussionGroup)
}
info = .broadcast(TelegramChannelBroadcastInfo(flags: infoFlags))
}
var channelFlags = TelegramChannelFlags()
if (flags & Int32(1 << 0)) != 0 {
channelFlags.insert(.isCreator)
}
if (flags & Int32(1 << 7)) != 0 {
channelFlags.insert(.isVerified)
}
if (flags & Int32(1 << 19)) != 0 {
channelFlags.insert(.isScam)
}
let restrictionInfo: PeerAccessRestrictionInfo?
if let restrictionReason = restrictionReason {
restrictionInfo = PeerAccessRestrictionInfo(reason: restrictionReason)
} else {
restrictionInfo = nil
}
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: id), accessHash: accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: version, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChatAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChatBannedRights.init), defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init))
case let .channelForbidden(flags, id, accessHash, title, untilDate):
let info: TelegramChannelInfo
if (flags & Int32(1 << 8)) != 0 {
info = .group(TelegramChannelGroupInfo(flags: []))
} else {
info = .broadcast(TelegramChannelBroadcastInfo(flags: []))
}
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: id), accessHash: accessHash, title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), defaultBannedRights: nil)
}
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: id), accessHash: accessHashValue, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: date, version: version, participationStatus: participationStatus, info: info, flags: channelFlags, restrictionInfo: restrictionInfo, adminRights: adminRights.flatMap(TelegramChatAdminRights.init), bannedRights: bannedRights.flatMap(TelegramChatBannedRights.init), defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init))
case let .channelForbidden(flags, id, accessHash, title, untilDate):
let info: TelegramChannelInfo
if (flags & Int32(1 << 8)) != 0 {
info = .group(TelegramChannelGroupInfo(flags: []))
} else {
info = .broadcast(TelegramChannelBroadcastInfo(flags: []))
}
return TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: id), accessHash: .personal(accessHash), title: title, username: nil, photo: [], creationDate: 0, version: 0, participationStatus: .kicked, info: info, flags: TelegramChannelFlags(), restrictionInfo: nil, adminRights: nil, bannedRights: TelegramChatBannedRights(flags: [.banReadMessages], untilDate: untilDate ?? Int32.max), defaultBannedRights: nil)
}
}
@ -121,7 +129,8 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
case .chat, .chatEmpty, .chatForbidden, .channelForbidden:
return parseTelegramGroupOrChannel(chat: rhs)
case let .channel(flags, _, accessHash, title, username, photo, _, _, _, _, _, defaultBannedRights, _/*feed*//*, feedId*/):
if accessHash != nil && (flags & (1 << 12)) == 0 {
let isMin = (flags & (1 << 12)) != 0
if accessHash != nil && !isMin {
return parseTelegramGroupOrChannel(chat: rhs)
} else if let lhs = lhs as? TelegramChannel {
var channelFlags = lhs.flags
@ -132,11 +141,11 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
}
var info = lhs.info
switch info {
case .broadcast:
break
case .group:
let infoFlags = TelegramChannelGroupFlags()
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
case .broadcast:
break
case .group:
let infoFlags = TelegramChannelGroupFlags()
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
}
return TelegramChannel(id: lhs.id, accessHash: lhs.accessHash, title: title, username: username, photo: imageRepresentationsForApiChatPhoto(photo), creationDate: lhs.creationDate, version: lhs.version, participationStatus: lhs.participationStatus, info: info, flags: channelFlags, restrictionInfo: lhs.restrictionInfo, adminRights: lhs.adminRights, bannedRights: lhs.bannedRights, defaultBannedRights: defaultBannedRights.flatMap(TelegramChatBannedRights.init))
} else {

View file

@ -56,7 +56,7 @@ public enum PeerReference: PostboxCoding, Hashable, Equatable {
switch peer {
case let user as TelegramUser:
if let accessHash = user.accessHash {
self = .user(id: user.id.id, accessHash: accessHash)
self = .user(id: user.id.id, accessHash: accessHash.value)
} else {
return nil
}
@ -64,7 +64,7 @@ public enum PeerReference: PostboxCoding, Hashable, Equatable {
self = .group(id: group.id.id)
case let channel as TelegramChannel:
if let accessHash = channel.accessHash {
self = .channel(id: channel.id.id, accessHash: accessHash)
self = .channel(id: channel.id.id, accessHash: accessHash.value)
} else {
return nil
}
@ -104,12 +104,12 @@ public enum PeerReference: PostboxCoding, Hashable, Equatable {
func forceApiInputPeer(_ peer: Peer) -> Api.InputPeer? {
switch peer {
case let user as TelegramUser:
return Api.InputPeer.inputPeerUser(userId: user.id.id, accessHash: user.accessHash ?? 0)
return Api.InputPeer.inputPeerUser(userId: user.id.id, accessHash: user.accessHash?.value ?? 0)
case let group as TelegramGroup:
return Api.InputPeer.inputPeerChat(chatId: group.id.id)
case let channel as TelegramChannel:
if let accessHash = channel.accessHash {
return Api.InputPeer.inputPeerChannel(channelId: channel.id.id, accessHash: accessHash)
return Api.InputPeer.inputPeerChannel(channelId: channel.id.id, accessHash: accessHash.value)
} else {
return nil
}
@ -121,12 +121,12 @@ func forceApiInputPeer(_ peer: Peer) -> Api.InputPeer? {
func apiInputPeer(_ peer: Peer) -> Api.InputPeer? {
switch peer {
case let user as TelegramUser where user.accessHash != nil:
return Api.InputPeer.inputPeerUser(userId: user.id.id, accessHash: user.accessHash!)
return Api.InputPeer.inputPeerUser(userId: user.id.id, accessHash: user.accessHash!.value)
case let group as TelegramGroup:
return Api.InputPeer.inputPeerChat(chatId: group.id.id)
case let channel as TelegramChannel:
if let accessHash = channel.accessHash {
return Api.InputPeer.inputPeerChannel(channelId: channel.id.id, accessHash: accessHash)
return Api.InputPeer.inputPeerChannel(channelId: channel.id.id, accessHash: accessHash.value)
} else {
return nil
}
@ -137,7 +137,7 @@ func apiInputPeer(_ peer: Peer) -> Api.InputPeer? {
func apiInputChannel(_ peer: Peer) -> Api.InputChannel? {
if let channel = peer as? TelegramChannel, let accessHash = channel.accessHash {
return Api.InputChannel.inputChannel(channelId: channel.id.id, accessHash: accessHash)
return Api.InputChannel.inputChannel(channelId: channel.id.id, accessHash: accessHash.value)
} else {
return nil
}
@ -145,7 +145,7 @@ func apiInputChannel(_ peer: Peer) -> Api.InputChannel? {
func apiInputUser(_ peer: Peer) -> Api.InputUser? {
if let user = peer as? TelegramUser, let accessHash = user.accessHash {
return Api.InputUser.inputUser(userId: user.id.id, accessHash: accessHash)
return Api.InputUser.inputUser(userId: user.id.id, accessHash: accessHash.value)
} else {
return nil
}

View file

@ -143,7 +143,6 @@ private final class HistoryPreloadViewContext {
private enum ChatHistoryPreloadEntity: Hashable {
case peer(PeerId)
//case group(PeerGroupId)
}
private struct ChatHistoryPreloadIndex {
@ -317,7 +316,9 @@ final class ChatHistoryPreloadManager {
return
}
#if DEBUG
//return;
if true {
//return
}
#endif
var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = []
for entry in view.0.entries {

View file

@ -0,0 +1,21 @@
import Foundation
#if os(macOS)
import SwiftSignalKitMac
import PostboxMac
#else
import SwiftSignalKit
import Postbox
#endif
import TelegramApi
public enum DeleteAccountError {
case generic
}
public func deleteAccount(account: Account) -> Signal<Never, DeleteAccountError> {
return account.network.request(Api.functions.account.deleteAccount(reason: "GDPR"))
|> mapError { _ -> DeleteAccountError in
return .generic
}
|> ignoreValues
}

View file

@ -1,53 +0,0 @@
import Foundation
#if os(macOS)
import PostboxMac
import SwiftSignalKitMac
#else
import Postbox
import SwiftSignalKit
#endif
import TelegramApi
public func availableGroupFeedPeers(postbox: Postbox, network: Network, groupId: PeerGroupId) -> Signal<[(Peer, Bool)], NoError> {
/*feed*/
return .single([])
/*return network.request(Api.functions.channels.getFeedSources(flags: 0, feedId: groupId.rawValue, hash: 0))
|> retryRequest
|> mapToSignal { result -> Signal<[(Peer, Bool)], NoError> in
return postbox.transaction { transaction -> [(Peer, Bool)] in
switch result {
case .feedSourcesNotModified:
return []
case let .feedSources(_, newlyJoinedFeed, feeds, chats, users):
var includedPeerIds = Set<PeerId>()
var excludedPeerIds = Set<PeerId>()
for feedsInfo in feeds {
switch feedsInfo {
case let .feedBroadcasts(feedId, channels):
if feedId == groupId.rawValue {
for id in channels {
includedPeerIds.insert(PeerId(namespace: Namespaces.Peer.CloudChannel, id: id))
}
}
case let .feedBroadcastsUngrouped(channels):
for id in channels {
excludedPeerIds.insert(PeerId(namespace: Namespaces.Peer.CloudChannel, id: id))
}
}
}
var peers: [(Peer, Bool)] = []
for peerId in includedPeerIds {
if let peer = transaction.getPeer(peerId) {
peers.append((peer, true))
}
}
for peerId in excludedPeerIds {
if let peer = transaction.getPeer(peerId) {
peers.append((peer, false))
}
}
return peers
}
}
}*/
}

View file

@ -12,7 +12,7 @@ public func loadedPeerFromMessage(account: Account, peerId: PeerId, messageId: M
return account.postbox.transaction { transaction -> Signal<Peer?, NoError> in
if let peer = transaction.getPeer(peerId) {
if let user = peer as? TelegramUser {
if user.accessHash != 0 {
if let accessHash = user.accessHash, accessHash.value != 0 {
return .single(user)
} else {
let messageSignal: Signal<Api.messages.Messages?, NoError>?
@ -50,7 +50,7 @@ public func loadedPeerFromMessage(account: Account, peerId: PeerId, messageId: M
for user in apiUsers {
let telegramUser = TelegramUser(user: user)
if telegramUser.id == peerId && telegramUser.accessHash != 0 {
if telegramUser.id == peerId, let accessHash = telegramUser.accessHash, accessHash.value != 0 {
if let presence = TelegramUserPresence(apiUser: user) {
updatePeerPresences(transaction: transaction, accountPeerId: account.peerId, peerPresences: [telegramUser.id: presence])
}

View file

@ -143,6 +143,12 @@ func locallyRenderedMessage(message: StoreMessage, peers: [PeerId: Peer]) -> Mes
if let peer = peers[id.peerId] {
messagePeers[peer.id] = peer
if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference {
if let channelPeer = peers[migrationReference.peerId] {
messagePeers[channelPeer.id] = channelPeer
}
}
}
for media in message.media {

View file

@ -22,12 +22,15 @@ public final class NotificationExceptionsList: Equatable {
}
}
public func notificationExceptionsList(network: Network) -> Signal<NotificationExceptionsList, NoError> {
return network.request(Api.functions.account.getNotifyExceptions(flags: 1 << 1, peer: nil)) |> retryRequest |> map { result in
switch result {
public func notificationExceptionsList(postbox: Postbox, network: Network) -> Signal<NotificationExceptionsList, NoError> {
return network.request(Api.functions.account.getNotifyExceptions(flags: 1 << 1, peer: nil))
|> retryRequest
|> mapToSignal { result -> Signal<NotificationExceptionsList, NoError> in
return postbox.transaction { transaction -> NotificationExceptionsList in
switch result {
case let .updates(updates, users, chats, _, _):
var peers:[PeerId: Peer] = [:]
var settings:[PeerId : TelegramPeerNotificationSettings] = [:]
var peers: [PeerId: Peer] = [:]
var settings: [PeerId: TelegramPeerNotificationSettings] = [:]
for user in users {
let peer = TelegramUser(user: user)
@ -39,32 +42,35 @@ public func notificationExceptionsList(network: Network) -> Signal<NotificationE
}
}
updatePeers(transaction: transaction, peers: Array(peers.values), update: { _, updated in updated })
for update in updates {
switch update {
case let .updateNotifySettings(apiPeer, notifySettings):
switch apiPeer {
case let .notifyPeer(notifyPeer):
let peerId: PeerId
switch notifyPeer {
case let .peerUser(userId):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
case let .peerChat(chatId):
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)
case let .peerChannel(channelId):
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
}
settings[peerId] = TelegramPeerNotificationSettings(apiSettings: notifySettings)
default:
break
case let .updateNotifySettings(apiPeer, notifySettings):
switch apiPeer {
case let .notifyPeer(notifyPeer):
let peerId: PeerId
switch notifyPeer {
case let .peerUser(userId):
peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)
case let .peerChat(chatId):
peerId = PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)
case let .peerChannel(channelId):
peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: channelId)
}
settings[peerId] = TelegramPeerNotificationSettings(apiSettings: notifySettings)
default:
break
}
default:
break
}
}
return NotificationExceptionsList(peers: peers, settings: settings)
default:
return NotificationExceptionsList(peers: [:], settings: [:])
}
}
}
}

View file

@ -3,4 +3,13 @@ import Foundation
public enum TelegramPeerAccessHash: Hashable {
case personal(Int64)
case genericPublic(Int64)
public var value: Int64 {
switch self {
case let .personal(personal):
return personal
case let .genericPublic(genericPublic):
return genericPublic
}
}
}

View file

@ -275,10 +275,8 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q
}
return combineLatest(peerMessages, additionalPeerMessages)
}
case let .group(groupId):
case .group:
remoteSearchResult = .single((nil, nil))
/*remoteSearchResult = account.network.request(Api.functions.channels.searchFeed(feedId: groupId.rawValue, q: query, offsetDate: 0, offsetPeer: Api.InputPeer.inputPeerEmpty, offsetId: 0, limit: 64), automaticFloodWait: false)
|> mapError { _ in } |> map(Optional.init)*/
case .general:
remoteSearchResult = account.postbox.transaction { transaction -> (Int32, MessageIndex?, Api.InputPeer) in
var lowerBound: MessageIndex?

View file

@ -148,7 +148,7 @@ public struct TelegramChannelFlags: OptionSet {
public final class TelegramChannel: Peer {
public let id: PeerId
public let accessHash: Int64?
public let accessHash: TelegramPeerAccessHash?
public let title: String
public let username: String?
public let photo: [TelegramMediaImageRepresentation]
@ -169,7 +169,7 @@ public final class TelegramChannel: Peer {
public let associatedPeerId: PeerId? = nil
public let notificationSettingsPeerId: PeerId? = nil
public init(id: PeerId, accessHash: Int64?, title: String, username: String?, photo: [TelegramMediaImageRepresentation], creationDate: Int32, version: Int32, participationStatus: TelegramChannelParticipationStatus, info: TelegramChannelInfo, flags: TelegramChannelFlags, restrictionInfo: PeerAccessRestrictionInfo?, adminRights: TelegramChatAdminRights?, bannedRights: TelegramChatBannedRights?, defaultBannedRights: TelegramChatBannedRights?) {
public init(id: PeerId, accessHash: TelegramPeerAccessHash?, title: String, username: String?, photo: [TelegramMediaImageRepresentation], creationDate: Int32, version: Int32, participationStatus: TelegramChannelParticipationStatus, info: TelegramChannelInfo, flags: TelegramChannelFlags, restrictionInfo: PeerAccessRestrictionInfo?, adminRights: TelegramChatAdminRights?, bannedRights: TelegramChatBannedRights?, defaultBannedRights: TelegramChatBannedRights?) {
self.id = id
self.accessHash = accessHash
self.title = title
@ -188,7 +188,17 @@ public final class TelegramChannel: Peer {
public init(decoder: PostboxDecoder) {
self.id = PeerId(decoder.decodeInt64ForKey("i", orElse: 0))
self.accessHash = decoder.decodeOptionalInt64ForKey("ah")
let accessHash = decoder.decodeOptionalInt64ForKey("ah")
let accessHashType: Int32 = decoder.decodeInt32ForKey("aht", orElse: 0)
if let accessHash = accessHash {
if accessHashType == 0 {
self.accessHash = .personal(accessHash)
} else {
self.accessHash = .genericPublic(accessHash)
}
} else {
self.accessHash = nil
}
self.title = decoder.decodeStringForKey("t", orElse: "")
self.username = decoder.decodeOptionalStringForKey("un")
self.photo = decoder.decodeObjectArrayForKey("ph")
@ -206,7 +216,14 @@ public final class TelegramChannel: Peer {
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt64(self.id.toInt64(), forKey: "i")
if let accessHash = self.accessHash {
encoder.encodeInt64(accessHash, forKey: "ah")
switch accessHash {
case let .personal(value):
encoder.encodeInt64(value, forKey: "ah")
encoder.encodeInt32(0, forKey: "aht")
case let .genericPublic(value):
encoder.encodeInt64(value, forKey: "ah")
encoder.encodeInt32(1, forKey: "aht")
}
} else {
encoder.encodeNil(forKey: "ah")
}

View file

@ -61,15 +61,11 @@ public struct BotUserInfo: PostboxCoding, Equatable {
encoder.encodeNil(forKey: "ip")
}
}
public static func ==(lhs: BotUserInfo, rhs: BotUserInfo) -> Bool {
return lhs.flags == rhs.flags && lhs.inlinePlaceholder == rhs.inlinePlaceholder
}
}
public final class TelegramUser: Peer {
public let id: PeerId
public let accessHash: Int64?
public let accessHash: TelegramPeerAccessHash?
public let firstName: String?
public let lastName: String?
public let username: String?
@ -100,7 +96,7 @@ public final class TelegramUser: Peer {
public let associatedPeerId: PeerId? = nil
public let notificationSettingsPeerId: PeerId? = nil
public init(id: PeerId, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: [TelegramMediaImageRepresentation], botInfo: BotUserInfo?, restrictionInfo: PeerAccessRestrictionInfo?, flags: UserInfoFlags) {
public init(id: PeerId, accessHash: TelegramPeerAccessHash?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: [TelegramMediaImageRepresentation], botInfo: BotUserInfo?, restrictionInfo: PeerAccessRestrictionInfo?, flags: UserInfoFlags) {
self.id = id
self.accessHash = accessHash
self.firstName = firstName
@ -117,8 +113,13 @@ public final class TelegramUser: Peer {
self.id = PeerId(decoder.decodeInt64ForKey("i", orElse: 0))
let accessHash: Int64 = decoder.decodeInt64ForKey("ah", orElse: 0)
let accessHashType: Int32 = decoder.decodeInt32ForKey("aht", orElse: 0)
if accessHash != 0 {
self.accessHash = accessHash
if accessHashType == 0 {
self.accessHash = .personal(accessHash)
} else {
self.accessHash = .genericPublic(accessHash)
}
} else {
self.accessHash = nil
}
@ -146,7 +147,14 @@ public final class TelegramUser: Peer {
encoder.encodeInt64(self.id.toInt64(), forKey: "i")
if let accessHash = self.accessHash {
encoder.encodeInt64(accessHash, forKey: "ah")
switch accessHash {
case let .personal(value):
encoder.encodeInt64(value, forKey: "ah")
encoder.encodeInt32(0, forKey: "aht")
case let .genericPublic(value):
encoder.encodeInt64(value, forKey: "ah")
encoder.encodeInt32(1, forKey: "aht")
}
}
if let firstName = self.firstName {
@ -242,7 +250,7 @@ public final class TelegramUser: Peer {
func parsedTelegramProfilePhoto(_ photo: Api.UserProfilePhoto) -> [TelegramMediaImageRepresentation] {
var representations: [TelegramMediaImageRepresentation] = []
switch photo {
case let .userProfilePhoto(photoId, photoSmall, photoBig, dcId):
case let .userProfilePhoto(_, photoSmall, photoBig, dcId):
let smallResource: TelegramMediaResource
let fullSizeResource: TelegramMediaResource
switch photoSmall {
@ -267,6 +275,15 @@ extension TelegramUser {
case let .user(flags, id, accessHash, firstName, lastName, username, phone, photo, _, _, restrictionReason, botInlinePlaceholder, _):
let representations: [TelegramMediaImageRepresentation] = photo.flatMap(parsedTelegramProfilePhoto) ?? []
let isMin = (flags & (1 << 20)) != 0
let accessHashValue = accessHash.flatMap { value -> TelegramPeerAccessHash in
if isMin {
return .genericPublic(value)
} else {
return .personal(value)
}
}
var userFlags: UserInfoFlags = []
if (flags & (1 << 17)) != 0 {
userFlags.insert(.isVerified)
@ -295,7 +312,7 @@ extension TelegramUser {
let restrictionInfo: PeerAccessRestrictionInfo? = restrictionReason.flatMap(PeerAccessRestrictionInfo.init)
self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: id), accessHash: accessHash, firstName: firstName, lastName: lastName, username: username, phone: phone, photo: representations, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags)
self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: id), accessHash: accessHashValue, firstName: firstName, lastName: lastName, username: username, phone: phone, photo: representations, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags)
case let .userEmpty(id):
self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: id), accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil, restrictionInfo: nil, flags: [])
}
@ -303,7 +320,7 @@ extension TelegramUser {
static func merge(_ lhs: TelegramUser?, rhs: Api.User) -> TelegramUser? {
switch rhs {
case let .user(flags, _, _, _, _, username, _, photo, _, _, restrictionReason, botInlinePlaceholder, _):
case let .user(flags, _, rhsAccessHash, _, _, username, _, photo, _, _, restrictionReason, botInlinePlaceholder, _):
let isMin = (flags & (1 << 20)) != 0
if !isMin {
return TelegramUser(user: rhs)
@ -338,7 +355,22 @@ extension TelegramUser {
let restrictionInfo: PeerAccessRestrictionInfo? = restrictionReason.flatMap(PeerAccessRestrictionInfo.init)
return TelegramUser(id: lhs.id, accessHash: lhs.accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: username, phone: lhs.phone, photo: telegramPhoto, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags)
let rhsAccessHashValue = rhsAccessHash.flatMap { value -> TelegramPeerAccessHash in
if isMin {
return .genericPublic(value)
} else {
return .personal(value)
}
}
let accessHash: TelegramPeerAccessHash?
if let rhsAccessHashValue = rhsAccessHashValue, case .personal = rhsAccessHashValue {
accessHash = rhsAccessHashValue
} else {
accessHash = lhs.accessHash ?? rhsAccessHashValue
}
return TelegramUser(id: lhs.id, accessHash: accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: username, phone: lhs.phone, photo: telegramPhoto, botInfo: botInfo, restrictionInfo: restrictionInfo, flags: userFlags)
} else {
return TelegramUser(user: rhs)
}

View file

@ -4,6 +4,7 @@ import Foundation
#else
import Postbox
#endif
import TelegramApi
func updatePeerChatInclusionWithMinTimestamp(transaction: Transaction, id: PeerId, minTimestamp: Int32, forceRootGroupIfNotExists: Bool) {
let currentInclusion = transaction.getPeerChatListInclusion(id)
@ -136,3 +137,34 @@ func updatePeerPresenceLastActivities(transaction: Transaction, accountPeerId: P
})
}
}
func updateContacts(transaction: Transaction, apiUsers: [Api.User]) {
if apiUsers.isEmpty {
return
}
var contactIds = transaction.getContactPeerIds()
var updated = false
for user in apiUsers {
let isContact: Bool
switch user {
case let .user(user):
isContact = (user.flags & (1 << 11)) != 0
case .userEmpty:
isContact = false
}
if isContact {
if !contactIds.contains(user.peerId) {
contactIds.insert(user.peerId)
updated = true
}
} else {
if contactIds.contains(user.peerId) {
contactIds.remove(user.peerId)
updated = true
}
}
}
if updated {
transaction.replaceContactPeerIds(contactIds)
}
}

View file

@ -400,6 +400,8 @@
D0575AF21E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AF01E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift */; };
D0575AF41E9FFDDE006F2541 /* ManagedSynchronizeSavedGifsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AF31E9FFDDD006F2541 /* ManagedSynchronizeSavedGifsOperations.swift */; };
D0575AF51E9FFDDE006F2541 /* ManagedSynchronizeSavedGifsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575AF31E9FFDDD006F2541 /* ManagedSynchronizeSavedGifsOperations.swift */; };
D0575C2D22B922DF00A71A0E /* DeleteAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575C2C22B922DF00A71A0E /* DeleteAccount.swift */; };
D0575C2E22B922DF00A71A0E /* DeleteAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0575C2C22B922DF00A71A0E /* DeleteAccount.swift */; };
D058E0D11E8AD65C00A442DE /* StandaloneSendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D058E0D01E8AD65C00A442DE /* StandaloneSendMessage.swift */; };
D058E0D21E8AD65C00A442DE /* StandaloneSendMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D058E0D01E8AD65C00A442DE /* StandaloneSendMessage.swift */; };
D05A32E11E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05A32E01E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift */; };
@ -770,8 +772,6 @@
D0F7B1EA1E045C87007EB8A5 /* ChangePeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843961DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift */; };
D0F7B1EB1E045C87007EB8A5 /* ResolvePeerByName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3CC7C1DDE289E008148FA /* ResolvePeerByName.swift */; };
D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827BA1E00451F00071108 /* SearchPeers.swift */; };
D0F8C39D20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */; };
D0F8C39E20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */; };
D0F8C3A02017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; };
D0F8C3A12017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; };
D0FA08BB2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */; };
@ -1025,6 +1025,7 @@
D0561DE91E5754FA00E6B9E9 /* ChannelAdmins.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAdmins.swift; sourceTree = "<group>"; };
D0575AF01E9FFA5D006F2541 /* SynchronizeSavedGifsOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeSavedGifsOperation.swift; sourceTree = "<group>"; };
D0575AF31E9FFDDD006F2541 /* ManagedSynchronizeSavedGifsOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeSavedGifsOperations.swift; sourceTree = "<group>"; };
D0575C2C22B922DF00A71A0E /* DeleteAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAccount.swift; sourceTree = "<group>"; };
D058E0D01E8AD65C00A442DE /* StandaloneSendMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandaloneSendMessage.swift; sourceTree = "<group>"; };
D05A32E01E6F0982002760B4 /* UpdatedAccountPrivacySettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdatedAccountPrivacySettings.swift; sourceTree = "<group>"; };
D05A32E31E6F0B2E002760B4 /* RecentAccountSessions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecentAccountSessions.swift; sourceTree = "<group>"; };
@ -1226,7 +1227,6 @@
D0F760D722202FE20074F7E5 /* ChannelStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelStats.swift; sourceTree = "<group>"; };
D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditedMessageAttribute.swift; sourceTree = "<group>"; };
D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = "<group>"; };
D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupFeedPeers.swift; sourceTree = "<group>"; };
D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalTelegramCoreConfiguration.swift; sourceTree = "<group>"; };
D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentPrivacySettings.swift; sourceTree = "<group>"; };
D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoStepVerification.swift; sourceTree = "<group>"; };
@ -1637,6 +1637,7 @@
D01843A72190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift */,
D02B198F21FB1D520094A764 /* RegisterNotificationToken.swift */,
D0338742223BD532007A2CE4 /* InitializeAccountAfterLogin.swift */,
D0575C2C22B922DF00A71A0E /* DeleteAccount.swift */,
);
name = Account;
sourceTree = "<group>";
@ -1975,7 +1976,6 @@
D0DA1D311F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift */,
D02395D51F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift */,
D0C44B601FC616E200227BE0 /* SearchGroupMembers.swift */,
D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */,
D018EE042045E95000CBB130 /* CheckPeerChatServiceActions.swift */,
9F06830F21A40DEC001D8EDB /* NotificationExceptionsList.swift */,
D0F760D722202FE20074F7E5 /* ChannelStats.swift */,
@ -2543,7 +2543,6 @@
C2366C861E4F403C0097CCFF /* AddressNames.swift in Sources */,
D0529D2421A4123400D7C3C4 /* SynchronizeRecentlyUsedMediaOperations.swift in Sources */,
D0FA08BB2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */,
D0F8C39D20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */,
D0B843C11DA7FF30005F29E1 /* NBPhoneMetaData.m in Sources */,
D0528E601E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */,
D08CAA841ED8164B0000FDA8 /* Localization.swift in Sources */,
@ -2564,6 +2563,7 @@
D054649A20738760002ECC1E /* SecureIdRentalAgreementValue.swift in Sources */,
D0B843BF1DA7FF30005F29E1 /* NBNumberFormat.m in Sources */,
D0E305A71E5B5CBE00D7A3A2 /* PeerAdmins.swift in Sources */,
D0575C2D22B922DF00A71A0E /* DeleteAccount.swift in Sources */,
D0B843C51DA7FF30005F29E1 /* NBPhoneNumber.m in Sources */,
D03B0D0D1D62255C00955575 /* SynchronizePeerReadState.swift in Sources */,
D03B0D081D62255C00955575 /* ChannelState.swift in Sources */,
@ -2803,7 +2803,6 @@
D0529D2521A4123400D7C3C4 /* SynchronizeRecentlyUsedMediaOperations.swift in Sources */,
D06ECFC920B810D300C576C2 /* TermsOfService.swift in Sources */,
9F06831121A40DEC001D8EDB /* NotificationExceptionsList.swift in Sources */,
D0F8C39E20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */,
D0AB262C21C3CE80008F6685 /* Polls.swift in Sources */,
D073CE6C1DCBCF17007511FD /* TextEntitiesMessageAttribute.swift in Sources */,
D03C53751DAD5CA9004C17B3 /* TelegramUserPresence.swift in Sources */,
@ -2844,6 +2843,7 @@
D08F4A671E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */,
D05A32E81E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */,
D0F7B1E31E045C7B007EB8A5 /* RichText.swift in Sources */,
D0575C2E22B922DF00A71A0E /* DeleteAccount.swift in Sources */,
D0FA8BB11E1FEC7E001E855B /* SecretChatEncryptionConfig.swift in Sources */,
D0B418AA1D7E0597004562A4 /* Download.swift in Sources */,
D001F3F41E128A1C007A8C60 /* UpdatesApiUtils.swift in Sources */,

View file

@ -28,6 +28,7 @@ private func encodeText(_ string: String, _ key: Int) -> String {
}
private let statusBarRootViewClass: AnyClass = NSClassFromString("UIStatusBar")!
private let statusBarPlaceholderClass: AnyClass? = NSClassFromString("UIStatusBar_Placeholder")
private let cutoutStatusBarForegroundClass: AnyClass? = NSClassFromString("_UIStatusBar")
private let keyboardViewClass: AnyClass? = NSClassFromString(encodeText("VJJoqvuTfuIptuWjfx", -1))!
private let keyboardViewContainerClass: AnyClass? = NSClassFromString(encodeText("VJJoqvuTfuDpoubjofsWjfx", -1))!
@ -65,6 +66,12 @@ private class ApplicationStatusBarHost: StatusBarHost {
if containerView.isKind(of: statusBarRootViewClass) {
return containerView
}
if let statusBarPlaceholderClass = statusBarPlaceholderClass {
if containerView.isKind(of: statusBarPlaceholderClass) {
return containerView
}
}
for subview in containerView.subviews {
if let cutoutStatusBarForegroundClass = cutoutStatusBarForegroundClass, subview.isKind(of: cutoutStatusBarForegroundClass) {

View file

@ -323,10 +323,18 @@ final class AuthorizedApplicationContext {
}))
let accountId = context.account.id
self.loggedOutDisposable.set(context.account.loggedOut.start(next: { value in
self.loggedOutDisposable.set((context.account.loggedOut
|> deliverOnMainQueue).start(next: { [weak self] value in
if value {
Logger.shared.log("ApplicationContext", "account logged out")
let _ = logoutFromAccount(id: accountId, accountManager: accountManager, alreadyLoggedOutRemotely: false).start()
if let strongSelf = self {
strongSelf.rootController.currentWindow?.forEachController { controller in
if let controller = controller as? TermsOfServiceController {
controller.dismiss()
}
}
}
}
}))
@ -486,15 +494,23 @@ final class AuthorizedApplicationContext {
})
}
declineImpl = {
declineImpl = { [weak controller] in
guard let strongSelf = self else {
return
}
let _ = (strongSelf.context.account.postbox.loadedPeerWithId(strongSelf.context.account.peerId)
|> deliverOnMainQueue).start(next: { peer in
if let phone = (peer as? TelegramUser)?.phone {
UIApplication.shared.openURL(URL(string: "https://telegram.org/deactivate?phone=\(phone)")!)
let accountId = strongSelf.context.account.id
let accountManager = strongSelf.context.sharedContext.accountManager
let _ = (deleteAccount(account: strongSelf.context.account)
|> deliverOnMainQueue).start(error: { _ in
guard let strongSelf = self else {
return
}
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = textAlertController(context: strongSelf.context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
}, completed: {
controller?.dismiss()
let _ = logoutFromAccount(id: accountId, accountManager: accountManager, alreadyLoggedOutRemotely: true).start()
})
}

View file

@ -1573,7 +1573,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
}
var didDisplayActionsPanel = false
if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings {
if let contactStatus = strongSelf.presentationInterfaceState.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
if !peerStatusSettings.isEmpty {
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
didDisplayActionsPanel = true
@ -1588,7 +1588,7 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
}
var displayActionsPanel = false
if let contactStatus = contactStatus, let peerStatusSettings = contactStatus.peerStatusSettings {
if let contactStatus = contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings {
if !peerStatusSettings.isEmpty {
if contactStatus.canAddContact && peerStatusSettings.contains(.canAddContact) {
displayActionsPanel = true
@ -2258,7 +2258,10 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
insertItems.append(ListViewInsertItem(index: item.index, previousIndex: item.previousIndex, item: item.item, directionHint: item.directionHint == .Down ? .Up : nil))
}
let scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: 0.2), directionHint: .Up)
var scrollToItem: ListViewScrollToItem?
if transition.historyView.originalView.laterId == nil {
scrollToItem = ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: 0.2), directionHint: .Up)
}
var stationaryItemRange: (Int, Int)?
if let maxInsertedItem = maxInsertedItem {
@ -2280,7 +2283,8 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation {
strongSelf.commitPurposefulAction()
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: strongSelf.transformEnqueueMessages(messages)) |> deliverOnMainQueue).start(next: { _ in
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: strongSelf.transformEnqueueMessages(messages))
|> deliverOnMainQueue).start(next: { _ in
if let strongSelf = self {
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
}
@ -5391,6 +5395,14 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
self.chatDisplayNode.historyNode.scrollToEndOfHistory()
}
func updateTextInputState(_ textInputState: ChatTextInputState) {
self.updateChatPresentationInterfaceState(interactive: false, { state in
state.updatedInterfaceState({ state in
state.withUpdatedComposeInputState(textInputState)
})
})
}
public func navigateToMessage(messageLocation: NavigateToMessageLocation, animated: Bool, forceInCurrentChat: Bool = false, completion: (() -> Void)? = nil, customPresentProgress: ((ViewController, Any?) -> Void)? = nil) {
self.navigateToMessage(from: nil, to: messageLocation, rememberInStack: false, forceInCurrentChat: forceInCurrentChat, animated: animated, completion: completion, customPresentProgress: customPresentProgress)
}
@ -5698,8 +5710,8 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
})
})
|> deliverOnMainQueue).start(completed: { [weak self] in
if let strongSelf = self {
(strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(context: strongSelf.context, chatLocation: .peer(peerId), messageId: messageId))
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
navigateToChatController(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), messageId: messageId, updateTextInputState: textInputState)
}
})
} else {
@ -5920,9 +5932,15 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
let title: String
var infoString: String?
if let _ = peer as? TelegramGroup {
title = self.presentationData.strings.Conversation_ReportSpam
} else if let _ = peer as? TelegramChannel {
title = self.presentationData.strings.Conversation_ReportSpam
title = self.presentationData.strings.Conversation_ReportSpamAndLeave
infoString = self.presentationData.strings.Conversation_ReportSpamGroupConfirmation
} else if let channel = peer as? TelegramChannel {
title = self.presentationData.strings.Conversation_ReportSpamAndLeave
if case .group = channel.info {
infoString = self.presentationData.strings.Conversation_ReportSpamGroupConfirmation
} else {
infoString = self.presentationData.strings.Conversation_ReportSpamChannelConfirmation
}
} else {
title = self.presentationData.strings.Conversation_ReportSpam
infoString = self.presentationData.strings.Conversation_ReportSpamConfirmation
@ -5955,25 +5973,42 @@ public final class ChatController: TelegramController, GalleryHiddenMediaTarget,
guard let strongSelf = self else {
return
}
guard let _ = accountPeer as? TelegramUser else {
guard let user = accountPeer as? TelegramUser, let phoneNumber = user.phone else {
return
}
guard let peer = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramUser else {
return
}
let _ = (acceptAndShareContact(account: strongSelf.context.account, peerId: peer.id)
|> deliverOnMainQueue).start(error: { _ in
let actionSheet = ActionSheetController(presentationTheme: strongSelf.presentationData.theme)
var items: [ActionSheetItem] = []
items.append(ActionSheetTextItem(title: strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumberConfirmation(formatPhoneNumber(phoneNumber), peer.compactDisplayTitle).0))
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumber, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
guard let strongSelf = self else {
return
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: {
guard let strongSelf = self else {
return
}
strongSelf.present(OverlayStatusController(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, type: .genericSuccess(strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumber_StatusSuccess(peer.compactDisplayTitle).0, true)), in: .window(.root))
})
let _ = (acceptAndShareContact(account: strongSelf.context.account, peerId: peer.id)
|> deliverOnMainQueue).start(error: { _ in
guard let strongSelf = self else {
return
}
strongSelf.present(textAlertController(context: strongSelf.context, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, completed: {
guard let strongSelf = self else {
return
}
strongSelf.present(OverlayStatusController(theme: strongSelf.presentationData.theme, strings: strongSelf.presentationData.strings, type: .genericSuccess(strongSelf.presentationData.strings.Conversation_ShareMyPhoneNumber_StatusSuccess(peer.compactDisplayTitle).0, true)), in: .window(.root))
})
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
strongSelf.chatDisplayNode.dismissInput()
strongSelf.present(actionSheet, in: .window(.root))
})
}

View file

@ -21,15 +21,13 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
var groupBucket: [(Message, Bool, ChatHistoryMessageSelection, ChatMessageEntryAttributes)] = []
loop: for entry in view.entries {
if entry.message.id.peerId.namespace == Namespaces.Peer.CloudChannel || entry.message.id.peerId.namespace == Namespaces.Peer.CloudUser {
for media in entry.message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case .channelMigratedFromGroup, .groupMigratedToChannel, .historyCleared:
continue loop
default:
break
}
for media in entry.message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case .channelMigratedFromGroup, .groupMigratedToChannel, .historyCleared:
continue loop
default:
break
}
}
}
@ -82,7 +80,9 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
let unreadEntry: ChatHistoryEntry = .UnreadEntry(maxReadIndex, presentationData)
for entry in entries {
if entry > unreadEntry {
entries.insert(unreadEntry, at: i)
if i != 0 {
entries.insert(unreadEntry, at: i)
}
break
}
i += 1
@ -100,38 +100,39 @@ func chatHistoryEntriesForView(location: ChatLocation, view: MessageHistoryView,
}
if let cachedPeerData = cachedPeerData as? CachedUserData, let botInfo = cachedPeerData.botInfo, !botInfo.description.isEmpty {
entries.insert(.ChatInfoEntry(botInfo.description, presentationData), at: 0)
}
var isEmpty = true
if entries.count <= 3 {
loop: for entry in view.entries {
var isEmptyMedia = false
for media in entry.message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case .groupCreated, .photoUpdated, .channelMigratedFromGroup, .groupMigratedToChannel:
isEmptyMedia = true
default:
break
} else {
var isEmpty = true
if entries.count <= 3 {
loop: for entry in view.entries {
var isEmptyMedia = false
for media in entry.message.media {
if let action = media as? TelegramMediaAction {
switch action.action {
case .groupCreated, .photoUpdated, .channelMigratedFromGroup, .groupMigratedToChannel:
isEmptyMedia = true
default:
break
}
}
}
var isCreator = false
if let peer = entry.message.peers[entry.message.id.peerId] as? TelegramGroup, case .creator = peer.role {
isCreator = true
} else if let peer = entry.message.peers[entry.message.id.peerId] as? TelegramChannel, case .group = peer.info, peer.flags.contains(.isCreator) {
isCreator = true
}
if isEmptyMedia && isCreator {
} else {
isEmpty = false
break loop
}
}
var isCreator = false
if let peer = entry.message.peers[entry.message.id.peerId] as? TelegramGroup, case .creator = peer.role {
isCreator = true
} else if let peer = entry.message.peers[entry.message.id.peerId] as? TelegramChannel, case .group = peer.info, peer.flags.contains(.isCreator) {
isCreator = true
}
if isEmptyMedia && isCreator {
} else {
isEmpty = false
break loop
}
} else {
isEmpty = false
}
if isEmpty {
entries.removeAll()
}
} else {
isEmpty = false
}
if isEmpty {
entries.removeAll()
}
}
} else if includeSearchEntry {

View file

@ -530,12 +530,12 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if let filteredEntries = historyView?.filteredEntries, let visibleRange = displayRange.visibleRange {
let lastEntry = filteredEntries[filteredEntries.count - 1 - visibleRange.lastIndex]
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount), id: 0)
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
} else {
if let messageId = messageId {
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60), id: 0)
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
} else {
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: 60), id: 0)
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Initial(count: 60), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
}
}
}
@ -993,11 +993,11 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
if let loaded = displayedRange.loadedRange, let firstEntry = historyView.filteredEntries.first, let lastEntry = historyView.filteredEntries.last {
if loaded.firstIndex < 5 && historyView.originalView.laterId != nil {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount), id: 0)
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(lastEntry.index), anchorIndex: .message(lastEntry.index), count: historyMessageCount), id: (self.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
} else if loaded.firstIndex < 5, historyView.originalView.laterId == nil, !historyView.originalView.holeLater, let chatHistoryLocationValue = self.chatHistoryLocationValue, !chatHistoryLocationValue.isAtUpperBound, historyView.originalView.anchorIndex != .upperBound {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount), id: 0)
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .upperBound, anchorIndex: .upperBound, count: historyMessageCount), id: (self.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
} else if loaded.lastIndex >= historyView.filteredEntries.count - 5 && historyView.originalView.earlierId != nil {
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount), id: 0)
self.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount), id: (self.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
}
}

View file

@ -58,23 +58,6 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
var displayInputTextPanel = false
/*if case .group = chatPresentationInterfaceState.chatLocation {
if chatPresentationInterfaceState.interfaceState.editMessage != nil {
displayInputTextPanel = true
} else {
if let currentPanel = currentPanel as? ChatFeedNavigationInputPanelNode {
currentPanel.interfaceInteraction = interfaceInteraction
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
return currentPanel
} else {
let panel = ChatFeedNavigationInputPanelNode(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
panel.context = context
panel.interfaceInteraction = interfaceInteraction
return panel
}
}
}*/
if let peer = chatPresentationInterfaceState.renderedPeer?.peer {
if let secretChat = peer as? TelegramSecretChat {
switch secretChat.embeddedState {
@ -100,7 +83,24 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
break
}
} else if let channel = peer as? TelegramChannel {
if channel.hasBannedPermission(.banSendMessages) != nil {
var isMember: Bool = false
switch channel.participationStatus {
case .kicked:
if let currentPanel = currentPanel as? DeleteChatInputPanelNode {
return currentPanel
} else {
let panel = DeleteChatInputPanelNode()
panel.context = context
panel.interfaceInteraction = interfaceInteraction
return panel
}
case .member:
isMember = true
case .left:
break
}
if isMember && channel.hasBannedPermission(.banSendMessages) != nil {
if let currentPanel = currentPanel as? ChatRestrictedInputPanelNode {
return currentPanel
} else {
@ -111,61 +111,48 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
}
}
switch channel.participationStatus {
case .kicked:
if let currentPanel = currentPanel as? DeleteChatInputPanelNode {
switch channel.info {
case .broadcast:
if chatPresentationInterfaceState.interfaceState.editMessage != nil, channel.hasPermission(.editAllMessages) {
displayInputTextPanel = true
} else if !channel.hasPermission(.sendMessages) {
if let currentPanel = currentPanel as? ChatChannelSubscriberInputPanelNode {
return currentPanel
} else {
let panel = DeleteChatInputPanelNode()
panel.context = context
let panel = ChatChannelSubscriberInputPanelNode()
panel.interfaceInteraction = interfaceInteraction
panel.context = context
return panel
}
case .member, .left:
}
case .group:
switch channel.participationStatus {
case .kicked, .left:
if let currentPanel = currentPanel as? ChatChannelSubscriberInputPanelNode {
return currentPanel
} else {
let panel = ChatChannelSubscriberInputPanelNode()
panel.interfaceInteraction = interfaceInteraction
panel.context = context
return panel
}
case .member:
break
}
switch channel.info {
case .broadcast:
if chatPresentationInterfaceState.interfaceState.editMessage != nil, channel.hasPermission(.editAllMessages) {
displayInputTextPanel = true
} else if !channel.hasPermission(.sendMessages) {
if let currentPanel = currentPanel as? ChatChannelSubscriberInputPanelNode {
return currentPanel
} else {
let panel = ChatChannelSubscriberInputPanelNode()
panel.interfaceInteraction = interfaceInteraction
panel.context = context
return panel
}
}
case .group:
switch channel.participationStatus {
case .kicked, .left:
if let currentPanel = currentPanel as? ChatChannelSubscriberInputPanelNode {
return currentPanel
} else {
let panel = ChatChannelSubscriberInputPanelNode()
panel.interfaceInteraction = interfaceInteraction
panel.context = context
return panel
}
case .member:
break
}
}
}
} else if let group = peer as? TelegramGroup {
switch group.membership {
case .Removed, .Left:
if let currentPanel = currentPanel as? DeleteChatInputPanelNode {
return currentPanel
} else {
let panel = DeleteChatInputPanelNode()
panel.context = context
panel.interfaceInteraction = interfaceInteraction
return panel
}
case .Member:
break
case .Removed, .Left:
if let currentPanel = currentPanel as? DeleteChatInputPanelNode {
return currentPanel
} else {
let panel = DeleteChatInputPanelNode()
panel.context = context
panel.interfaceInteraction = interfaceInteraction
return panel
}
case .Member:
break
}
if group.hasBannedPermission(.banSendMessages) {

View file

@ -11,32 +11,6 @@ import TelegramUIPreferences
private enum ChatListRecentEntryStableId: Hashable {
case topPeers
case peerId(PeerId)
static func ==(lhs: ChatListRecentEntryStableId, rhs: ChatListRecentEntryStableId) -> Bool {
switch lhs {
case .topPeers:
if case .topPeers = rhs {
return true
} else {
return false
}
case let .peerId(peerId):
if case .peerId(peerId) = rhs {
return true
} else {
return false
}
}
}
var hashValue: Int {
switch self {
case .topPeers:
return 0
case let .peerId(peerId):
return peerId.hashValue
}
}
}
private enum ChatListRecentEntry: Comparable, Identifiable {
@ -251,7 +225,7 @@ enum ChatListSearchEntryStableId: Hashable {
enum ChatListSearchEntry: Comparable, Identifiable {
case localPeer(Peer, Peer?, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)
case globalPeer(FoundPeer, UnreadSearchBadge?, Int, PresentationTheme, PresentationStrings, PresentationPersonNameOrder, PresentationPersonNameOrder)
case message(Message, CombinedPeerReadState?, ChatListPresentationData)
case message(Message, RenderedPeer, CombinedPeerReadState?, ChatListPresentationData)
case addContact(String, PresentationTheme, PresentationStrings)
var stableId: ChatListSearchEntryStableId {
@ -260,7 +234,7 @@ enum ChatListSearchEntry: Comparable, Identifiable {
return .localPeerId(peer.id)
case let .globalPeer(peer, _, _, _, _, _, _):
return .globalPeerId(peer.peer.id)
case let .message(message, _, _):
case let .message(message, _, _, _):
return .messageId(message.id)
case .addContact:
return .addContact
@ -281,14 +255,17 @@ enum ChatListSearchEntry: Comparable, Identifiable {
} else {
return false
}
case let .message(lhsMessage, lhsCombinedPeerReadState, lhsPresentationData):
if case let .message(rhsMessage, rhsCombinedPeerReadState, rhsPresentationData) = rhs {
case let .message(lhsMessage, lhsPeer, lhsCombinedPeerReadState, lhsPresentationData):
if case let .message(rhsMessage, rhsPeer, rhsCombinedPeerReadState, rhsPresentationData) = rhs {
if lhsMessage.id != rhsMessage.id {
return false
}
if lhsMessage.stableVersion != rhsMessage.stableVersion {
return false
}
if lhsPeer != rhsPeer {
return false
}
if lhsPresentationData !== rhsPresentationData {
return false
}
@ -334,8 +311,8 @@ enum ChatListSearchEntry: Comparable, Identifiable {
case .message, .addContact:
return true
}
case let .message(lhsMessage, _, _):
if case let .message(rhsMessage, _, _) = rhs {
case let .message(lhsMessage, _, _, _):
if case let .message(rhsMessage, _, _, _) = rhs {
return lhsMessage.index < rhsMessage.index
} else if case .addContact = rhs {
return true
@ -448,8 +425,8 @@ enum ChatListSearchEntry: Comparable, Identifiable {
return ContactsPeerItem(theme: theme, strings: strings, sortOrder: nameSortOrder, displayOrder: nameDisplayOrder, account: context.account, peerMode: .generalSearch, peer: .peer(peer: peer.peer, chatPeer: peer.peer), status: .addressName(suffixString), badge: badge, enabled: enabled, selection: .none, editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false), index: nil, header: header, action: { _ in
interaction.peerSelected(peer.peer)
})
case let .message(message, readState, presentationData):
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: RenderedPeer(message: message), combinedReadState: readState, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
case let .message(message, peer, readState, presentationData):
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(message: message, peer: peer, combinedReadState: readState, notificationSettings: nil, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, isAd: false, ignoreUnreadBadge: true), editing: false, hasActiveRevealControls: false, selected: false, header: enableHeaders ? ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil) : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
case let .addContact(phoneNumber, theme, strings):
return ContactsAddItem(theme: theme, strings: strings, phoneNumber: phoneNumber, header: ChatListSearchItemHeader(type: .phoneNumber, theme: theme, strings: strings, actionTitle: nil, action: nil), action: {
interaction.addContact(phoneNumber)
@ -813,7 +790,13 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode {
if !foundRemotePeers.2 {
index = 0
for message in foundRemoteMessages.0.0 {
entries.append(.message(message, foundRemoteMessages.0.1[message.id.peerId], presentationData))
var peer = RenderedPeer(message: message)
if let group = message.peers[message.id.peerId] as? TelegramGroup, let migrationReference = group.migrationReference {
if let channelPeer = message.peers[migrationReference.peerId] {
peer = RenderedPeer(peer: channelPeer)
}
}
entries.append(.message(message, peer, foundRemoteMessages.0.1[message.id.peerId], presentationData))
index += 1
}
}

View file

@ -323,6 +323,16 @@ final class ChatRecordedMediaPreview: Equatable {
struct ChatContactStatus: Equatable {
var canAddContact: Bool
var peerStatusSettings: PeerStatusSettings?
var isEmpty: Bool {
guard var peerStatusSettings = self.peerStatusSettings else {
return false
}
if !self.canAddContact {
peerStatusSettings.remove(.canAddContact)
}
return peerStatusSettings.isEmpty
}
}
final class ChatPresentationInterfaceState: Equatable {

View file

@ -26,7 +26,7 @@ private enum ChatReportPeerTitleButton: Equatable {
case .shareMyPhoneNumber:
return strings.Conversation_ShareMyPhoneNumber
case .reportSpam:
return strings.Conversation_ReportSpam
return strings.Conversation_ReportSpamAndLeave
case .reportIrrelevantGeoLocation:
return strings.Conversation_ReportGroupLocation
}
@ -131,11 +131,12 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
let view = UIButton()
view.setTitle(button.title(strings: interfaceState.strings), for: [])
view.titleLabel?.font = Font.regular(16.0)
if case .block = button {
switch button {
case .block, .reportSpam:
view.setTitleColor(interfaceState.theme.chat.inputPanel.panelControlDestructiveColor, for: [])
view.setTitleColor(interfaceState.theme.chat.inputPanel.panelControlDestructiveColor.withAlphaComponent(0.7), for: [.highlighted])
} else {
view.setTitleColor(interfaceState.theme.rootController.navigationBar.accentTextColor, for: [])
default:
view.setTitleColor(interfaceState.theme.rootController.navigationBar.accentTextColor, for: [])
view.setTitleColor(interfaceState.theme.rootController.navigationBar.accentTextColor.withAlphaComponent(0.7), for: [.highlighted])
}
view.addTarget(self, action: #selector(self.buttonPressed(_:)), for: [.touchUpInside])
@ -154,7 +155,7 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
nextButtonOrigin += buttonWidth
}
} else {
let additionalRightInset: CGFloat = 18.0
let additionalRightInset: CGFloat = 36.0
let areaWidth = width - maxInset * 2.0 - additionalRightInset
let maxButtonWidth = floor(areaWidth / CGFloat(self.buttons.count))
let buttonSizes = self.buttons.map { button -> CGFloat in

View file

@ -202,7 +202,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
let textInputContainer: ASDisplayNode
var textInputNode: EditableTextNode?
let textInputBackgroundView: UIImageView
let textInputBackgroundNode: ASImageNode
let actionButtons: ChatTextInputActionButtonsNode
var mediaRecordingAccessibilityArea: AccessibilityAreaNode?
@ -322,7 +322,9 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.textInputContainer.clipsToBounds = true
self.textInputContainer.backgroundColor = theme.chat.inputPanel.inputBackgroundColor
self.textInputBackgroundView = UIImageView()
self.textInputBackgroundNode = ASImageNode()
self.textInputBackgroundNode.displaysAsynchronously = false
self.textInputBackgroundNode.displayWithoutProcessing = true
self.textPlaceholderNode = ImmediateTextNode()
self.textPlaceholderNode.maximumNumberOfLines = 1
self.textPlaceholderNode.isUserInteractionEnabled = false
@ -407,21 +409,21 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
self.searchLayoutClearButton.addSubview(self.searchLayoutProgressView)
self.addSubnode(self.textInputContainer)
self.view.addSubview(self.textInputBackgroundView)
self.addSubnode(self.textInputBackgroundNode)
self.addSubnode(self.textPlaceholderNode)
self.view.addSubview(self.searchLayoutClearButton)
self.textInputBackgroundView.clipsToBounds = true
self.textInputBackgroundNode.clipsToBounds = true
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
recognizer.touchDown = { [weak self] in
if let strongSelf = self {
strongSelf.ensureFocused()
}
}
self.textInputBackgroundView.addGestureRecognizer(recognizer)
self.textInputBackgroundView.isUserInteractionEnabled = true
self.textInputBackgroundNode.isUserInteractionEnabled = true
self.textInputBackgroundNode.view.addGestureRecognizer(recognizer)
}
required init?(coder aDecoder: NSCoder) {
@ -490,8 +492,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
textInputNode.frame = CGRect(origin: CGPoint(x: self.textInputViewInternalInsets.left, y: self.textInputViewInternalInsets.top), size: CGSize(width: textInputFrame.size.width - (self.textInputViewInternalInsets.left + self.textInputViewInternalInsets.right + accessoryButtonsWidth), height: textInputFrame.size.height - self.textInputViewInternalInsets.top - self.textInputViewInternalInsets.bottom))
}
self.textInputBackgroundView.isUserInteractionEnabled = false
self.textInputBackgroundView.removeGestureRecognizer(self.textInputBackgroundView.gestureRecognizers![0])
self.textInputBackgroundNode.isUserInteractionEnabled = false
self.textInputBackgroundNode.view.removeGestureRecognizer(self.textInputBackgroundNode.view.gestureRecognizers![0])
let recognizer = TouchDownGestureRecognizer(target: self, action: #selector(self.textInputBackgroundViewTap(_:)))
recognizer.touchDown = { [weak self] in
@ -647,7 +649,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, metrics: metrics)
let minimalInputHeight: CGFloat = 2.0 + textFieldMinHeight
self.textInputBackgroundView.image = textInputBackgroundImage(backgroundColor: interfaceState.theme.chat.inputPanel.panelBackgroundColor, strokeColor: interfaceState.theme.chat.inputPanel.inputStrokeColor, diameter: minimalInputHeight)
self.textInputBackgroundNode.image = textInputBackgroundImage(backgroundColor: interfaceState.theme.chat.inputPanel.panelBackgroundColor, strokeColor: interfaceState.theme.chat.inputPanel.inputStrokeColor, diameter: minimalInputHeight)
self.searchLayoutClearButton.setImage(PresentationResourcesChat.chatInputTextFieldClearImage(interfaceState.theme), for: [])
@ -799,7 +801,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
var audioRecordingItemsVerticalOffset: CGFloat = 0.0
if let mediaRecordingState = interfaceState.inputTextPanelState.mediaRecordingState {
audioRecordingItemsVerticalOffset = panelHeight * 2.0
transition.updateAlpha(layer: self.textInputBackgroundView.layer, alpha: 0.0)
transition.updateAlpha(layer: self.textInputBackgroundNode.layer, alpha: 0.0)
if let textInputNode = self.textInputNode {
transition.updateAlpha(node: textInputNode, alpha: 0.0)
}
@ -908,7 +910,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
} else {
self.actionButtons.micButton.audioRecorder = nil
self.actionButtons.micButton.videoRecordingStatus = nil
transition.updateAlpha(layer: self.textInputBackgroundView.layer, alpha: 1.0)
transition.updateAlpha(layer: self.textInputBackgroundNode.layer, alpha: 1.0)
if let textInputNode = self.textInputNode {
transition.updateAlpha(node: textInputNode, alpha: 1.0)
}
@ -1056,7 +1058,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
transition.updateFrame(node: self.textPlaceholderNode, frame: CGRect(origin: CGPoint(x: leftInset + textFieldInsets.left + self.textInputViewInternalInsets.left, y: textFieldInsets.top + self.textInputViewInternalInsets.top + self.textInputViewRealInsets.top + audioRecordingItemsVerticalOffset + UIScreenPixel), size: self.textPlaceholderNode.frame.size))
transition.updateFrame(layer: self.textInputBackgroundView.layer, frame: CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top + audioRecordingItemsVerticalOffset, width: baseWidth - textFieldInsets.left - textFieldInsets.right + textInputBackgroundWidthOffset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom))
transition.updateFrame(layer: self.textInputBackgroundNode.layer, frame: CGRect(x: leftInset + textFieldInsets.left, y: textFieldInsets.top + audioRecordingItemsVerticalOffset, width: baseWidth - textFieldInsets.left - textFieldInsets.right + textInputBackgroundWidthOffset, height: panelHeight - textFieldInsets.top - textFieldInsets.bottom))
var nextButtonTopRight = CGPoint(x: width - rightInset - textFieldInsets.right - accessoryButtonInset, y: panelHeight - textFieldInsets.bottom - minimalInputHeight + audioRecordingItemsVerticalOffset)
for (_, button) in self.accessoryItemButtons.reversed() {

View file

@ -180,7 +180,7 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
if peerView.peerId == self.account.peerId {
string = NSAttributedString(string: self.strings.Conversation_SavedMessages, font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
} else {
if !peerView.peerIsContact, let user = peer as? TelegramUser, let phone = user.phone, !phone.isEmpty {
if !peerView.peerIsContact, let user = peer as? TelegramUser, !user.flags.contains(.isSupport), user.botInfo == nil, let phone = user.phone, !phone.isEmpty {
string = NSAttributedString(string: formatPhoneNumber(phone), font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)
} else {
string = NSAttributedString(string: peer.displayTitle(strings: self.strings, displayOrder: self.nameDisplayOrder), font: Font.medium(17.0), textColor: self.theme.rootController.navigationBar.primaryTextColor)

View file

@ -1009,7 +1009,7 @@ final class ContactListNode: ASDisplayNode {
|> map { ($0.0, $0.1) }
|> delay(0.2, queue: Queue.concurrentDefaultQueue())
)
let foundDeviceContacts: Signal<[DeviceContactStableId: DeviceContactBasicData], NoError>
let foundDeviceContacts: Signal<[DeviceContactStableId: (DeviceContactBasicData, PeerId?)], NoError>
if searchDeviceContacts {
foundDeviceContacts = context.sharedContext.contactDataManager?.search(query: query) ?? .single([:])
} else {
@ -1106,13 +1106,18 @@ final class ContactListNode: ASDisplayNode {
}
outer: for (stableId, contact) in deviceContacts {
inner: for phoneNumber in contact.phoneNumbers {
inner: for phoneNumber in contact.0.phoneNumbers {
let normalizedNumber = DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phoneNumber.value))
if existingNormalizedPhoneNumbers.contains(normalizedNumber) {
continue outer
}
}
peers.append(.deviceContact(stableId, contact))
if let peerId = contact.1 {
if existingPeerIds.contains(peerId) {
continue outer
}
}
peers.append(.deviceContact(stableId, contact.0))
}
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: themeAndStrings.0, strings: themeAndStrings.1, dateTimeFormat: themeAndStrings.2, sortOrder: themeAndStrings.3, displayOrder: themeAndStrings.4, disabledPeerIds: disabledPeerIds, authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false)

View file

@ -188,7 +188,7 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
foundRemoteContacts = .single(([], []))
}
let searchDeviceContacts = categories.contains(.deviceContacts)
let foundDeviceContacts: Signal<[DeviceContactStableId: DeviceContactBasicData]?, NoError>
let foundDeviceContacts: Signal<[DeviceContactStableId: (DeviceContactBasicData, PeerId?)]?, NoError>
if searchDeviceContacts, let contactDataManager = context.sharedContext.contactDataManager {
foundDeviceContacts = contactDataManager.search(query: query)
|> map(Optional.init)
@ -271,13 +271,18 @@ final class ContactsSearchContainerNode: SearchDisplayControllerContentNode {
}
if let _ = remotePeers, let deviceContacts = deviceContacts {
outer: for (stableId, contact) in deviceContacts {
inner: for phoneNumber in contact.phoneNumbers {
inner: for phoneNumber in contact.0.phoneNumbers {
let normalizedNumber = DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phoneNumber.value))
if existingNormalizedPhoneNumbers.contains(normalizedNumber) {
continue outer
}
}
entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .deviceContact(stableId, contact), presence: nil, group: .deviceContacts, enabled: true))
if let peerId = contact.1 {
if existingPeerIds.contains(peerId) {
continue outer
}
}
entries.append(ContactListSearchEntry(index: index, theme: themeAndStrings.0, strings: themeAndStrings.1, peer: .deviceContact(stableId, contact.0), presence: nil, group: .deviceContacts, enabled: true))
index += 1
}
}

View file

@ -485,6 +485,7 @@ private final class DeviceContactDataManagerImpl {
private var stableIdToBasicContactData: [DeviceContactStableId: DeviceContactBasicData] = [:]
private var normalizedPhoneNumberToStableId: [DeviceContactNormalizedPhoneNumber: [DeviceContactStableId]] = [:]
private var appSpecificReferences: [PeerId: DeviceContactBasicDataWithReference] = [:]
private var stableIdToAppSpecificReference: [DeviceContactStableId: PeerId] = [:]
private var importableContacts: [DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData] = [:]
@ -616,6 +617,11 @@ private final class DeviceContactDataManagerImpl {
private func updateAppSpecificReferences(appSpecificReferences: [PeerId: DeviceContactBasicDataWithReference]) {
self.appSpecificReferences = appSpecificReferences
var stableIdToAppSpecificReference: [DeviceContactStableId: PeerId] = [:]
for (peerId, value) in appSpecificReferences {
stableIdToAppSpecificReference[value.stableId] = peerId
}
self.stableIdToAppSpecificReference = stableIdToAppSpecificReference
for f in self.appSpecificReferencesSubscribers.copyItems() {
f(appSpecificReferences)
}
@ -721,12 +727,12 @@ private final class DeviceContactDataManagerImpl {
}
}
func search(query: String, updated: @escaping ([DeviceContactStableId: DeviceContactBasicData]) -> Void) -> Disposable {
func search(query: String, updated: @escaping ([DeviceContactStableId: (DeviceContactBasicData, PeerId?)]) -> Void) -> Disposable {
let normalizedQuery = query.lowercased()
var result: [DeviceContactStableId: DeviceContactBasicData] = [:]
var result: [DeviceContactStableId: (DeviceContactBasicData, PeerId?)] = [:]
for (stableId, basicData) in self.stableIdToBasicContactData {
if basicData.firstName.lowercased().hasPrefix(normalizedQuery) || basicData.lastName.lowercased().hasPrefix(normalizedQuery) {
result[stableId] = basicData
result[stableId] = (basicData, self.stableIdToAppSpecificReference[stableId])
}
}
updated(result)
@ -837,7 +843,7 @@ public final class DeviceContactDataManager {
}
}
public func search(query: String) -> Signal<[DeviceContactStableId: DeviceContactBasicData], NoError> {
public func search(query: String) -> Signal<[DeviceContactStableId: (DeviceContactBasicData, PeerId?)], NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with({ impl in

View file

@ -414,9 +414,9 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry {
arguments.setPhoneIdWithRevealedOptions(lhs, rhs)
}, updated: { value in
arguments.updatePhone(id, value)
}, selectLabel: {
}, selectLabel: nil /*{
arguments.updatePhoneLabel(id, label)
}, delete: {
}*/, delete: {
arguments.deletePhone(id)
}, tag: DeviceContactInfoEntryTag.editingPhone(id))
case let .addPhoneNumber(_, theme, title):

View file

@ -41,7 +41,7 @@ final class HashtagSearchController: TelegramController {
let search = searchMessages(account: context.account, location: location, query: query, state: nil)
let foundMessages: Signal<[ChatListSearchEntry], NoError> = search
|> map { result, _ in
return result.messages.map({ .message($0, result.readStates[$0.id.peerId], chatListPresentationData) })
return result.messages.map({ .message($0, RenderedPeer(message: $0), result.readStates[$0.id.peerId], chatListPresentationData) })
}
let interaction = ChatListNodeInteraction(activateSearch: {
}, peerSelected: { peer in

View file

@ -351,20 +351,22 @@ final class InviteContactsControllerNode: ASDisplayNode {
self?.requestShareTelegram?()
})
let existingNumbers: Signal<Set<String>, NoError> = account.postbox.contactPeersView(accountPeerId: nil, includePresences: false)
|> map { view -> Set<String> in
let existingNumbers: Signal<(Set<String>, Set<PeerId>), NoError> = account.postbox.contactPeersView(accountPeerId: nil, includePresences: false)
|> map { view -> (Set<String>, Set<PeerId>) in
var existingNumbers = Set<String>()
var existingPeerIds = Set<PeerId>()
for peer in view.peers {
if let peer = peer as? TelegramUser, let phone = peer.phone {
existingNumbers.insert(formatPhoneNumber(phone))
}
existingPeerIds.insert(peer.id)
}
return existingNumbers
return (existingNumbers, existingPeerIds)
}
let currentSortedContacts = self.currentSortedContacts
let sortedContacts: Signal<[(DeviceContactStableId, DeviceContactBasicData, Int32)], NoError> = combineLatest(existingNumbers, (context.sharedContext.contactDataManager?.basicData() ?? .single([:])) |> take(1))
|> mapToSignal { existingNumbers, contacts -> Signal<[(DeviceContactStableId, DeviceContactBasicData, Int32)], NoError> in
|> mapToSignal { existingNumbersAndPeerIds, contacts -> Signal<[(DeviceContactStableId, DeviceContactBasicData, Int32)], NoError> in
var mappedContacts: [(String, [DeviceContactNormalizedPhoneNumber])] = []
for (id, basicData) in contacts {
mappedContacts.append((id: id, basicData.phoneNumbers.map({ phoneNumber in
@ -378,7 +380,7 @@ final class InviteContactsControllerNode: ASDisplayNode {
for (id, basicData) in contacts {
var found = false
for number in basicData.phoneNumbers {
if existingNumbers.contains(formatPhoneNumber(number.value)) {
if existingNumbersAndPeerIds.0.contains(formatPhoneNumber(number.value)) {
found = true
}
}

View file

@ -23,7 +23,7 @@ func loadLegacyUser(database: SqliteInterface, id: Int32) -> (TelegramUser, Tele
photo.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: 600.0, height: 600.0), resource: resource))
}
let user = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: cursor.getInt32(at: 0)), accessHash: accessHash == 0 ? nil : accessHash, firstName: firstName.isEmpty ? nil : firstName, lastName: lastName.isEmpty ? nil : lastName, username: username.isEmpty ? nil : username, phone: phone.isEmpty ? nil : phone, photo: photo, botInfo: nil, restrictionInfo: nil, flags: [])
let user = TelegramUser(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: cursor.getInt32(at: 0)), accessHash: accessHash == 0 ? nil : .personal(accessHash), firstName: firstName.isEmpty ? nil : firstName, lastName: lastName.isEmpty ? nil : lastName, username: username.isEmpty ? nil : username, phone: phone.isEmpty ? nil : phone, photo: photo, botInfo: nil, restrictionInfo: nil, flags: [])
let status: UserPresenceStatus
let lastSeen = cursor.getInt32(at: 7)

View file

@ -10,11 +10,14 @@ public enum NavigateToChatKeepStack {
case never
}
public func navigateToChatController(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, animated: Bool = true, parentGroupId: PeerGroupId? = nil, completion: @escaping () -> Void = {}) {
public func navigateToChatController(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, animated: Bool = true, parentGroupId: PeerGroupId? = nil, completion: @escaping () -> Void = {}) {
var found = false
var isFirst = true
for controller in navigationController.viewControllers.reversed() {
if let controller = controller as? ChatController, controller.chatLocation == chatLocation {
if let updateTextInputState = updateTextInputState {
controller.updateTextInputState(updateTextInputState)
}
if let messageId = messageId {
controller.navigateToMessage(messageLocation: .id(messageId), animated: isFirst, completion: { [weak navigationController, weak controller] in
if let navigationController = navigationController, let controller = controller {

View file

@ -883,6 +883,7 @@ final class NotificationExceptionsControllerNode: ViewControllerTracingNode {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationTheme: presentationData.theme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: presentationData.strings.Notification_Exceptions_DeleteAllConfirmation),
ActionSheetButtonItem(title: presentationData.strings.Notification_Exceptions_DeleteAll, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()

View file

@ -219,27 +219,36 @@ func playSound(context: AccountContext, sound: PeerMessageSound, defaultSound: P
var currentPlayer: AudioPlayerWrapper?
var deactivateImpl: (() -> Void)?
let session = context.sharedContext.mediaManager.audioSession.push(audioSessionType: .play, activate: { _ in
if let url = Bundle.main.url(forResource: fileNameForNotificationSound(sound, defaultSound: defaultSound), withExtension: "m4a") {
currentPlayer = AudioPlayerWrapper(url: url, completed: {
deactivateImpl?()
})
currentPlayer?.play()
Queue.mainQueue().async {
if let url = Bundle.main.url(forResource: fileNameForNotificationSound(sound, defaultSound: defaultSound), withExtension: "m4a") {
currentPlayer = AudioPlayerWrapper(url: url, completed: {
deactivateImpl?()
})
currentPlayer?.play()
}
}
}, deactivate: {
currentPlayer?.stop()
currentPlayer = nil
return .complete()
return Signal { subscriber in
Queue.mainQueue().async {
currentPlayer?.stop()
currentPlayer = nil
subscriber.putCompletion()
}
return EmptyDisposable
}
})
deactivateImpl = {
session.dispose()
}
return ActionDisposable {
session.dispose()
currentPlayer?.stop()
currentPlayer = nil
Queue.mainQueue().async {
currentPlayer?.stop()
currentPlayer = nil
}
}
} |> runOn(Queue.mainQueue())
}
|> runOn(Queue.mainQueue())
}
}

View file

@ -1002,7 +1002,7 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions
let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings])
let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications])
let exceptionsSignal = Signal<NotificationExceptionsList?, NoError>.single(exceptionsList) |> then(notificationExceptionsList(network: context.account.network) |> map(Optional.init))
let exceptionsSignal = Signal<NotificationExceptionsList?, NoError>.single(exceptionsList) |> then(notificationExceptionsList(postbox: context.account.postbox, network: context.account.network) |> map(Optional.init))
notificationExceptions.set(exceptionsSignal |> map { list -> (NotificationExceptionMode, NotificationExceptionMode, NotificationExceptionMode) in
var users:[PeerId : NotificationExceptionWrapper] = [:]

View file

@ -1074,7 +1074,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|> mapToSignal { context -> Signal<NotificationExceptionsList?, NoError> in
return .single(NotificationExceptionsList(peers: [:], settings: [:]))
|> then(
notificationExceptionsList(network: context.account.network)
notificationExceptionsList(postbox: context.account.postbox, network: context.account.network)
|> map(Optional.init)
)
}

View file

@ -102,7 +102,7 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode {
strongSelf.leftActionTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
self.leftActionNode.highligthedChanged = { [weak self] highlighted in
self.rightActionNode.highligthedChanged = { [weak self] highlighted in
guard let strongSelf = self else {
return
}

View file

@ -773,6 +773,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us
var botAddToGroupImpl: (() -> Void)?
var shareBotImpl: (() -> Void)?
var dismissInputImpl: (() -> Void)?
var dismissImpl: (() -> Void)?
let actionsDisposable = DisposableSet()
@ -1066,7 +1067,9 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us
}
updatePeerBlockedDisposable.set((deleteSignal
|> deliverOnMainQueue).start())
|> deliverOnMainQueue).start(completed: {
dismissImpl?()
}))
})
})
]),
@ -1288,6 +1291,12 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Us
pushControllerImpl = { [weak controller] value in
(controller?.navigationController as? NavigationController)?.pushViewController(value)
}
dismissImpl = { [weak controller] in
guard let controller = controller else {
return
}
(controller.navigationController as? NavigationController)?.filterController(controller, animated: true)
}
presentControllerImpl = { [weak controller] value, presentationArguments in
controller?.present(value, in: .window(.root), with: presentationArguments, blockInteraction: true)
}

View file

@ -20,11 +20,11 @@ class UserInfoEditingPhoneItem: ListViewItem, ItemListItem {
let sectionId: ItemListSectionId
let setPhoneIdWithRevealedOptions: (Int64?, Int64?) -> Void
let updated: (String) -> Void
let selectLabel: () -> Void
let selectLabel: (() -> Void)?
let delete: () -> Void
let tag: ItemListItemTag?
init(theme: PresentationTheme, strings: PresentationStrings, id: Int64, label: String, value: String, editing: UserInfoEditingPhoneItemEditing, sectionId: ItemListSectionId, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, updated: @escaping (String) -> Void, selectLabel: @escaping () -> Void, delete: @escaping () -> Void, tag: ItemListItemTag?) {
init(theme: PresentationTheme, strings: PresentationStrings, id: Int64, label: String, value: String, editing: UserInfoEditingPhoneItemEditing, sectionId: ItemListSectionId, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, updated: @escaping (String) -> Void, selectLabel: (() -> Void)?, delete: @escaping () -> Void, tag: ItemListItemTag?) {
self.theme = theme
self.strings = strings
self.id = id
@ -237,6 +237,7 @@ class UserInfoEditingPhoneItemNode: ItemListRevealOptionsItemNode, ItemListItemN
let labelFrame = CGRect(origin: CGPoint(x: revealOffset + leftInset + 30.0, y: 12.0), size: labelLayout.size)
strongSelf.labelNode.frame = labelFrame
strongSelf.labelButtonNode.frame = labelFrame
strongSelf.labelButtonNode.isUserInteractionEnabled = item.selectLabel != nil
strongSelf.labelSeparatorNode.frame = CGRect(origin: CGPoint(x: labelFrame.maxX + 8.0, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.contentSize.height))
let phoneX = labelFrame.maxX + 16.0
@ -293,7 +294,7 @@ class UserInfoEditingPhoneItemNode: ItemListRevealOptionsItemNode, ItemListItemN
}
@objc func labelPressed() {
self.item?.selectLabel()
self.item?.selectLabel?()
}
func focus() {

View file

@ -317,6 +317,7 @@
D02D60B1206C189900FEFE1E /* SecureIdPlaintextFormController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60B0206C189900FEFE1E /* SecureIdPlaintextFormController.swift */; };
D02D60B3206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60B2206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift */; };
D02D60C8206E705D00FEFE1E /* SecureIdValueFormPhoneItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02D60C7206E705D00FEFE1E /* SecureIdValueFormPhoneItem.swift */; };
D02D634A22B85B94006BE519 /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D02D634922B85B94006BE519 /* PushKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
D02F4AE91FCF370B004DFBAE /* ChatMessageInteractiveMediaBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02F4AE81FCF370B004DFBAE /* ChatMessageInteractiveMediaBadge.swift */; };
D02F4AF01FD4C46D004DFBAE /* SystemVideoContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02F4AEF1FD4C46D004DFBAE /* SystemVideoContent.swift */; };
D035734B22B5CCCA00F0920D /* LegacyBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D035734A22B5CCCA00F0920D /* LegacyBuffer.swift */; };
@ -332,6 +333,8 @@
D03AA4DF202DBF6F0056C405 /* ChatContextResultPeekContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4DE202DBF6F0056C405 /* ChatContextResultPeekContentNode.swift */; };
D03AA4E5202DF8840056C405 /* StickerPreviewPeekContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4E4202DF8840056C405 /* StickerPreviewPeekContent.swift */; };
D03AA4E7202DFB160056C405 /* ItemListEditableReorderControlNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4E6202DFB160056C405 /* ItemListEditableReorderControlNode.swift */; };
D03AE67322B9459C0078411C /* HockeySDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03AE67222B9459C0078411C /* HockeySDK.framework */; };
D03AE67522B945D30078411C /* BuildConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D03AE67422B945D30078411C /* BuildConfig.framework */; };
D04203152037162700490EA5 /* MediaInputPaneTrendingItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04203142037162700490EA5 /* MediaInputPaneTrendingItem.swift */; };
D04281ED200E3B28009DDE36 /* ItemListControllerSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04281EC200E3B28009DDE36 /* ItemListControllerSearch.swift */; };
D04281EF200E3D88009DDE36 /* GroupInfoSearchItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04281EE200E3D88009DDE36 /* GroupInfoSearchItem.swift */; };
@ -1574,6 +1577,7 @@
D02D60B0206C189900FEFE1E /* SecureIdPlaintextFormController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPlaintextFormController.swift; sourceTree = "<group>"; };
D02D60B2206C18A600FEFE1E /* SecureIdPlaintextFormControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdPlaintextFormControllerNode.swift; sourceTree = "<group>"; };
D02D60C7206E705D00FEFE1E /* SecureIdValueFormPhoneItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureIdValueFormPhoneItem.swift; sourceTree = "<group>"; };
D02D634922B85B94006BE519 /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = System/Library/Frameworks/PushKit.framework; sourceTree = SDKROOT; };
D02DADBE2138D76F00116225 /* Vision.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Vision.framework; path = System/Library/Frameworks/Vision.framework; sourceTree = SDKROOT; };
D02F4AE81FCF370B004DFBAE /* ChatMessageInteractiveMediaBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMessageInteractiveMediaBadge.swift; sourceTree = "<group>"; };
D02F4AEF1FD4C46D004DFBAE /* SystemVideoContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemVideoContent.swift; sourceTree = "<group>"; };
@ -1599,6 +1603,8 @@
D03ADB4A1D70443F005A521C /* ReplyAccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyAccessoryPanelNode.swift; sourceTree = "<group>"; };
D03ADB4C1D7045C9005A521C /* ChatInterfaceStateAccessoryPanels.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatInterfaceStateAccessoryPanels.swift; sourceTree = "<group>"; };
D03ADB4E1D70546B005A521C /* AccessoryPanelNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessoryPanelNode.swift; sourceTree = "<group>"; };
D03AE67222B9459C0078411C /* HockeySDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = HockeySDK.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03AE67422B945D30078411C /* BuildConfig.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = BuildConfig.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D03E5E081E55C49C0029569A /* DebugAccountsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugAccountsController.swift; sourceTree = "<group>"; };
D03E5E0E1E55F8B90029569A /* ChannelVisibilityController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelVisibilityController.swift; sourceTree = "<group>"; };
D04203142037162700490EA5 /* MediaInputPaneTrendingItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaInputPaneTrendingItem.swift; sourceTree = "<group>"; };
@ -2383,6 +2389,9 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
D03AE67522B945D30078411C /* BuildConfig.framework in Frameworks */,
D03AE67322B9459C0078411C /* HockeySDK.framework in Frameworks */,
D02D634A22B85B94006BE519 /* PushKit.framework in Frameworks */,
D00818CD22B595CB008A895F /* LightweightAccountData.framework in Frameworks */,
D008184A22B57225008A895F /* WatchCommon.framework in Frameworks */,
D0750C7A22B2A14300BE5F6E /* DeviceAccess.framework in Frameworks */,
@ -3489,6 +3498,9 @@
D08D45281D5E340200A7428A /* Frameworks */ = {
isa = PBXGroup;
children = (
D03AE67422B945D30078411C /* BuildConfig.framework */,
D03AE67222B9459C0078411C /* HockeySDK.framework */,
D02D634922B85B94006BE519 /* PushKit.framework */,
D00818CC22B595CB008A895F /* LightweightAccountData.framework */,
D008184922B57225008A895F /* WatchCommon.framework */,
D0750C7922B2A14300BE5F6E /* DeviceAccess.framework */,

View file

@ -733,11 +733,12 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@ -764,12 +765,14 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "-DTARGET_OS_WATCH=1";
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.WatchCommonWatch;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
@ -778,7 +781,7 @@
TARGETED_DEVICE_FAMILY = 4;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 5.1;
WATCHOS_DEPLOYMENT_TARGET = 2.2;
};
name = DebugAppStoreLLC;
};
@ -816,11 +819,12 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@ -841,11 +845,13 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CFLAGS = "-DTARGET_OS_WATCH=1";
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.WatchCommonWatch;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
@ -855,7 +861,7 @@
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 5.1;
WATCHOS_DEPLOYMENT_TARGET = 2.2;
};
name = ReleaseAppStoreLLC;
};
@ -984,11 +990,12 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@ -1015,12 +1022,14 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = "-DTARGET_OS_WATCH=1";
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.WatchCommonWatch;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
@ -1029,7 +1038,7 @@
TARGETED_DEVICE_FAMILY = 4;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 5.1;
WATCHOS_DEPLOYMENT_TARGET = 2.2;
};
name = DebugHockeyapp;
};
@ -1152,11 +1161,12 @@
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
@ -1177,11 +1187,13 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MACH_O_TYPE = staticlib;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CFLAGS = "-DTARGET_OS_WATCH=1";
PRODUCT_BUNDLE_IDENTIFIER = org.telegram.WatchCommonWatch;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = watchos;
SKIP_INSTALL = YES;
SWIFT_COMPILATION_MODE = wholemodule;
@ -1191,7 +1203,7 @@
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
WATCHOS_DEPLOYMENT_TARGET = 5.1;
WATCHOS_DEPLOYMENT_TARGET = 2.2;
};
name = ReleaseHockeyappInternal;
};

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>Lottie.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
</dict>
<key>Lottie_iOS.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>44</integer>
</dict>
</dict>
</dict>
</plist>