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

This commit is contained in:
Ilya Laktyushin 2023-04-12 12:41:17 +04:00
commit c88d5e16cf
28 changed files with 819 additions and 74 deletions

View File

@ -21,6 +21,7 @@ swift_library(
"//submodules/rlottie:RLottieBinding",
"//submodules/GZip:GZip",
"//submodules/PersistentStringHash:PersistentStringHash",
"//submodules/Utils/RangeSet",
],
visibility = [

View File

@ -15,6 +15,7 @@ import PersistentStringHash
import CallKit
import AppLockState
import NotificationsPresentationData
import RangeSet
private let queue = Queue()
@ -1187,7 +1188,7 @@ private final class NotificationServiceHandler {
fetchMediaSignal = Signal { subscriber in
final class DataValue {
var data = Data()
var totalSize: Int64?
var missingRanges = RangeSet<Int64>(0 ..< Int64.max)
}
let collectedData = Atomic<DataValue>(value: DataValue())
@ -1217,12 +1218,22 @@ private final class NotificationServiceHandler {
useMainConnection: true
).start(next: { result in
switch result {
case let .dataPart(_, data, _, _):
case let .dataPart(offset, data, dataRange, _):
var isCompleted = false
let _ = collectedData.modify { current in
let current = current
current.data.append(data)
if let totalSize = current.totalSize, Int64(current.data.count) >= totalSize {
let fillRange = Int(offset) ..< (Int(offset) + data.count)
if current.data.count < fillRange.upperBound {
current.data.count = fillRange.upperBound
}
current.data.withUnsafeMutableBytes { buffer -> Void in
let bytes = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
data.copyBytes(to: bytes.advanced(by: Int(offset)), from: Int(dataRange.lowerBound) ..< Int(dataRange.upperBound))
}
current.missingRanges.remove(contentsOf: Int64(fillRange.lowerBound) ..< Int64(fillRange.upperBound))
if current.missingRanges.isEmpty {
isCompleted = true
}
return current
@ -1235,8 +1246,8 @@ private final class NotificationServiceHandler {
var isCompleted = false
let _ = collectedData.modify { current in
let current = current
current.totalSize = size
if Int64(current.data.count) >= size {
current.missingRanges.remove(contentsOf: size ..< Int64.max)
if current.missingRanges.isEmpty {
isCompleted = true
}
return current

View File

@ -330,7 +330,7 @@ private final class AnimatedStickerDirectFrameSourceCache {
return .notFound
}
self.file.seek(position: Int64(index * 4 * 2))
let _ = self.file.seek(position: Int64(index * 4 * 2))
var offset: Int32 = 0
var length: Int32 = 0
if self.file.read(&offset, 4) != 4 {
@ -384,12 +384,12 @@ private final class AnimatedStickerDirectFrameSourceCache {
return
}
strongSelf.file.seek(position: Int64(index * 4 * 2))
let _ = strongSelf.file.seek(position: Int64(index * 4 * 2))
var offset = Int32(currentSize)
var length = Int32(compressedData.data.count)
let _ = strongSelf.file.write(&offset, count: 4)
let _ = strongSelf.file.write(&length, count: 4)
strongSelf.file.seek(position: Int64(currentSize))
let _ = strongSelf.file.seek(position: Int64(currentSize))
compressedData.data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
if let baseAddress = buffer.baseAddress {
let _ = strongSelf.file.write(baseAddress, count: Int(length))
@ -427,12 +427,12 @@ private final class AnimatedStickerDirectFrameSourceCache {
return
}
strongSelf.file.seek(position: Int64(index * 4 * 2))
let _ = strongSelf.file.seek(position: Int64(index * 4 * 2))
var offset = Int32(currentSize)
var length = Int32(compressedData.count)
let _ = strongSelf.file.write(&offset, count: 4)
let _ = strongSelf.file.write(&length, count: 4)
strongSelf.file.seek(position: Int64(currentSize))
let _ = strongSelf.file.seek(position: Int64(currentSize))
compressedData.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
if let baseAddress = buffer.baseAddress {
let _ = strongSelf.file.write(baseAddress, count: Int(length))
@ -502,7 +502,7 @@ private final class AnimatedStickerDirectFrameSourceCache {
switch rangeResult {
case let .range(range):
self.file.seek(position: Int64(range.lowerBound))
let _ = self.file.seek(position: Int64(range.lowerBound))
let length = range.upperBound - range.lowerBound
let compressedData = self.file.readData(count: length)
if compressedData.count != length {

View File

@ -100,7 +100,7 @@ private final class VideoStickerFrameSourceCache {
return true
}
self.file.seek(position: 0)
let _ = self.file.seek(position: 0)
var frameRate: Int32 = 0
if self.file.read(&frameRate, 4) != 4 {
return false
@ -113,7 +113,7 @@ private final class VideoStickerFrameSourceCache {
}
self.frameRate = frameRate
self.file.seek(position: 4)
let _ = self.file.seek(position: 4)
var frameCount: Int32 = 0
if self.file.read(&frameCount, 4) != 4 {
@ -144,7 +144,7 @@ private final class VideoStickerFrameSourceCache {
return .notFound
}
self.file.seek(position: Int64(8 + index * 4 * 2))
let _ = self.file.seek(position: Int64(8 + index * 4 * 2))
var offset: Int32 = 0
var length: Int32 = 0
if self.file.read(&offset, 4) != 4 {
@ -167,11 +167,11 @@ private final class VideoStickerFrameSourceCache {
}
func storeFrameRateAndCount(frameRate: Int, frameCount: Int) {
self.file.seek(position: 0)
let _ = self.file.seek(position: 0)
var frameRate = Int32(frameRate)
let _ = self.file.write(&frameRate, count: 4)
self.file.seek(position: 4)
let _ = self.file.seek(position: 4)
var frameCount = Int32(frameCount)
let _ = self.file.write(&frameCount, count: 4)
}
@ -203,12 +203,12 @@ private final class VideoStickerFrameSourceCache {
return
}
strongSelf.file.seek(position: Int64(8 + index * 4 * 2))
let _ = strongSelf.file.seek(position: Int64(8 + index * 4 * 2))
var offset = Int32(currentSize)
var length = Int32(compressedData.count)
let _ = strongSelf.file.write(&offset, count: 4)
let _ = strongSelf.file.write(&length, count: 4)
strongSelf.file.seek(position: Int64(currentSize))
let _ = strongSelf.file.seek(position: Int64(currentSize))
compressedData.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
if let baseAddress = buffer.baseAddress {
let _ = strongSelf.file.write(baseAddress, count: Int(length))
@ -226,7 +226,7 @@ private final class VideoStickerFrameSourceCache {
switch rangeResult {
case let .range(range):
self.file.seek(position: Int64(range.lowerBound))
let _ = self.file.seek(position: Int64(range.lowerBound))
let length = range.upperBound - range.lowerBound
let compressedData = self.file.readData(count: length)
if compressedData.count != length {

View File

@ -2000,6 +2000,36 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let _ = value
})
})
Queue.mainQueue().after(2.0, { [weak self] in
guard let self else {
return
}
//TODO:generalize
var hasEmptyMark = false
self.chatListDisplayNode.mainContainerNode.currentItemNode.forEachItemNode { itemNode in
if itemNode is ChatListSectionHeaderNode {
hasEmptyMark = true
}
}
if hasEmptyMark {
if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
if let rightButtonView = componentView.rightButtonView {
let absoluteFrame = rightButtonView.convert(rightButtonView.bounds, to: self.view)
//TODO:localize
let text: String = "Send a message or\nstart a group here."
let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0))
self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
guard let self else {
return nil
}
return (self.displayNode, absoluteFrame.insetBy(dx: 4.0, dy: 8.0).offsetBy(dx: 4.0, dy: -1.0))
}))
}
}
}
})
}
self.chatListDisplayNode.mainContainerNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in

View File

@ -0,0 +1,278 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Postbox
import Display
import SwiftSignalKit
import TelegramPresentationData
import ListSectionHeaderNode
import AppBundle
import AnimatedStickerNode
import TelegramAnimatedStickerNode
class ChatListEmptyInfoItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let selectable: Bool = false
init(theme: PresentationTheme, strings: PresentationStrings) {
self.theme = theme
self.strings = strings
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ChatListEmptyInfoItemNode()
let (nodeLayout, apply) = node.asyncLayout()(self, params, false)
node.insets = nodeLayout.insets
node.contentSize = nodeLayout.contentSize
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in
apply()
})
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
assert(node() is ChatListEmptyInfoItemNode)
if let nodeValue = node() as? ChatListEmptyInfoItemNode {
let layout = nodeValue.asyncLayout()
async {
let (nodeLayout, apply) = layout(self, params, nextItem == nil)
Queue.mainQueue().async {
completion(nodeLayout, { _ in
apply()
})
}
}
}
}
}
}
class ChatListEmptyInfoItemNode: ListViewItemNode {
private var item: ChatListEmptyInfoItem?
private let animationNode: AnimatedStickerNode
private let textNode: TextNode
override var visibility: ListViewItemNodeVisibility {
didSet {
let wasVisible = self.visibilityStatus
let isVisible: Bool
switch self.visibility {
case let .visible(fraction, _):
isVisible = fraction > 0.2
case .none:
isVisible = false
}
if wasVisible != isVisible {
self.visibilityStatus = isVisible
}
}
}
private var visibilityStatus: Bool = false {
didSet {
if self.visibilityStatus != oldValue {
self.animationNode.visibility = self.visibilityStatus
}
}
}
required init() {
self.animationNode = DefaultAnimatedStickerNodeImpl()
self.textNode = TextNode()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.animationNode)
self.addSubnode(self.textNode)
}
override func didLoad() {
super.didLoad()
}
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
let layout = self.asyncLayout()
let (_, apply) = layout(item as! ChatListEmptyInfoItem, params, nextItem == nil)
apply()
}
func asyncLayout() -> (_ item: ChatListEmptyInfoItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) {
let makeTextLayout = TextNode.asyncLayout(self.textNode)
return { item, params, last in
let baseWidth = params.width - params.leftInset - params.rightInset
let topInset: CGFloat = 8.0
let textSpacing: CGFloat = 27.0
let bottomInset: CGFloat = 24.0
let animationHeight: CGFloat = 140.0
let string = NSMutableAttributedString(string: item.strings.ChatList_EmptyChatList, font: Font.semibold(17.0), textColor: item.theme.list.itemPrimaryTextColor)
let textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: string, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: baseWidth, height: .greatestFiniteMagnitude), alignment: .center))
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: topInset + animationHeight + textSpacing + textLayout.0.size.height + bottomInset), insets: UIEdgeInsets())
return (layout, { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.item = item
var topOffset: CGFloat = topInset
let animationFrame = CGRect(origin: CGPoint(x: floor((params.width - animationHeight) * 0.5), y: topOffset), size: CGSize(width: animationHeight, height: animationHeight))
if strongSelf.animationNode.bounds.isEmpty {
strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ChatListEmpty"), width: 248, height: 248, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
}
strongSelf.animationNode.frame = animationFrame
topOffset += animationHeight + textSpacing
let _ = textLayout.1()
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.0.size.width) * 0.5), y: topOffset), size: textLayout.0.size)
strongSelf.contentSize = layout.contentSize
strongSelf.insets = layout.insets
})
}
}
}
class ChatListSectionHeaderItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let hide: (() -> Void)?
let selectable: Bool = false
init(theme: PresentationTheme, strings: PresentationStrings, hide: (() -> Void)?) {
self.theme = theme
self.strings = strings
self.hide = hide
}
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
async {
let node = ChatListSectionHeaderNode()
let (nodeLayout, apply) = node.asyncLayout()(self, params, false)
node.insets = nodeLayout.insets
node.contentSize = nodeLayout.contentSize
Queue.mainQueue().async {
completion(node, {
return (nil, { _ in
apply()
})
})
}
}
}
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
Queue.mainQueue().async {
assert(node() is ChatListSectionHeaderNode)
if let nodeValue = node() as? ChatListSectionHeaderNode {
let layout = nodeValue.asyncLayout()
async {
let (nodeLayout, apply) = layout(self, params, nextItem == nil)
Queue.mainQueue().async {
completion(nodeLayout, { _ in
apply()
})
}
}
}
}
}
}
class ChatListSectionHeaderNode: ListViewItemNode {
private var item: ChatListSectionHeaderItem?
private var headerNode: ListSectionHeaderNode?
required init() {
super.init(layerBacked: false, dynamicBounce: false)
self.zPosition = 1.0
}
override func didLoad() {
super.didLoad()
}
override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
let layout = self.asyncLayout()
let (_, apply) = layout(item as! ChatListSectionHeaderItem, params, nextItem == nil)
apply()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let headerNode = self.headerNode {
if let result = headerNode.view.hitTest(self.view.convert(point, to: headerNode.view), with: event) {
return result
}
}
return nil
}
func asyncLayout() -> (_ item: ChatListSectionHeaderItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) {
return { item, params, last in
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 28.0), insets: UIEdgeInsets())
return (layout, { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.item = item
let headerNode: ListSectionHeaderNode
if let current = strongSelf.headerNode {
headerNode = current
} else {
headerNode = ListSectionHeaderNode(theme: item.theme)
strongSelf.headerNode = headerNode
strongSelf.addSubnode(headerNode)
}
//TODO:localize
headerNode.title = "YOUR CONTACTS ON TELEGRAM"
if item.hide != nil {
headerNode.action = "hide"
headerNode.actionType = .generic
headerNode.activateAction = {
guard let self else {
return
}
self.item?.hide?()
}
} else {
headerNode.action = nil
}
headerNode.updateTheme(theme: item.theme)
headerNode.updateLayout(size: CGSize(width: params.width, height: layout.contentSize.height), leftInset: params.leftInset, rightInset: params.rightInset)
strongSelf.contentSize = layout.contentSize
strongSelf.insets = layout.insets
})
}
}
}

View File

@ -3312,6 +3312,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleLayout.trailingLineWidth + 3.0 + titleOffset
let lastLineRect: CGRect
if let rect = titleLayout.linesRects().last {
lastLineRect = CGRect(origin: CGPoint(x: 0.0, y: titleLayout.size.height - rect.height - 2.0), size: CGSize(width: rect.width, height: rect.height + 2.0))
} else {
lastLineRect = CGRect(origin: CGPoint(), size: titleLayout.size)
}
if let currentCredibilityIconContent = currentCredibilityIconContent {
let credibilityIconView: ComponentHostView<Empty>
@ -3339,7 +3345,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
environment: {},
containerSize: CGSize(width: 20.0, height: 20.0)
)
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0) - UIScreenPixel), size: iconSize))
transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.maxY - lastLineRect.height * 0.5 - iconSize.height / 2.0) - UIScreenPixel), size: iconSize))
nextTitleIconOrigin += credibilityIconView.bounds.width + 4.0
} else if let credibilityIconView = strongSelf.credibilityIconView {
strongSelf.credibilityIconView = nil
@ -3349,7 +3355,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let currentMutedIconImage = currentMutedIconImage {
strongSelf.mutedIconNode.image = currentMutedIconImage
strongSelf.mutedIconNode.isHidden = false
transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: floorToScreenPixels(titleFrame.midY - currentMutedIconImage.size.height / 2.0)), size: currentMutedIconImage.size))
transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: floorToScreenPixels(titleFrame.maxY - lastLineRect.height * 0.5 - currentMutedIconImage.size.height / 2.0)), size: currentMutedIconImage.size))
nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0
} else {
strongSelf.mutedIconNode.image = nil

View File

@ -616,8 +616,44 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
hiddenOffset: hiddenByDefault && !revealed,
interaction: nodeInteraction
), directionHint: entry.directionHint)
case let .ContactEntry(contactEntry):
let header: ChatListSearchItemHeader? = nil
var status: ContactsPeerItemStatus = .none
status = .presence(contactEntry.presence, contactEntry.presentationData.dateTimeFormat)
let presentationData = contactEntry.presentationData
let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer)
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder,
context: context,
peerMode: .generalSearch,
peer: peerContent,
status: status,
enabled: true,
selection: .none,
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
index: nil,
header: header,
action: { _ in
nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil)
},
disabledAction: nil,
animationCache: nodeInteraction.animationCache,
animationRenderer: nodeInteraction.animationRenderer
), directionHint: entry.directionHint)
case let .ArchiveIntro(presentationData):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
case let .EmptyIntro(presentationData):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
case let .SectionHeader(presentationData, displayHide):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
hideChatListContacts(context: context)
} : nil), directionHint: entry.directionHint)
case let .Notice(presentationData, notice):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
switch action {
@ -881,8 +917,44 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
hiddenOffset: hiddenByDefault && !revealed,
interaction: nodeInteraction
), directionHint: entry.directionHint)
case let .ContactEntry(contactEntry):
let header: ChatListSearchItemHeader? = nil
var status: ContactsPeerItemStatus = .none
status = .presence(contactEntry.presence, contactEntry.presentationData.dateTimeFormat)
let presentationData = contactEntry.presentationData
let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer)
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
sortOrder: presentationData.nameSortOrder,
displayOrder: presentationData.nameDisplayOrder,
context: context,
peerMode: .generalSearch,
peer: peerContent,
status: status,
enabled: true,
selection: .none,
editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
index: nil,
header: header,
action: { _ in
nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil)
},
disabledAction: nil,
animationCache: nodeInteraction.animationCache,
animationRenderer: nodeInteraction.animationRenderer
), directionHint: entry.directionHint)
case let .ArchiveIntro(presentationData):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
case let .EmptyIntro(presentationData):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
case let .SectionHeader(presentationData, displayHide):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
hideChatListContacts(context: context)
} : nil), directionHint: entry.directionHint)
case let .Notice(presentationData, notice):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
switch action {
@ -1702,6 +1774,65 @@ public final class ChatListNode: ListView {
let _ = self.enqueueTransition(value).start()
})*/
let contacts: Signal<[ChatListContactPeer], NoError>
if case .chatList(groupId: .root) = location, chatListFilter == nil {
contacts = ApplicationSpecificNotice.displayChatListContacts(accountManager: context.sharedContext.accountManager)
|> distinctUntilChanged
|> mapToSignal { value -> Signal<[ChatListContactPeer], NoError> in
if value {
return .single([])
}
return context.engine.messages.chatList(group: .root, count: 10)
|> map { chatList -> Bool in
if chatList.items.count >= 5 {
return true
} else {
return false
}
}
|> distinctUntilChanged
|> mapToSignal { hasChats -> Signal<[ChatListContactPeer], NoError> in
if hasChats {
return .single([])
}
return context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Contacts.List(includePresences: true)
)
|> mapToThrottled { next -> Signal<EngineContactList, NoError> in
return .single(next)
|> then(
.complete()
|> delay(5.0, queue: Queue.concurrentDefaultQueue())
)
}
|> map { contactList -> [ChatListContactPeer] in
var result: [ChatListContactPeer] = []
for peer in contactList.peers {
if peer.id == context.account.peerId {
continue
}
result.append(ChatListContactPeer(
peer: peer,
presence: contactList.presences[peer.id] ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0)
))
}
result.sort(by: { lhs, rhs in
if lhs.presence.status != rhs.presence.status {
return lhs.presence.status < rhs.presence.status
} else {
return lhs.peer.id < rhs.peer.id
}
})
return result
}
}
}
} else {
contacts = .single([])
}
let chatListNodeViewTransition = combineLatest(
queue: viewProcessingQueue,
hideArchivedFolderByDefault,
@ -1711,9 +1842,10 @@ public final class ChatListNode: ListView {
savedMessagesPeer,
chatListViewUpdate,
self.chatFolderUpdates.get() |> distinctUntilChanged,
self.statePromise.get()
self.statePromise.get(),
contacts
)
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, chatFolderUpdates, state) -> Signal<ChatListNodeListViewTransition, NoError> in
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, chatFolderUpdates, state, contacts) -> Signal<ChatListNodeListViewTransition, NoError> in
let (update, filter) = updateAndFilter
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
@ -1729,7 +1861,7 @@ public final class ChatListNode: ListView {
notice = nil
}
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location)
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location, contacts: contacts)
var isEmpty = true
var entries = rawEntries.filter { entry in
switch entry {
@ -1974,6 +2106,9 @@ public final class ChatListNode: ListView {
return false
}
}
case .ContactEntry:
isEmpty = false
return true
case .GroupReferenceEntry:
isEmpty = false
return true
@ -2910,7 +3045,7 @@ public final class ChatListNode: ListView {
var hasArchive = false
loop: for entry in transition.chatListView.filteredEntries {
switch entry {
case .GroupReferenceEntry, .HoleEntry, .PeerEntry:
case .GroupReferenceEntry, .HoleEntry, .PeerEntry, .ContactEntry:
if case .GroupReferenceEntry = entry {
hasArchive = true
} else {
@ -2929,7 +3064,7 @@ public final class ChatListNode: ListView {
} else {
break loop
}
case .ArchiveIntro, .Notice, .HeaderEntry, .AdditionalCategory:
case .ArchiveIntro, .EmptyIntro, .SectionHeader, .Notice, .HeaderEntry, .AdditionalCategory:
break
}
}
@ -3660,3 +3795,7 @@ public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
}
}
}
func hideChatListContacts(context: AccountContext) {
let _ = ApplicationSpecificNotice.setDisplayChatListContacts(accountManager: context.sharedContext.accountManager).start()
}

View File

@ -12,7 +12,10 @@ enum ChatListNodeEntryId: Hashable {
case PeerId(Int64)
case ThreadId(Int64)
case GroupId(EngineChatList.Group)
case ContactId(EnginePeer.Id)
case ArchiveIntro
case EmptyIntro
case SectionHeader
case Notice
case additionalCategory(Int)
}
@ -20,6 +23,8 @@ enum ChatListNodeEntryId: Hashable {
enum ChatListNodeEntrySortIndex: Comparable {
case index(EngineChatList.Item.Index)
case additionalCategory(Int)
case sectionHeader
case contact(id: EnginePeer.Id, presence: EnginePeer.Presence)
static func <(lhs: ChatListNodeEntrySortIndex, rhs: ChatListNodeEntrySortIndex) -> Bool {
switch lhs {
@ -29,6 +34,10 @@ enum ChatListNodeEntrySortIndex: Comparable {
return lhsIndex < rhsIndex
case .additionalCategory:
return false
case .sectionHeader:
return true
case .contact:
return true
}
case let .additionalCategory(lhsIndex):
switch rhs {
@ -36,6 +45,30 @@ enum ChatListNodeEntrySortIndex: Comparable {
return lhsIndex < rhsIndex
case .index:
return true
case .sectionHeader:
return true
case .contact:
return true
}
case .sectionHeader:
switch rhs {
case .additionalCategory, .index, .sectionHeader:
return false
case .contact:
return true
}
case let .contact(lhsId, lhsPresense):
switch rhs {
case .sectionHeader:
return false
case let .contact(rhsId, rhsPresense):
if lhsPresense != rhsPresense {
return rhsPresense.status > rhsPresense.status
} else {
return lhsId < rhsId
}
default:
return false
}
}
}
@ -238,11 +271,39 @@ enum ChatListNodeEntry: Comparable, Identifiable {
}
}
struct ContactEntryData: Equatable {
var presentationData: ChatListPresentationData
var peer: EnginePeer
var presence: EnginePeer.Presence
init(presentationData: ChatListPresentationData, peer: EnginePeer, presence: EnginePeer.Presence) {
self.presentationData = presentationData
self.peer = peer
self.presence = presence
}
static func ==(lhs: ContactEntryData, rhs: ContactEntryData) -> Bool {
if lhs.presentationData !== rhs.presentationData {
return false
}
if lhs.peer != rhs.peer {
return false
}
if lhs.presence != rhs.presence {
return false
}
return true
}
}
case HeaderEntry
case PeerEntry(PeerEntryData)
case HoleEntry(EngineMessage.Index, theme: PresentationTheme)
case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool)
case ContactEntry(ContactEntryData)
case ArchiveIntro(presentationData: ChatListPresentationData)
case EmptyIntro(presentationData: ChatListPresentationData)
case SectionHeader(presentationData: ChatListPresentationData, displayHide: Bool)
case Notice(presentationData: ChatListPresentationData, notice: ChatListNotice)
case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData)
@ -256,8 +317,14 @@ enum ChatListNodeEntry: Comparable, Identifiable {
return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex)))
case let .GroupReferenceEntry(index, _, _, _, _, _, _, _, _):
return .index(index)
case let .ContactEntry(contactEntry):
return .contact(id: contactEntry.peer.id, presence: contactEntry.presence)
case .ArchiveIntro:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
case .EmptyIntro:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
case .SectionHeader:
return .sectionHeader
case .Notice:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor.successor))
case let .AdditionalCategory(index, _, _, _, _, _, _):
@ -280,8 +347,14 @@ enum ChatListNodeEntry: Comparable, Identifiable {
return .Hole(Int64(holeIndex.id.id))
case let .GroupReferenceEntry(_, _, groupId, _, _, _, _, _, _):
return .GroupId(groupId)
case let .ContactEntry(contactEntry):
return .ContactId(contactEntry.peer.id)
case .ArchiveIntro:
return .ArchiveIntro
case .EmptyIntro:
return .EmptyIntro
case .SectionHeader:
return .SectionHeader
case .Notice:
return .Notice
case let .AdditionalCategory(_, id, _, _, _, _, _):
@ -347,6 +420,12 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else {
return false
}
case let .ContactEntry(contactEntry):
if case .ContactEntry(contactEntry) = rhs {
return true
} else {
return false
}
case let .ArchiveIntro(lhsPresentationData):
if case let .ArchiveIntro(rhsPresentationData) = rhs {
if lhsPresentationData !== rhsPresentationData {
@ -356,6 +435,27 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else {
return false
}
case let .EmptyIntro(lhsPresentationData):
if case let .EmptyIntro(rhsPresentationData) = rhs {
if lhsPresentationData !== rhsPresentationData {
return false
}
return true
} else {
return false
}
case let .SectionHeader(lhsPresentationData, lhsDisplayHide):
if case let .SectionHeader(rhsPresentationData, rhsDisplayHide) = rhs {
if lhsPresentationData !== rhsPresentationData {
return false
}
if lhsDisplayHide != rhsDisplayHide {
return false
}
return true
} else {
return false
}
case let .Notice(lhsPresentationData, lhsInfo):
if case let .Notice(rhsPresentationData, rhsInfo) = rhs {
if lhsPresentationData !== rhsPresentationData {
@ -407,9 +507,32 @@ private func offsetPinnedIndex(_ index: EngineChatList.Item.Index, offset: UInt1
}
}
func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation) -> (entries: [ChatListNodeEntry], loading: Bool) {
struct ChatListContactPeer {
var peer: EnginePeer
var presence: EnginePeer.Presence
init(peer: EnginePeer, presence: EnginePeer.Presence) {
self.peer = peer
self.presence = presence
}
}
func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer]) -> (entries: [ChatListNodeEntry], loading: Bool) {
var result: [ChatListNodeEntry] = []
if !view.hasEarlier {
for contact in contacts {
result.append(.ContactEntry(ChatListNodeEntry.ContactEntryData(
presentationData: state.presentationData,
peer: contact.peer,
presence: contact.presence
)))
}
if !contacts.isEmpty {
result.append(.SectionHeader(presentationData: state.presentationData, displayHide: !view.items.isEmpty))
}
}
var pinnedIndexOffset: UInt16 = 0
if !view.hasLater, case .chatList = mode {
@ -668,6 +791,14 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
if displayArchiveIntro {
result.append(.ArchiveIntro(presentationData: state.presentationData))
} else if !contacts.isEmpty && !result.contains(where: { entry in
if case .PeerEntry = entry {
return true
} else {
return false
}
}) {
result.append(.EmptyIntro(presentationData: state.presentationData))
}
if let notice {

View File

@ -123,13 +123,14 @@ open class TooltipController: ViewController, StandalonePresentableController {
private var timeoutTimer: SwiftSignalKit.Timer?
private var padding: CGFloat
private var innerPadding: UIEdgeInsets
private var layout: ContainerViewLayout?
private var initialArrowOnBottom: Bool
public var dismissed: ((Bool) -> Void)?
public init(content: TooltipControllerContent, baseFontSize: CGFloat, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true, padding: CGFloat = 8.0) {
public init(content: TooltipControllerContent, baseFontSize: CGFloat, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true, padding: CGFloat = 8.0, innerPadding: UIEdgeInsets = UIEdgeInsets()) {
self.content = content
self.baseFontSize = baseFontSize
self.timeout = timeout
@ -138,6 +139,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
self.dismissImmediatelyOnLayoutUpdate = dismissImmediatelyOnLayoutUpdate
self.initialArrowOnBottom = arrowOnBottom
self.padding = padding
self.innerPadding = innerPadding
super.init(navigationBarPresentationData: nil)
@ -157,6 +159,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
self?.dismiss(tappedInside: tappedInside)
}, dismissByTapOutside: self.dismissByTapOutside, dismissByTapOutsideSource: self.dismissByTapOutsideSource)
self.controllerNode.padding = self.padding
self.controllerNode.innerPadding = self.innerPadding
self.controllerNode.arrowOnBottom = self.initialArrowOnBottom
self.displayNodeDidLoad()
}

View File

@ -20,6 +20,7 @@ final class TooltipControllerNode: ASDisplayNode {
var arrowOnBottom: Bool = true
var padding: CGFloat = 8.0
var innerPadding: UIEdgeInsets = UIEdgeInsets()
private var dismissedByTouchOutside = false
private var dismissByTapOutsideSource = false
@ -98,14 +99,14 @@ final class TooltipControllerNode: ASDisplayNode {
textSize.width = ceil(textSize.width / 2.0) * 2.0
textSize.height = ceil(textSize.height / 2.0) * 2.0
contentSize = CGSize(width: imageSizeWithInset.width + textSize.width + 12.0, height: textSize.height + 34.0)
contentSize = CGSize(width: imageSizeWithInset.width + textSize.width + 12.0 + self.innerPadding.left + self.innerPadding.right, height: textSize.height + 34.0 + self.innerPadding.top + self.innerPadding.bottom)
let textFrame = CGRect(origin: CGPoint(x: 6.0 + imageSizeWithInset.width, y: 17.0), size: textSize)
let textFrame = CGRect(origin: CGPoint(x: 6.0 + self.innerPadding.left + imageSizeWithInset.width, y: 17.0 + self.innerPadding.top), size: textSize)
if transition.isAnimated, textFrame.size != self.textNode.frame.size {
transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: textFrame.minX - self.textNode.frame.minX, y: 0.0))
}
let imageFrame = CGRect(origin: CGPoint(x: 10.0, y: floor((contentSize.height - imageSize.height) / 2.0)), size: imageSize)
let imageFrame = CGRect(origin: CGPoint(x: self.innerPadding.left + 10.0, y: floor((contentSize.height - imageSize.height) / 2.0)), size: imageSize)
self.imageNode.frame = imageFrame
self.textNode.frame = textFrame
}

View File

@ -5,7 +5,7 @@ import Display
import TelegramPresentationData
private let titleFont = Font.bold(13.0)
private let actionFont = Font.medium(13.0)
private let actionFont = Font.regular(13.0)
public enum ListSectionHeaderActionType {
case generic
@ -13,6 +13,7 @@ public enum ListSectionHeaderActionType {
}
public final class ListSectionHeaderNode: ASDisplayNode {
private let backgroundLayer: SimpleLayer
private let label: ImmediateTextNode
private var actionButtonLabel: ImmediateTextNode?
private var actionButton: HighlightableButtonNode?
@ -87,16 +88,29 @@ public final class ListSectionHeaderNode: ASDisplayNode {
public init(theme: PresentationTheme) {
self.theme = theme
self.backgroundLayer = SimpleLayer()
self.label = ImmediateTextNode()
self.label.isUserInteractionEnabled = false
self.label.isAccessibilityElement = true
super.init()
self.layer.addSublayer(self.backgroundLayer)
self.addSubnode(self.label)
self.backgroundColor = theme.chatList.sectionHeaderFillColor
self.backgroundLayer.backgroundColor = theme.chatList.sectionHeaderFillColor.cgColor
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let actionButton = self.actionButton {
if actionButton.frame.contains(point) {
return actionButton.view
}
}
return super.hitTest(point, with: event)
}
public func updateTheme(theme: PresentationTheme) {
@ -105,7 +119,7 @@ public final class ListSectionHeaderNode: ASDisplayNode {
self.label.attributedText = NSAttributedString(string: self.title ?? "", font: titleFont, textColor: self.theme.chatList.sectionHeaderTextColor)
self.backgroundColor = theme.chatList.sectionHeaderFillColor
self.backgroundLayer.backgroundColor = theme.chatList.sectionHeaderFillColor.cgColor
if let action = self.action {
self.actionButtonLabel?.attributedText = NSAttributedString(string: action, font: actionFont, textColor: self.theme.chatList.sectionHeaderTextColor)
}
@ -126,6 +140,8 @@ public final class ListSectionHeaderNode: ASDisplayNode {
actionButtonLabel.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize)
actionButton.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize)
}
self.backgroundLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel))
}
@objc private func actionButtonPressed() {

View File

@ -75,7 +75,7 @@ public final class MeshWriteBuffer {
}
public func seek(offset: Int) {
self.file.seek(position: Int64(offset))
let _ = self.file.seek(position: Int64(offset))
self.offset = offset
}
}

View File

@ -99,12 +99,14 @@ public final class ManagedFile {
return result
}
public func seek(position: Int64) {
@discardableResult
public func seek(position: Int64) -> Bool {
if let queue = self.queue {
assert(queue.isCurrent())
}
assert(!self.isClosed)
lseek(self.fd, position, SEEK_SET)
let result = lseek(self.fd, position, SEEK_SET)
return result == position
}
public func truncate(count: Int64) {

View File

@ -139,7 +139,7 @@ public final class MediaBox {
private let statusQueue = Queue()
private let concurrentQueue = Queue.concurrentDefaultQueue()
private let dataQueue = Queue()
private let dataQueue = Queue(name: "MediaBox-Data")
private let dataFileManager: MediaBoxFileManager
private let cacheQueue = Queue()
private let timeBasedCleanup: TimeBasedCleanup
@ -209,6 +209,58 @@ public final class MediaBox {
let _ = self.ensureDirectoryCreated
//self.updateResourceIndex()
/*#if DEBUG
self.dataQueue.async {
for _ in 0 ..< 5 {
let tempFile = TempBox.shared.tempFile(fileName: "file")
print("MediaBox test: file \(tempFile.path)")
let queue2 = Queue.concurrentDefaultQueue()
if let fileContext = MediaBoxFileContextV2Impl(queue: self.dataQueue, manager: self.dataFileManager, storageBox: self.storageBox, resourceId: tempFile.path.data(using: .utf8)!, path: tempFile.path + "_complete", partialPath: tempFile.path + "_partial", metaPath: tempFile.path + "_partial" + ".meta") {
let _ = fileContext.fetched(
range: 0 ..< Int64.max,
priority: .default,
fetch: { ranges in
return ranges
|> filter { !$0.isEmpty }
|> take(1)
|> castError(MediaResourceDataFetchError.self)
|> mapToSignal { _ in
return Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError> { subscriber in
queue2.async {
subscriber.putNext(.resourceSizeUpdated(524288))
}
queue2.async {
subscriber.putNext(.resourceSizeUpdated(393216))
}
queue2.async {
subscriber.putNext(.resourceSizeUpdated(655360))
}
queue2.async {
subscriber.putNext(.resourceSizeUpdated(169608))
}
queue2.async {
subscriber.putNext(.dataPart(resourceOffset: 131072, data: Data(repeating: 0xbb, count: 38536), range: 0 ..< 38536, complete: true))
}
queue2.async {
subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(repeating: 0xaa, count: 131072), range: 0 ..< 131072, complete: false))
}
return EmptyDisposable
}
}
},
error: { _ in
},
completed: {
assert(try! Data(contentsOf: URL(fileURLWithPath: tempFile.path + "_complete")) == Data(repeating: 0xaa, count: 131072) + Data(repeating: 0xbb, count: 38536))
let _ = fileContext.addReference()
}
)
}
}
}
#endif*/
}
public func setMaxStoreTimes(general: Int32, shortLived: Int32, gigabytesLimit: Int32) {
@ -688,7 +740,7 @@ public final class MediaBox {
let clippedLowerBound = min(completeSize, max(0, range.lowerBound))
let clippedUpperBound = min(completeSize, max(0, range.upperBound))
if clippedLowerBound < clippedUpperBound && (clippedUpperBound - clippedLowerBound) <= 64 * 1024 * 1024 {
file.seek(position: clippedLowerBound)
let _ = file.seek(position: clippedLowerBound)
let data = file.readData(count: Int(clippedUpperBound - clippedLowerBound))
subscriber.putNext((data, true))
} else {
@ -725,7 +777,7 @@ public final class MediaBox {
subscriber.putNext((Data(), true))
subscriber.putCompletion()
} else if clippedUpperBound <= fileSize && (clippedUpperBound - clippedLowerBound) <= 64 * 1024 * 1024 {
file.seek(position: Int64(clippedLowerBound))
let _ = file.seek(position: Int64(clippedLowerBound))
let resultData = file.readData(count: Int(clippedUpperBound - clippedLowerBound))
subscriber.putNext((resultData, true))
subscriber.putCompletion()

View File

@ -88,7 +88,7 @@ final class MediaBoxPartialFile {
guard let clippedRange = fileMap.contains(range) else {
return nil
}
fd.seek(position: Int64(clippedRange.lowerBound))
let _ = fd.seek(position: Int64(clippedRange.lowerBound))
return fd.readData(count: Int(clippedRange.upperBound - clippedRange.lowerBound))
}
@ -227,7 +227,7 @@ final class MediaBoxPartialFile {
do {
try self.fd.access { fd in
fd.seek(position: offset)
let _ = fd.seek(position: offset)
let written = data.withUnsafeBytes { rawBytes -> Int in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
@ -330,7 +330,7 @@ final class MediaBoxPartialFile {
do {
var result: Data?
try self.fd.access { fd in
fd.seek(position: Int64(actualRange.lowerBound))
let _ = fd.seek(position: Int64(actualRange.lowerBound))
var data = Data(count: actualRange.count)
let dataCount = data.count
let readBytes = data.withUnsafeMutableBytes { rawBytes -> Int in

View File

@ -421,21 +421,28 @@ final class MediaBoxFileContextV2Impl: MediaBoxFileContext {
private func processWrite(resourceOffset: Int64, data: Data, dataRange: Range<Int64>) {
if let destinationFile = self.destinationFile {
do {
var success = true
try destinationFile.access { fd in
fd.seek(position: resourceOffset)
let written = data.withUnsafeBytes { rawBytes -> Int in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
return fd.write(bytes.advanced(by: Int(dataRange.lowerBound)), count: dataRange.count)
if fd.seek(position: resourceOffset) {
let written = data.withUnsafeBytes { rawBytes -> Int in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
return fd.write(bytes.advanced(by: Int(dataRange.lowerBound)), count: dataRange.count)
}
assert(written == dataRange.count)
} else {
success = false
}
assert(written == dataRange.count)
}
let range: Range<Int64> = resourceOffset ..< (resourceOffset + Int64(dataRange.count))
self.fileMap.fill(range)
self.fileMap.serialize(manager: self.manager, to: self.metaPath)
self.storageBox.update(id: self.resourceId, size: self.fileMap.sum)
if success {
let range: Range<Int64> = resourceOffset ..< (resourceOffset + Int64(dataRange.count))
self.fileMap.fill(range)
self.fileMap.serialize(manager: self.manager, to: self.metaPath)
self.storageBox.update(id: self.resourceId, size: self.fileMap.sum)
} else {
postboxLog("MediaBoxFileContextV2Impl: error seeking file to \(resourceOffset) at \(self.partialPath)")
}
} catch let e {
postboxLog("MediaBoxFileContextV2Impl: error writing file at \(self.partialPath): \(e)")
}

View File

@ -32,8 +32,8 @@ final class MediaBoxFileManager {
return self.file.readData(count: count)
}
func seek(position: Int64) {
self.file.seek(position: position)
func seek(position: Int64) -> Bool {
return self.file.seek(position: position)
}
}

View File

@ -203,7 +203,7 @@ final class MediaBoxFileMap {
}
let _ = try? fileItem.access { file in
file.seek(position: 0)
let _ = file.seek(position: 0)
let buffer = WriteBuffer()
var magic: UInt32 = 0x7bac1487
buffer.write(&magic, offset: 0, length: 4)

View File

@ -181,9 +181,17 @@ private func collectExternalShareItems(strings: PresentationStrings, dateTimeFor
case .progress:
return .progress
case let .done(data):
if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)), let image = UIImage(data: fileData) {
guard let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
return .progress
}
if let image = UIImage(data: fileData) {
return .done(.image(image))
} else {
#if DEBUG
if "".isEmpty {
return .done(.file(URL(fileURLWithPath: data.path), "image.bin", "application/octet-stream"))
}
#endif
return .progress
}
}

View File

@ -387,7 +387,7 @@ public func cacheVideoStickerFrames(path: String, size: CGSize, cacheKey: String
}
if frameCount > 0 {
file.seek(position: 4)
let _ = file.seek(position: 4)
let _ = file.write(&frameCount, count: 4)
}

View File

@ -647,8 +647,8 @@ private final class FetchImpl {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): setting known size to \(resultingSize)")
self.knownSize = resultingSize
}
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): reporting resource size \(fetchRange.lowerBound + actualLength)")
self.onNext(.resourceSizeUpdated(fetchRange.lowerBound + actualLength))
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): reporting resource size \(resultingSize)")
self.onNext(.resourceSizeUpdated(resultingSize))
}
state.completedRanges.formUnion(RangeSet<Int64>(partRange))

View File

@ -186,7 +186,7 @@ private final class MultipartUploadManager {
self.bigTotalParts = nil
} else {
self.bigParts = false
self.defaultPartSize = 16 * 1024
self.defaultPartSize = 128 * 1024
self.bigTotalParts = nil
}
}
@ -317,7 +317,7 @@ private final class MultipartUploadManager {
switch resourceData {
case let .resourceData(data):
if let file = ManagedFile(queue: nil, path: data.path, mode: .read) {
file.seek(position: Int64(partOffset))
let _ = file.seek(position: Int64(partOffset))
let data = file.readData(count: Int(partSize))
if data.count == partSize {
partData = data

View File

@ -172,6 +172,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case sendWhenOnlineTip = 38
case chatWallpaperLightPreviewTip = 39
case chatWallpaperDarkPreviewTip = 40
case displayChatListContacts = 41
var key: ValueBoxKey {
let v = ValueBoxKey(length: 4)
@ -389,6 +390,10 @@ private struct ApplicationSpecificNoticeKeys {
static func sendWhenOnlineTip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sendWhenOnlineTip.key)
}
static func displayChatListContacts() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListContacts.key)
}
}
public struct ApplicationSpecificNotice {
@ -1420,6 +1425,26 @@ public struct ApplicationSpecificNotice {
}
}
public static func displayChatListContacts(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.displayChatListContacts())
|> map { view -> Bool in
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
return true
} else {
return false
}
}
}
public static func setDisplayChatListContacts(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
return accountManager.transaction { transaction -> Void in
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
transaction.setNotice(ApplicationSpecificNoticeKeys.displayChatListContacts(), entry)
}
}
|> ignoreValues
}
public static func reset(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Void, NoError> {
return accountManager.transaction { transaction -> Void in
}

View File

@ -605,10 +605,10 @@ private final class AnimationCacheItemWriterImpl: AnimationCacheItemWriter {
let metadataPosition = file.position()
let contentLength = Int(metadataPosition) - contentLengthOffset - 4
file.seek(position: Int64(contentLengthOffset))
let _ = file.seek(position: Int64(contentLengthOffset))
file.write(UInt32(contentLength))
file.seek(position: metadataPosition)
let _ = file.seek(position: metadataPosition)
file.write(UInt32(self.frames.count))
for frame in self.frames {
file.write(Float32(frame.duration))

View File

@ -662,7 +662,21 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
)
}
let readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: messages[0].id.peerId))
let readCounters: Signal<Bool, NoError>
if case let .replyThread(threadMessage) = chatPresentationInterfaceState.chatLocation, threadMessage.isForumPost {
readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ThreadData(id: threadMessage.messageId.peerId, threadId: Int64(threadMessage.messageId.id)))
|> map { threadData -> Bool in
guard let threadData else {
return false
}
return threadData.maxOutgoingReadId >= message.id.id
}
} else {
readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: messages[0].id.peerId))
|> map { readCounters -> Bool in
return readCounters.isOutgoingMessageIndexRead(message.index)
}
}
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?), NoError> = combineLatest(
loadLimits,
@ -679,15 +693,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
context.engine.peers.notificationSoundList() |> take(1),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
)
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, readCounters, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in
|> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, isMessageRead, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in
let (limitsConfiguration, appConfig) = limitsAndAppConfig
var canEdit = false
if !isAction {
let message = messages[0]
canEdit = canEditMessage(context: context, limitsConfiguration: limitsConfiguration, message: message)
}
let isMessageRead = readCounters.isOutgoingMessageIndexRead(message.index)
let translationSettings: TranslationSettings
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {

View File

@ -324,7 +324,14 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}
})
} else {
let query = to.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789").inverted)
let _ = (context.engine.peers.resolvePeerByPhone(phone: to)
|> deliverOnMainQueue).start(next: { peer in
if let peer = peer {
context.sharedContext.applicationBindings.dismissNativeController()
continueWithPeer(peer.id)
}
})
/*let query = to.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789").inverted)
let _ = (context.account.postbox.searchContacts(query: query)
|> deliverOnMainQueue).start(next: { (peers, _) in
for case let peer as TelegramUser in peers {
@ -334,7 +341,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
break
}
}
})
})*/
}
} else {
if let url = url, !url.isEmpty {

View File

@ -194,6 +194,22 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
if let phone = phone, let hash = hash {
return .cancelAccountReset(phone: phone, hash: hash)
}
} else if peerName == "msg" {
var url: String?
var text: String?
var to: String?
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "url" {
url = value
} else if queryItem.name == "text" {
text = value
} else if queryItem.name == "to" {
to = value
}
}
}
return .share(url: url, text: text, to: to)
} else {
for queryItem in queryItems {
if let value = queryItem.value {