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

This commit is contained in:
Ilya Laktyushin 2020-10-20 04:02:08 +04:00
commit 047a0085c7
8 changed files with 347 additions and 101 deletions

View File

@ -13,10 +13,10 @@ private var accountCache: Account?
private var installedSharedLogger = false private var installedSharedLogger = false
private func setupSharedLogger(_ path: String) { private func setupSharedLogger(rootPath: String, path: String) {
if !installedSharedLogger { if !installedSharedLogger {
installedSharedLogger = true installedSharedLogger = true
Logger.setSharedLogger(Logger(basePath: path)) Logger.setSharedLogger(Logger(rootPath: rootPath, basePath: path))
} }
} }
@ -92,7 +92,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
let logsPath = rootPath + "/siri-logs" let logsPath = rootPath + "/siri-logs"
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil) let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
setupSharedLogger(logsPath) setupSharedLogger(rootPath: rootPath, path: logsPath)
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"

View File

@ -51,6 +51,7 @@ private enum DebugControllerSection: Int32 {
private enum DebugControllerEntry: ItemListNodeEntry { private enum DebugControllerEntry: ItemListNodeEntry {
case sendLogs(PresentationTheme) case sendLogs(PresentationTheme)
case sendOneLog(PresentationTheme) case sendOneLog(PresentationTheme)
case sendShareLogs
case sendNotificationLogs(PresentationTheme) case sendNotificationLogs(PresentationTheme)
case sendCriticalLogs(PresentationTheme) case sendCriticalLogs(PresentationTheme)
case accounts(PresentationTheme) case accounts(PresentationTheme)
@ -73,6 +74,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case photoPreview(PresentationTheme, Bool) case photoPreview(PresentationTheme, Bool)
case knockoutWallpaper(PresentationTheme, Bool) case knockoutWallpaper(PresentationTheme, Bool)
case alternativeFolderTabs(Bool) case alternativeFolderTabs(Bool)
case snapPinListToTop(Bool)
case playerEmbedding(Bool) case playerEmbedding(Bool)
case playlistPlayback(Bool) case playlistPlayback(Bool)
case voiceConference case voiceConference
@ -84,7 +86,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
var section: ItemListSectionId { var section: ItemListSectionId {
switch self { switch self {
case .sendLogs, .sendOneLog, .sendNotificationLogs, .sendCriticalLogs: case .sendLogs, .sendOneLog, .sendShareLogs, .sendNotificationLogs, .sendCriticalLogs:
return DebugControllerSection.logs.rawValue return DebugControllerSection.logs.rawValue
case .accounts: case .accounts:
return DebugControllerSection.logs.rawValue return DebugControllerSection.logs.rawValue
@ -92,7 +94,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.logging.rawValue return DebugControllerSection.logging.rawValue
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .playerEmbedding, .playlistPlayback, .voiceConference: case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .snapPinListToTop, .playerEmbedding, .playlistPlayback, .voiceConference:
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .preferredVideoCodec: case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue return DebugControllerSection.videoExperiments.rawValue
@ -109,58 +111,62 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 0 return 0
case .sendOneLog: case .sendOneLog:
return 1 return 1
case .sendNotificationLogs: case .sendShareLogs:
return 2 return 2
case .sendCriticalLogs: case .sendNotificationLogs:
return 3 return 3
case .accounts: case .sendCriticalLogs:
return 4 return 4
case .logToFile: case .accounts:
return 5 return 5
case .logToConsole: case .logToFile:
return 6 return 6
case .redactSensitiveData: case .logToConsole:
return 7 return 7
case .enableRaiseToSpeak: case .redactSensitiveData:
return 8 return 8
case .keepChatNavigationStack: case .enableRaiseToSpeak:
return 9 return 9
case .skipReadHistory: case .keepChatNavigationStack:
return 10 return 10
case .crashOnSlowQueries: case .skipReadHistory:
return 11 return 11
case .clearTips: case .crashOnSlowQueries:
return 12 return 12
case .reimport: case .clearTips:
return 13 return 13
case .resetData: case .reimport:
return 14 return 14
case .resetDatabase: case .resetData:
return 15 return 15
case .resetDatabaseAndCache: case .resetDatabase:
return 16 return 16
case .resetHoles: case .resetDatabaseAndCache:
return 17 return 17
case .reindexUnread: case .resetHoles:
return 18 return 18
case .resetBiometricsData: case .reindexUnread:
return 19 return 19
case .optimizeDatabase: case .resetBiometricsData:
return 20 return 20
case .photoPreview: case .optimizeDatabase:
return 21 return 21
case .knockoutWallpaper: case .photoPreview:
return 22 return 22
case .alternativeFolderTabs: case .knockoutWallpaper:
return 23 return 23
case .playerEmbedding: case .alternativeFolderTabs:
return 24 return 24
case .playlistPlayback: case .snapPinListToTop:
return 25 return 25
case .voiceConference: case .playerEmbedding:
return 26 return 26
case .playlistPlayback:
return 27
case .voiceConference:
return 28
case let .preferredVideoCodec(index, _, _, _): case let .preferredVideoCodec(index, _, _, _):
return 27 + index return 29 + index
case .disableVideoAspectScaling: case .disableVideoAspectScaling:
return 100 return 100
case .enableVoipTcp: case .enableVoipTcp:
@ -327,9 +333,77 @@ private enum DebugControllerEntry: ItemListNodeEntry {
arguments.presentController(actionSheet, nil) arguments.presentController(actionSheet, nil)
}) })
}) })
case let .sendShareLogs:
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Share Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: {
let _ = (Logger.shared.collectLogs(prefix: "/share-logs")
|> deliverOnMainQueue).start(next: { logs in
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
let actionSheet = ActionSheetController(presentationData: presentationData)
var items: [ActionSheetButtonItem] = []
if let context = arguments.context {
items.append(ActionSheetButtonItem(title: "Via Telegram", color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled]))
controller.peerSelected = { [weak controller] peerId in
if let strongController = controller {
strongController.dismiss()
let lineFeed = "\n".data(using: .utf8)!
var logData: Data = Data()
for (name, path) in logs {
if !logData.isEmpty {
logData.append(lineFeed)
logData.append(lineFeed)
}
logData.append("------ File: \(name) ------\n".data(using: .utf8)!)
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) {
logData.append(data)
}
}
let id = arc4random64()
let fileResource = LocalFileMediaResource(fileId: id, size: logData.count, isSecretRelated: false)
context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: logData)
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: logData.count, attributes: [.FileName(fileName: "Log-iOS-Full.txt")])
let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start()
}
}
arguments.pushController(controller)
}))
}
items.append(ActionSheetButtonItem(title: "Via Email", color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
let composeController = MFMailComposeViewController()
composeController.mailComposeDelegate = arguments.mailComposeDelegate
composeController.setSubject("Telegram Logs")
for (name, path) in logs {
if let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe) {
composeController.addAttachmentData(data, mimeType: "application/text", fileName: name)
}
}
arguments.getRootController()?.present(composeController, animated: true, completion: nil)
}))
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])])
arguments.presentController(actionSheet, nil)
})
})
case let .sendNotificationLogs(theme): case let .sendNotificationLogs(theme):
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Notification Logs", label: "", sectionId: self.section, style: .blocks, action: { return ItemListDisclosureItem(presentationData: presentationData, title: "Send Notification Logs", label: "", sectionId: self.section, style: .blocks, action: {
let _ = (Logger(basePath: arguments.sharedContext.basePath + "/notificationServiceLogs").collectLogs() let _ = (Logger(rootPath: arguments.sharedContext.basePath, basePath: arguments.sharedContext.basePath + "/notificationServiceLogs").collectLogs()
|> deliverOnMainQueue).start(next: { logs in |> deliverOnMainQueue).start(next: { logs in
guard let context = arguments.context else { guard let context = arguments.context else {
return return
@ -632,6 +706,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).start()
}) })
case let .snapPinListToTop(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Pin List Top Edge", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
settings.snapPinListToTop = value
return settings
})
}).start()
})
case let .playerEmbedding(value): case let .playerEmbedding(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in return ItemListSwitchItem(presentationData: presentationData, title: "Player Embedding", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
@ -708,6 +792,7 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
entries.append(.sendLogs(presentationData.theme)) entries.append(.sendLogs(presentationData.theme))
entries.append(.sendOneLog(presentationData.theme)) entries.append(.sendOneLog(presentationData.theme))
entries.append(.sendShareLogs)
entries.append(.sendNotificationLogs(presentationData.theme)) entries.append(.sendNotificationLogs(presentationData.theme))
entries.append(.sendCriticalLogs(presentationData.theme)) entries.append(.sendCriticalLogs(presentationData.theme))
entries.append(.accounts(presentationData.theme)) entries.append(.accounts(presentationData.theme))
@ -735,6 +820,7 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
//entries.append(.photoPreview(presentationData.theme, experimentalSettings.chatListPhotos)) //entries.append(.photoPreview(presentationData.theme, experimentalSettings.chatListPhotos))
entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper)) entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper))
entries.append(.alternativeFolderTabs(experimentalSettings.foldersTabAtBottom)) entries.append(.alternativeFolderTabs(experimentalSettings.foldersTabAtBottom))
entries.append(.snapPinListToTop(experimentalSettings.snapPinListToTop))
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding)) entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))

View File

@ -77,6 +77,7 @@ public final class Logger {
private let maxShortLength: Int = 1 * 1024 * 1024 private let maxShortLength: Int = 1 * 1024 * 1024
private let maxFiles: Int = 20 private let maxFiles: Int = 20
private let rootPath: String
private let basePath: String private let basePath: String
private var file: (ManagedFile, Int)? private var file: (ManagedFile, Int)?
private var shortFile: (ManagedFile, Int)? private var shortFile: (ManagedFile, Int)?
@ -114,22 +115,30 @@ public final class Logger {
return sharedLogger return sharedLogger
} else { } else {
assertionFailure() assertionFailure()
let tempLogger = Logger(basePath: "") let tempLogger = Logger(rootPath: "", basePath: "")
tempLogger.logToFile = false tempLogger.logToFile = false
tempLogger.logToConsole = false tempLogger.logToConsole = false
return tempLogger return tempLogger
} }
} }
public init(basePath: String) { public init(rootPath: String, basePath: String) {
self.rootPath = rootPath
self.basePath = basePath self.basePath = basePath
} }
public func collectLogs() -> Signal<[(String, String)], NoError> { public func collectLogs(prefix: String? = nil) -> Signal<[(String, String)], NoError> {
return Signal { subscriber in return Signal { subscriber in
self.queue.async { self.queue.async {
let logsPath: String
if let prefix = prefix {
logsPath = self.rootPath + prefix
} else {
logsPath = self.basePath
}
var result: [(Date, String, String)] = [] var result: [(Date, String, String)] = []
if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: self.basePath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) { if let files = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: logsPath), includingPropertiesForKeys: [URLResourceKey.creationDateKey], options: []) {
for url in files { for url in files {
if url.lastPathComponent.hasPrefix("log-") { if url.lastPathComponent.hasPrefix("log-") {
if let creationDate = (try? url.resourceValues(forKeys: Set([.creationDateKey])))?.creationDate { if let creationDate = (try? url.resourceValues(forKeys: Set([.creationDateKey])))?.creationDate {

View File

@ -444,7 +444,7 @@ final class SharedApplicationContext {
let logsPath = rootPath + "/logs" let logsPath = rootPath + "/logs"
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil) let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
Logger.setSharedLogger(Logger(basePath: logsPath)) Logger.setSharedLogger(Logger(rootPath: rootPath, basePath: logsPath))
if let contents = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: rootPath + "/accounts-metadata"), includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants]) { if let contents = try? FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: rootPath + "/accounts-metadata"), includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants]) {
for url in contents { for url in contents {

View File

@ -155,6 +155,18 @@ private func calculateSlowmodeActiveUntilTimestamp(account: Account, untilTimest
} }
} }
private struct ScrolledToMessageId: Equatable {
struct AllowedReplacementDirections: OptionSet {
var rawValue: Int32
static let up = AllowedReplacementDirections(rawValue: 1 << 0)
static let down = AllowedReplacementDirections(rawValue: 1 << 1)
}
var id: MessageId
var allowedReplacementDirection: AllowedReplacementDirections
}
public final class ChatControllerImpl: TelegramBaseController, ChatController, GalleryHiddenMediaTarget, UIDropInteractionDelegate { public final class ChatControllerImpl: TelegramBaseController, ChatController, GalleryHiddenMediaTarget, UIDropInteractionDelegate {
private var validLayout: ContainerViewLayout? private var validLayout: ContainerViewLayout?
@ -358,7 +370,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
public var purposefulAction: (() -> Void)? public var purposefulAction: (() -> Void)?
private let scrolledToMessageId = ValuePromise<MessageId?>(nil, ignoreRepeated: true) private let scrolledToMessageId = ValuePromise<ScrolledToMessageId?>(nil, ignoreRepeated: true)
private var scrolledToMessageIdValue: ScrolledToMessageId? = nil {
didSet {
self.scrolledToMessageId.set(self.scrolledToMessageIdValue)
}
}
public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil) { public init(context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false), peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil) {
let _ = ChatControllerCount.modify { value in let _ = ChatControllerCount.modify { value in
@ -3247,7 +3264,62 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError> let topPinnedMessage: Signal<ChatPinnedMessage?, NoError>
switch self.chatLocation { switch self.chatLocation {
case let .peer(peerId): case let .peer(peerId):
let replyHistory: Signal<ChatHistoryViewUpdate, NoError> = (chatHistoryViewForLocation(ChatHistoryLocationInput(content: .Initial(count: 100), id: 0), context: self.context, chatLocation: .peer(peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), scheduled: false, fixedCombinedReadStates: nil, tagMask: MessageTags.pinned, additionalData: []) struct ReferenceMessage {
var id: MessageId
var isScrolled: Bool
}
let messageRangeEdge: Signal<Bool, NoError> = self.context.sharedContext.accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.experimentalUISettings]))
|> map { sharedData -> Bool in
let experimentalSettings: ExperimentalUISettings = (sharedData.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings) ?? ExperimentalUISettings.defaultSettings
return experimentalSettings.snapPinListToTop
}
|> distinctUntilChanged
let referenceMessage: Signal<ReferenceMessage?, NoError>
if latest {
referenceMessage = .single(nil)
} else {
referenceMessage = combineLatest(
queue: Queue.mainQueue(),
self.scrolledToMessageId.get(),
self.chatDisplayNode.historyNode.topVisibleMessageRange.get(),
messageRangeEdge
)
|> map { scrolledToMessageId, topVisibleMessageRange, messageRangeEdge -> ReferenceMessage? in
let topVisibleMessage: MessageId?
if messageRangeEdge {
topVisibleMessage = topVisibleMessageRange?.lowerBound
} else {
topVisibleMessage = topVisibleMessageRange?.upperBound
}
if let scrolledToMessageId = scrolledToMessageId {
if let topVisibleMessage = topVisibleMessage {
if scrolledToMessageId.allowedReplacementDirection.contains(.up) && topVisibleMessage < scrolledToMessageId.id {
return ReferenceMessage(id: topVisibleMessage, isScrolled: false)
}
}
return ReferenceMessage(id: scrolledToMessageId.id, isScrolled: true)
} else if let topVisibleMessage = topVisibleMessage {
return ReferenceMessage(id: topVisibleMessage, isScrolled: false)
} else {
return nil
}
}
}
let context = self.context
func replyHistorySignal(anchorMessageId: MessageId?, count: Int) -> Signal<ChatHistoryViewUpdate, NoError> {
let location: ChatHistoryLocation
if let anchorMessageId = anchorMessageId {
location = .InitialSearch(location: .id(anchorMessageId), count: count, highlight: false)
} else {
location = .Initial(count: count)
}
return (chatHistoryViewForLocation(ChatHistoryLocationInput(content: location, id: 0), context: context, chatLocation: .peer(peerId), chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil), scheduled: false, fixedCombinedReadStates: nil, tagMask: MessageTags.pinned, additionalData: [])
|> castError(Bool.self) |> castError(Bool.self)
|> mapToSignal { update -> Signal<ChatHistoryViewUpdate, Bool> in |> mapToSignal { update -> Signal<ChatHistoryViewUpdate, Bool> in
switch update { switch update {
@ -3263,59 +3335,126 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return .single(update) return .single(update)
}) })
|> restartIfError |> restartIfError
struct ReferenceMessage {
var id: MessageId
var isScrolled: Bool
} }
let referenceMessage: Signal<ReferenceMessage?, NoError> let topMessage = replyHistorySignal(anchorMessageId: nil, count: 3)
if latest { |> map { update -> Message? in
referenceMessage = .single(nil)
} else {
referenceMessage = combineLatest(
queue: Queue.mainQueue(),
self.scrolledToMessageId.get(),
self.chatDisplayNode.historyNode.topVisibleMessageRange.get()
)
|> map { scrolledToMessageId, topVisibleMessageRange -> ReferenceMessage? in
if let scrolledToMessageId = scrolledToMessageId {
return ReferenceMessage(id: scrolledToMessageId, isScrolled: true)
} else if let topVisibleMessageRange = topVisibleMessageRange {
return ReferenceMessage(id: topVisibleMessageRange.upperBound, isScrolled: false)
} else {
return nil
}
}
}
topPinnedMessage = combineLatest(
replyHistory,
referenceMessage
)
|> map { update, referenceMessage -> ChatPinnedMessage? in
var message: ChatPinnedMessage?
switch update { switch update {
case .Loading: case .Loading:
break return nil
case let .HistoryView(view, _, _, _, _, _, _): case let .HistoryView(viewValue, _, _, _, _, _, _):
return viewValue.entries.last?.message
}
}
let loadCount = 100
let adjustedReplyHistory: Signal<[Message], NoError>
if latest {
adjustedReplyHistory = replyHistorySignal(anchorMessageId: nil, count: loadCount)
|> map { view -> [Message] in
switch view {
case .Loading:
return []
case let .HistoryView(viewValue, _, _, _, _, _, _):
return viewValue.entries.map(\.message)
}
}
} else {
adjustedReplyHistory = (Signal<[Message], NoError> { subscriber in
var referenceMessageValue: ReferenceMessage?
var view: ChatHistoryViewUpdate?
let updateState: () -> Void = {
guard let view = view else {
return
}
guard case let .HistoryView(viewValue, _, _, _, _, _, _) = view else {
subscriber.putNext([])
return
}
if let referenceId = referenceMessageValue?.id {
if viewValue.entries.count < loadCount {
subscriber.putNext(viewValue.entries.map(\.message))
} else if referenceId < viewValue.entries[1].message.id {
if viewValue.earlierId != nil {
subscriber.putCompletion()
} else {
subscriber.putNext(viewValue.entries.map(\.message))
}
} else if referenceId > viewValue.entries[viewValue.entries.count - 2].message.id {
if viewValue.laterId != nil {
subscriber.putCompletion()
} else {
subscriber.putNext(viewValue.entries.map(\.message))
}
} else {
subscriber.putNext(viewValue.entries.map(\.message))
}
} else {
if viewValue.holeLater || viewValue.laterId != nil {
subscriber.putCompletion()
} else {
subscriber.putNext(viewValue.entries.map(\.message))
}
}
}
var initializedView = false
let viewDisposable = MetaDisposable()
let referenceDisposable = (referenceMessage
|> deliverOnMainQueue).start(next: { referenceMessage in
referenceMessageValue = referenceMessage
if !initializedView {
initializedView = true
//print("reload at \(String(describing: referenceMessage?.id)) disposable \(unsafeBitCast(viewDisposable, to: UInt64.self))")
viewDisposable.set((replyHistorySignal(anchorMessageId: referenceMessage?.id, count: loadCount)
|> deliverOnMainQueue).start(next: { next in
view = next
updateState()
}))
}
updateState()
})
return ActionDisposable {
//print("dispose \(unsafeBitCast(viewDisposable, to: UInt64.self))")
referenceDisposable.dispose()
viewDisposable.dispose()
}
}
|> runOn(.mainQueue()))
|> restart
}
topPinnedMessage = combineLatest(queue: .mainQueue(),
adjustedReplyHistory,
topMessage,
referenceMessage
)
|> map { messages, topMessage, referenceMessage -> ChatPinnedMessage? in
var message: ChatPinnedMessage?
let topMessageId: MessageId let topMessageId: MessageId
if view.entries.isEmpty { if messages.isEmpty {
return nil return nil
} }
topMessageId = view.entries[view.entries.count - 1].message.id topMessageId = topMessage?.id ?? messages[messages.count - 1].id
for i in 0 ..< view.entries.count { //print("reference: \(String(describing: referenceMessage?.id.id)) entries: \(view.entries.map(\.index.id.id))")
let entry = view.entries[i] for i in 0 ..< messages.count {
let entry = messages[i]
var matches = false var matches = false
if message == nil { if message == nil {
matches = true matches = true
} else if let referenceMessage = referenceMessage { } else if let referenceMessage = referenceMessage {
if referenceMessage.isScrolled { if referenceMessage.isScrolled {
if entry.message.id < referenceMessage.id { if entry.id < referenceMessage.id {
matches = true matches = true
} }
} else { } else {
if entry.message.id <= referenceMessage.id { if entry.id <= referenceMessage.id {
matches = true matches = true
} }
} }
@ -3323,11 +3462,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
matches = true matches = true
} }
if matches { if matches {
message = ChatPinnedMessage(message: entry.message, topMessageId: topMessageId) message = ChatPinnedMessage(message: entry, topMessageId: topMessageId)
} }
} }
break
}
return message return message
} }
|> distinctUntilChanged |> distinctUntilChanged
@ -3355,12 +3493,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
self.chatDisplayNode.historyNode.beganDragging = { [weak self] in self.chatDisplayNode.historyNode.didScrollWithOffset = { [weak self] offset, _, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.scrolledToMessageId.set(nil) if offset > 0.0 {
if var scrolledToMessageIdValue = strongSelf.scrolledToMessageIdValue {
scrolledToMessageIdValue.allowedReplacementDirection.insert(.up)
strongSelf.scrolledToMessageIdValue = scrolledToMessageIdValue
}
} else if offset < 0.0 {
strongSelf.scrolledToMessageIdValue = nil
}
} }
self.chatDisplayNode.peerView = self.peerView self.chatDisplayNode.peerView = self.peerView
@ -3690,7 +3835,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId) let highlightedState = ChatInterfaceHighlightedState(messageStableId: message.stableId)
controllerInteraction.highlightedState = highlightedState controllerInteraction.highlightedState = highlightedState
strongSelf.updateItemNodesHighlightedStates(animated: false) strongSelf.updateItemNodesHighlightedStates(animated: false)
strongSelf.scrolledToMessageId.set(index.id) strongSelf.scrolledToMessageIdValue = ScrolledToMessageId(id: index.id, allowedReplacementDirection: [])
strongSelf.messageContextDisposable.set((Signal<Void, NoError>.complete() |> delay(0.7, queue: Queue.mainQueue())).start(completed: { strongSelf.messageContextDisposable.set((Signal<Void, NoError>.complete() |> delay(0.7, queue: Queue.mainQueue())).start(completed: {
if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction {
@ -3709,7 +3854,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
strongSelf.scrolledToMessageId.set(nil) strongSelf.scrolledToMessageIdValue = nil
} }
self.chatDisplayNode.historyNode.maxVisibleMessageIndexUpdated = { [weak self] index in self.chatDisplayNode.historyNode.maxVisibleMessageIndexUpdated = { [weak self] index in

View File

@ -25,10 +25,10 @@ private var sharedAccountContext: SharedAccountContext?
private var installedSharedLogger = false private var installedSharedLogger = false
private func setupSharedLogger(_ path: String) { private func setupSharedLogger(rootPath: String, path: String) {
if !installedSharedLogger { if !installedSharedLogger {
installedSharedLogger = true installedSharedLogger = true
Logger.setSharedLogger(Logger(basePath: path)) Logger.setSharedLogger(Logger(rootPath: rootPath, basePath: path))
} }
} }
@ -107,7 +107,7 @@ public final class NotificationViewControllerImpl {
let logsPath = rootPath + "/notificationcontent-logs" let logsPath = rootPath + "/notificationcontent-logs"
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil) let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
setupSharedLogger(logsPath) setupSharedLogger(rootPath: rootPath, path: logsPath)
accountsPath = rootPath accountsPath = rootPath

View File

@ -35,10 +35,10 @@ private var globalInternalContext: InternalContext?
private var installedSharedLogger = false private var installedSharedLogger = false
private func setupSharedLogger(_ path: String) { private func setupSharedLogger(rootPath: String, path: String) {
if !installedSharedLogger { if !installedSharedLogger {
installedSharedLogger = true installedSharedLogger = true
Logger.setSharedLogger(Logger(basePath: path)) Logger.setSharedLogger(Logger(rootPath: rootPath, basePath: path))
} }
} }
@ -138,7 +138,7 @@ public class ShareRootControllerImpl {
let logsPath = rootPath + "/share-logs" let logsPath = rootPath + "/share-logs"
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil) let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
setupSharedLogger(logsPath) setupSharedLogger(rootPath: rootPath, path: logsPath)
let applicationBindings = TelegramApplicationBindings(isMainApp: false, containerPath: self.initializationData.appGroupPath, appSpecificScheme: "tg", openUrl: { _ in let applicationBindings = TelegramApplicationBindings(isMainApp: false, containerPath: self.initializationData.appGroupPath, appSpecificScheme: "tg", openUrl: { _ in
}, openUniversalUrl: { _, completion in }, openUniversalUrl: { _, completion in

View File

@ -14,6 +14,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
public var preferredVideoCodec: String? public var preferredVideoCodec: String?
public var disableVideoAspectScaling: Bool public var disableVideoAspectScaling: Bool
public var enableVoipTcp: Bool public var enableVoipTcp: Bool
public var snapPinListToTop: Bool
public static var defaultSettings: ExperimentalUISettings { public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings( return ExperimentalUISettings(
@ -27,7 +28,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
playlistPlayback: false, playlistPlayback: false,
preferredVideoCodec: nil, preferredVideoCodec: nil,
disableVideoAspectScaling: false, disableVideoAspectScaling: false,
enableVoipTcp: false enableVoipTcp: false,
snapPinListToTop: false
) )
} }
@ -42,7 +44,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
playlistPlayback: Bool, playlistPlayback: Bool,
preferredVideoCodec: String?, preferredVideoCodec: String?,
disableVideoAspectScaling: Bool, disableVideoAspectScaling: Bool,
enableVoipTcp: Bool enableVoipTcp: Bool,
snapPinListToTop: Bool
) { ) {
self.keepChatNavigationStack = keepChatNavigationStack self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory self.skipReadHistory = skipReadHistory
@ -55,6 +58,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
self.preferredVideoCodec = preferredVideoCodec self.preferredVideoCodec = preferredVideoCodec
self.disableVideoAspectScaling = disableVideoAspectScaling self.disableVideoAspectScaling = disableVideoAspectScaling
self.enableVoipTcp = enableVoipTcp self.enableVoipTcp = enableVoipTcp
self.snapPinListToTop = snapPinListToTop
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
@ -69,6 +73,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
self.preferredVideoCodec = decoder.decodeOptionalStringForKey("preferredVideoCodec") self.preferredVideoCodec = decoder.decodeOptionalStringForKey("preferredVideoCodec")
self.disableVideoAspectScaling = decoder.decodeInt32ForKey("disableVideoAspectScaling", orElse: 0) != 0 self.disableVideoAspectScaling = decoder.decodeInt32ForKey("disableVideoAspectScaling", orElse: 0) != 0
self.enableVoipTcp = decoder.decodeInt32ForKey("enableVoipTcp", orElse: 0) != 0 self.enableVoipTcp = decoder.decodeInt32ForKey("enableVoipTcp", orElse: 0) != 0
self.snapPinListToTop = decoder.decodeInt32ForKey("snapPinListToTop", orElse: 0) != 0
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
@ -85,6 +90,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
} }
encoder.encodeInt32(self.disableVideoAspectScaling ? 1 : 0, forKey: "disableVideoAspectScaling") encoder.encodeInt32(self.disableVideoAspectScaling ? 1 : 0, forKey: "disableVideoAspectScaling")
encoder.encodeInt32(self.enableVoipTcp ? 1 : 0, forKey: "enableVoipTcp") encoder.encodeInt32(self.enableVoipTcp ? 1 : 0, forKey: "enableVoipTcp")
encoder.encodeInt32(self.snapPinListToTop ? 1 : 0, forKey: "snapPinListToTop")
} }
public func isEqual(to: PreferencesEntry) -> Bool { public func isEqual(to: PreferencesEntry) -> Bool {