Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
@ -8609,3 +8609,16 @@ Sorry for the inconvenience.";
|
||||
"GroupMembers.HideMembers" = "Hide Members";
|
||||
"GroupMembers.MembersHiddenOn" = "Switch this off to show the list of members in this group.";
|
||||
"GroupMembers.MembersHiddenOff" = "Switch this on to hide the list of members in this group. Admins will remain visible.";
|
||||
|
||||
"StorageManagement.ClearCache" = "Clear Cache";
|
||||
|
||||
"ChatList.StorageHintTitle" = "Free up to %@";
|
||||
"ChatList.StorageHintText" = "Clear storage space on your iPhone";
|
||||
|
||||
"StorageManagement.PeerShowDetails" = "Show Details";
|
||||
"StorageManagement.PeerOpenProfile" = "Open Profile";
|
||||
"StorageManagement.ContextSelect" = "Select";
|
||||
"StorageManagement.ContextDeselect" = "Deselect";
|
||||
"StorageManagement.OpenPhoto" = "Open Photo";
|
||||
"StorageManagement.OpenVideo" = "Open Video";
|
||||
"StorageManagement.OpenFile" = "Open File";
|
||||
|
@ -791,6 +791,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeCreateGroupController(context: AccountContext, peerIds: [PeerId], initialTitle: String?, mode: CreateGroupMode, completion: ((PeerId, @escaping () -> Void) -> Void)?) -> ViewController
|
||||
func makeChatRecentActionsController(context: AccountContext, peer: Peer, adminPeerId: PeerId?) -> ViewController
|
||||
func makePrivacyAndSecurityController(context: AccountContext) -> ViewController
|
||||
func makeStorageManagementController(context: AccountContext) -> ViewController
|
||||
func navigateToChatController(_ params: NavigateToChatControllerParams)
|
||||
func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController)
|
||||
func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError>
|
||||
|
@ -185,7 +185,7 @@ private final class ChatListShimmerNode: ASDisplayNode {
|
||||
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in }, openForumThread: { _, _ in })
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {})
|
||||
interaction.isInlineMode = isInlineMode
|
||||
|
||||
let items = (0 ..< 2).map { _ -> ChatListItem in
|
||||
|
@ -2004,6 +2004,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
interaction.openPeer(peer, peer, threadId, false)
|
||||
self.listNode.clearHighlightAnimated(true)
|
||||
})
|
||||
}, openStorageManagement: {
|
||||
})
|
||||
chatListInteraction.isSearchMode = true
|
||||
|
||||
@ -3205,7 +3206,7 @@ private final class ChatListSearchShimmerNode: ASDisplayNode {
|
||||
let interaction = ChatListNodeInteraction(context: context, animationCache: animationCache, animationRenderer: animationRenderer, activateSearch: {}, peerSelected: { _, _, _, _ in }, disabledPeerSelected: { _, _ in }, togglePeerSelected: { _, _ in }, togglePeersSelection: { _, _ in }, additionalCategorySelected: { _ in
|
||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in }, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in }, openForumThread: { _, _ in })
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {})
|
||||
var isInlineMode = false
|
||||
if case .topics = key {
|
||||
isInlineMode = false
|
||||
|
@ -91,6 +91,7 @@ public final class ChatListNodeInteraction {
|
||||
let activateChatPreview: (ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void
|
||||
let present: (ViewController) -> Void
|
||||
let openForumThread: (EnginePeer.Id, Int64) -> Void
|
||||
let openStorageManagement: () -> Void
|
||||
|
||||
public var searchTextHighightState: String?
|
||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||
@ -132,7 +133,8 @@ public final class ChatListNodeInteraction {
|
||||
hidePsa: @escaping (EnginePeer.Id) -> Void,
|
||||
activateChatPreview: @escaping (ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void,
|
||||
present: @escaping (ViewController) -> Void,
|
||||
openForumThread: @escaping (EnginePeer.Id, Int64) -> Void
|
||||
openForumThread: @escaping (EnginePeer.Id, Int64) -> Void,
|
||||
openStorageManagement: @escaping () -> Void
|
||||
) {
|
||||
self.activateSearch = activateSearch
|
||||
self.peerSelected = peerSelected
|
||||
@ -162,6 +164,7 @@ public final class ChatListNodeInteraction {
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.openForumThread = openForumThread
|
||||
self.openStorageManagement = openStorageManagement
|
||||
}
|
||||
}
|
||||
|
||||
@ -567,6 +570,10 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
), 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 .StorageInfo(presentationData, sizeFraction):
|
||||
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, sizeFraction: sizeFraction, action: { [weak nodeInteraction] in
|
||||
nodeInteraction?.openStorageManagement()
|
||||
}), directionHint: entry.directionHint)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -778,6 +785,10 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
|
||||
), 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 .StorageInfo(presentationData, sizeFraction):
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, sizeFraction: sizeFraction, action: { [weak nodeInteraction] in
|
||||
nodeInteraction?.openStorageManagement()
|
||||
}), directionHint: entry.directionHint)
|
||||
case .HeaderEntry:
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
|
||||
case let .AdditionalCategory(index: _, id, title, image, appearance, selected, presentationData):
|
||||
@ -1258,6 +1269,12 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
self.peerSelected?(peer, threadId, true, true, nil)
|
||||
})
|
||||
}, openStorageManagement: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = self.context.sharedContext.makeStorageManagementController(context: self.context)
|
||||
self.push?(controller)
|
||||
})
|
||||
nodeInteraction.isInlineMode = isInlineMode
|
||||
|
||||
@ -1323,15 +1340,94 @@ public final class ChatListNode: ListView {
|
||||
displayArchiveIntro = .single(false)
|
||||
}
|
||||
|
||||
let storageInfo: Signal<Double?, NoError>
|
||||
if "".isEmpty, case .chatList(groupId: .root) = location, chatListFilter == nil {
|
||||
let storageBox = context.account.postbox.mediaBox.storageBox
|
||||
storageInfo = storageBox.totalSize()
|
||||
|> take(1)
|
||||
|> mapToSignal { initialSize -> Signal<Double?, NoError> in
|
||||
let fractionLimit: Double = 0.3
|
||||
|
||||
let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
|
||||
let deviceFreeSpace = (systemAttributes?[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
|
||||
|
||||
let initialFraction: Double
|
||||
if deviceFreeSpace != 0 && initialSize != 0 {
|
||||
initialFraction = Double(initialSize) / Double(deviceFreeSpace + initialSize)
|
||||
} else {
|
||||
initialFraction = 0.0
|
||||
}
|
||||
|
||||
let initialReportSize: Double?
|
||||
if initialFraction > fractionLimit {
|
||||
initialReportSize = Double(initialSize)
|
||||
} else {
|
||||
initialReportSize = nil
|
||||
}
|
||||
|
||||
final class ReportState {
|
||||
var lastSize: Int64
|
||||
|
||||
init(lastSize: Int64) {
|
||||
self.lastSize = lastSize
|
||||
}
|
||||
}
|
||||
|
||||
let state = Atomic(value: ReportState(lastSize: initialSize))
|
||||
let updatedReportSize: Signal<Double?, NoError> = Signal { subscriber in
|
||||
let disposable = storageBox.totalSize().start(next: { size in
|
||||
let updatedSize = state.with { state -> Int64 in
|
||||
if abs(initialSize - size) > 50 * 1024 * 1024 {
|
||||
state.lastSize = size
|
||||
return size
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
if updatedSize >= 0 {
|
||||
let deviceFreeSpace = (systemAttributes?[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value ?? 0
|
||||
|
||||
let updatedFraction: Double
|
||||
if deviceFreeSpace != 0 && updatedSize != 0 {
|
||||
updatedFraction = Double(updatedSize) / Double(deviceFreeSpace + updatedSize)
|
||||
} else {
|
||||
updatedFraction = 0.0
|
||||
}
|
||||
|
||||
let updatedReportSize: Double?
|
||||
if updatedFraction > fractionLimit {
|
||||
updatedReportSize = Double(updatedSize)
|
||||
} else {
|
||||
updatedReportSize = nil
|
||||
}
|
||||
|
||||
subscriber.putNext(updatedReportSize)
|
||||
}
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
disposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
return .single(initialReportSize)
|
||||
|> then(
|
||||
updatedReportSize
|
||||
)
|
||||
}
|
||||
} else {
|
||||
storageInfo = .single(nil)
|
||||
}
|
||||
|
||||
let currentPeerId: EnginePeer.Id = context.account.peerId
|
||||
|
||||
let chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue, hideArchivedFolderByDefault, displayArchiveIntro, savedMessagesPeer, chatListViewUpdate, self.statePromise.get())
|
||||
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, savedMessagesPeer, updateAndFilter, state) -> Signal<ChatListNodeListViewTransition, NoError> in
|
||||
let chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue, hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, chatListViewUpdate, self.statePromise.get())
|
||||
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, savedMessagesPeer, updateAndFilter, state) -> Signal<ChatListNodeListViewTransition, NoError> in
|
||||
let (update, filter) = updateAndFilter
|
||||
|
||||
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
|
||||
|
||||
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, mode: mode, chatListLocation: location)
|
||||
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, storageInfo: storageInfo, mode: mode, chatListLocation: location)
|
||||
let entries = rawEntries.filter { entry in
|
||||
switch entry {
|
||||
case let .PeerEntry(peerEntry):
|
||||
@ -2361,7 +2457,7 @@ public final class ChatListNode: ListView {
|
||||
} else {
|
||||
break loop
|
||||
}
|
||||
case .ArchiveIntro, .HeaderEntry, .AdditionalCategory:
|
||||
case .ArchiveIntro, .StorageInfo, .HeaderEntry, .AdditionalCategory:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ enum ChatListNodeEntryId: Hashable {
|
||||
case ThreadId(Int64)
|
||||
case GroupId(EngineChatList.Group)
|
||||
case ArchiveIntro
|
||||
case StorageInfo
|
||||
case additionalCategory(Int)
|
||||
}
|
||||
|
||||
@ -234,6 +235,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
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 ArchiveIntro(presentationData: ChatListPresentationData)
|
||||
case StorageInfo(presentationData: ChatListPresentationData, sizeFraction: Double)
|
||||
case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData)
|
||||
|
||||
var sortIndex: ChatListNodeEntrySortIndex {
|
||||
@ -248,6 +250,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
return .index(index)
|
||||
case .ArchiveIntro:
|
||||
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
|
||||
case .StorageInfo:
|
||||
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor.successor))
|
||||
case let .AdditionalCategory(index, _, _, _, _, _, _):
|
||||
return .additionalCategory(index)
|
||||
}
|
||||
@ -270,6 +274,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
return .GroupId(groupId)
|
||||
case .ArchiveIntro:
|
||||
return .ArchiveIntro
|
||||
case .StorageInfo:
|
||||
return .StorageInfo
|
||||
case let .AdditionalCategory(_, id, _, _, _, _, _):
|
||||
return .additionalCategory(id)
|
||||
}
|
||||
@ -342,6 +348,18 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .StorageInfo(lhsPresentationData, lhsInfo):
|
||||
if case let .StorageInfo(rhsPresentationData, rhsInfo) = rhs {
|
||||
if lhsPresentationData !== rhsPresentationData {
|
||||
return false
|
||||
}
|
||||
if lhsInfo != rhsInfo {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .AdditionalCategory(lhsIndex, lhsId, lhsTitle, lhsImage, lhsAppearance, lhsSelected, lhsPresentationData):
|
||||
if case let .AdditionalCategory(rhsIndex, rhsId, rhsTitle, rhsImage, rhsAppearance, rhsSelected, rhsPresentationData) = rhs {
|
||||
if lhsIndex != rhsIndex {
|
||||
@ -381,7 +399,7 @@ private func offsetPinnedIndex(_ index: EngineChatList.Item.Index, offset: UInt1
|
||||
}
|
||||
}
|
||||
|
||||
func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation) -> (entries: [ChatListNodeEntry], loading: Bool) {
|
||||
func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, storageInfo: Double?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation) -> (entries: [ChatListNodeEntry], loading: Bool) {
|
||||
var result: [ChatListNodeEntry] = []
|
||||
|
||||
var pinnedIndexOffset: UInt16 = 0
|
||||
@ -643,6 +661,9 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
|
||||
if displayArchiveIntro {
|
||||
result.append(.ArchiveIntro(presentationData: state.presentationData))
|
||||
}
|
||||
if let storageInfo {
|
||||
result.append(.StorageInfo(presentationData: state.presentationData, sizeFraction: storageInfo))
|
||||
}
|
||||
|
||||
result.append(.HeaderEntry)
|
||||
}
|
||||
|
164
submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift
Normal file
@ -0,0 +1,164 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import ListSectionHeaderNode
|
||||
import AppBundle
|
||||
|
||||
class ChatListStorageInfoItem: ListViewItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let sizeFraction: Double
|
||||
let action: () -> Void
|
||||
|
||||
let selectable: Bool = true
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, sizeFraction: Double, action: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.sizeFraction = sizeFraction
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func selected(listView: ListView) {
|
||||
listView.clearHighlightAnimated(true)
|
||||
|
||||
self.action()
|
||||
}
|
||||
|
||||
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 = ChatListStorageInfoItemNode()
|
||||
|
||||
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 ChatListStorageInfoItemNode)
|
||||
if let nodeValue = node() as? ChatListStorageInfoItemNode {
|
||||
|
||||
let layout = nodeValue.asyncLayout()
|
||||
async {
|
||||
let (nodeLayout, apply) = layout(self, params, nextItem == nil)
|
||||
Queue.mainQueue().async {
|
||||
completion(nodeLayout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let separatorHeight = 1.0 / UIScreen.main.scale
|
||||
|
||||
private let titleFont = Font.semibold(15.0)
|
||||
private let textFont = Font.regular(15.0)
|
||||
|
||||
class ChatListStorageInfoItemNode: ListViewItemNode {
|
||||
private let titleNode: TextNode
|
||||
private let textNode: TextNode
|
||||
private let arrowNode: ASImageNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
private var item: ChatListStorageInfoItem?
|
||||
|
||||
required init() {
|
||||
self.titleNode = TextNode()
|
||||
self.textNode = TextNode()
|
||||
self.arrowNode = ASImageNode()
|
||||
self.separatorNode = ASDisplayNode()
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
}
|
||||
|
||||
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! ChatListStorageInfoItem, params, nextItem == nil)
|
||||
apply()
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ChatListStorageInfoItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let previousItem = self.item
|
||||
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
|
||||
return { item, params, last in
|
||||
let baseWidth = params.width - params.leftInset - params.rightInset
|
||||
let _ = baseWidth
|
||||
|
||||
let sideInset: CGFloat = params.leftInset + 16.0
|
||||
let height: CGFloat = 54.0
|
||||
let rightInset: CGFloat = sideInset + 24.0
|
||||
|
||||
let themeUpdated = item.theme !== previousItem?.theme
|
||||
|
||||
let sizeString = dataSizeString(Int64(item.sizeFraction), formatting: DataSizeStringFormatting(strings: item.strings, decimalSeparator: "."))
|
||||
let rawTitleString = item.strings.ChatList_StorageHintTitle(sizeString)
|
||||
let titleString = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString.string, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor))
|
||||
if let range = rawTitleString.ranges.first {
|
||||
titleString.addAttribute(.foregroundColor, value: item.theme.rootController.navigationBar.accentTextColor, range: range.range)
|
||||
}
|
||||
|
||||
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
|
||||
let textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.ChatList_StorageHintText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor), maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: height), insets: UIEdgeInsets())
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
if themeUpdated {
|
||||
strongSelf.backgroundColor = item.theme.chatList.pinnedItemBackgroundColor
|
||||
strongSelf.separatorNode.backgroundColor = item.theme.chatList.itemSeparatorColor
|
||||
strongSelf.arrowNode.image = PresentationResourcesItemList.disclosureArrowImage(item.theme)
|
||||
}
|
||||
|
||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel))
|
||||
|
||||
let _ = titleLayout.1()
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: sideInset, y: 9.0), size: titleLayout.0.size)
|
||||
|
||||
let _ = textLayout.1()
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: sideInset, y: strongSelf.titleNode.frame.maxY - 0.0), size: textLayout.0.size)
|
||||
|
||||
if let image = strongSelf.arrowNode.image {
|
||||
strongSelf.arrowNode.frame = CGRect(origin: CGPoint(x: layout.size.width - sideInset - image.size.width + 8.0, y: floor((layout.size.height - image.size.height) / 2.0)), size: image.size)
|
||||
}
|
||||
|
||||
strongSelf.contentSize = layout.contentSize
|
||||
strongSelf.insets = layout.insets
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -91,6 +91,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
||||
gesture?.cancel()
|
||||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in
|
||||
}, openStorageManagement: {
|
||||
})
|
||||
|
||||
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
|
||||
|
@ -193,24 +193,24 @@
|
||||
}];
|
||||
[itemViews addObject:galleryItem];
|
||||
|
||||
if (_hasSearchButton)
|
||||
{
|
||||
TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SearchWeb") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
||||
{
|
||||
__strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||
if (strongSelf == nil)
|
||||
return;
|
||||
|
||||
__strong TGMenuSheetController *strongController = weakController;
|
||||
if (strongController == nil)
|
||||
return;
|
||||
|
||||
[strongController dismissAnimated:true];
|
||||
if (strongSelf != nil)
|
||||
strongSelf.requestSearchController(nil);
|
||||
}];
|
||||
[itemViews addObject:viewItem];
|
||||
}
|
||||
// if (_hasSearchButton)
|
||||
// {
|
||||
// TGMenuSheetButtonItemView *viewItem = [[TGMenuSheetButtonItemView alloc] initWithTitle:TGLocalized(@"ProfilePhoto.SearchWeb") type:TGMenuSheetButtonTypeDefault fontSize:20.0 action:^
|
||||
// {
|
||||
// __strong TGMediaAvatarMenuMixin *strongSelf = weakSelf;
|
||||
// if (strongSelf == nil)
|
||||
// return;
|
||||
//
|
||||
// __strong TGMenuSheetController *strongController = weakController;
|
||||
// if (strongController == nil)
|
||||
// return;
|
||||
//
|
||||
// [strongController dismissAnimated:true];
|
||||
// if (strongSelf != nil)
|
||||
// strongSelf.requestSearchController(nil);
|
||||
// }];
|
||||
// [itemViews addObject:viewItem];
|
||||
// }
|
||||
|
||||
if (_hasViewButton)
|
||||
{
|
||||
|
@ -158,6 +158,9 @@ public final class StorageBox {
|
||||
let contentTypeStatsTable: ValueBoxTable
|
||||
let metadataTable: ValueBoxTable
|
||||
|
||||
let totalSizeSubscribers = Bag<(Int64) -> Void>()
|
||||
private var totalSize: Int64 = 0
|
||||
|
||||
init(queue: Queue, logger: StorageBox.Logger, basePath: String) {
|
||||
self.queue = queue
|
||||
self.logger = logger
|
||||
@ -183,6 +186,8 @@ public final class StorageBox {
|
||||
self.metadataTable = ValueBoxTable(id: 21, keyType: .binary, compactValuesOnCreation: true)
|
||||
|
||||
self.performUpdatesIfNeeded()
|
||||
|
||||
self.updateTotalSize()
|
||||
}
|
||||
|
||||
private func performUpdatesIfNeeded() {
|
||||
@ -230,6 +235,35 @@ public final class StorageBox {
|
||||
})
|
||||
}
|
||||
|
||||
func updateTotalSize() {
|
||||
self.valueBox.begin()
|
||||
|
||||
var totalSize: Int64 = 0
|
||||
self.valueBox.scan(self.contentTypeStatsTable, values: { key, value in
|
||||
var size: Int64 = 0
|
||||
value.read(&size, offset: 0, length: 8)
|
||||
totalSize += size
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
self.valueBox.commit()
|
||||
|
||||
if self.totalSize != totalSize {
|
||||
self.totalSize = totalSize
|
||||
|
||||
for f in self.totalSizeSubscribers.copyItems() {
|
||||
f(totalSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func incrementalUpdateTotalSize() {
|
||||
for f in self.totalSizeSubscribers.copyItems() {
|
||||
f(totalSize)
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
self.valueBox.begin()
|
||||
|
||||
@ -241,6 +275,8 @@ public final class StorageBox {
|
||||
self.valueBox.removeAllFromTable(self.metadataTable)
|
||||
|
||||
self.valueBox.commit()
|
||||
|
||||
self.updateTotalSize()
|
||||
}
|
||||
|
||||
private func internalAddSize(contentType: UInt8, delta: Int64) {
|
||||
@ -260,6 +296,8 @@ public final class StorageBox {
|
||||
}
|
||||
|
||||
self.valueBox.set(self.contentTypeStatsTable, key: key, value: MemoryBuffer(memory: ¤tSize, capacity: 8, length: 8, freeWhenDone: false))
|
||||
|
||||
self.totalSize += delta
|
||||
}
|
||||
|
||||
private func internalAddSize(peerId: Int64, contentType: UInt8, delta: Int64) {
|
||||
@ -390,6 +428,8 @@ public final class StorageBox {
|
||||
}
|
||||
|
||||
self.valueBox.commit()
|
||||
|
||||
self.incrementalUpdateTotalSize()
|
||||
}
|
||||
|
||||
private func peerIdsReferencing(hashId: HashId) -> Set<Int64> {
|
||||
@ -435,6 +475,8 @@ public final class StorageBox {
|
||||
}
|
||||
|
||||
self.valueBox.commit()
|
||||
|
||||
self.incrementalUpdateTotalSize()
|
||||
}
|
||||
|
||||
func addEmptyReferencesIfNotReferenced(ids: [(id: Data, size: Int64)], contentType: UInt8) -> Int {
|
||||
@ -508,6 +550,8 @@ public final class StorageBox {
|
||||
}
|
||||
|
||||
self.valueBox.commit()
|
||||
|
||||
self.incrementalUpdateTotalSize()
|
||||
}
|
||||
|
||||
func allPeerIds() -> [PeerId] {
|
||||
@ -615,6 +659,22 @@ public final class StorageBox {
|
||||
return (ids, nextId)
|
||||
}
|
||||
|
||||
func subscribeTotalSize(next: @escaping (Int64) -> Void) -> Disposable {
|
||||
let index = self.totalSizeSubscribers.add(next)
|
||||
|
||||
next(self.totalSize)
|
||||
|
||||
let queue = self.queue
|
||||
return ActionDisposable { [weak self] in
|
||||
queue.async {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.totalSizeSubscribers.remove(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func all() -> [Entry] {
|
||||
var result: [Entry] = []
|
||||
|
||||
@ -825,6 +885,8 @@ public final class StorageBox {
|
||||
|
||||
self.valueBox.commit()
|
||||
|
||||
self.incrementalUpdateTotalSize()
|
||||
|
||||
return Array(resultIds)
|
||||
}
|
||||
|
||||
@ -854,6 +916,8 @@ public final class StorageBox {
|
||||
|
||||
self.valueBox.commit()
|
||||
|
||||
self.incrementalUpdateTotalSize()
|
||||
|
||||
return Array(resultIds)
|
||||
}
|
||||
}
|
||||
@ -973,4 +1037,12 @@ public final class StorageBox {
|
||||
return EmptyDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public func totalSize() -> Signal<Int64, NoError> {
|
||||
return self.impl.signalWith { impl, subscriber in
|
||||
return impl.subscribeTotalSize(next: { value in
|
||||
subscriber.putNext(value)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -598,7 +598,6 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
|
||||
pushControllerImpl?(StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { category in
|
||||
return storageUsageExceptionsScreen(context: context, category: category)
|
||||
}))
|
||||
//pushControllerImpl?(storageUsageController(context: context, cacheUsagePromise: cacheUsagePromise))
|
||||
}, openNetworkUsage: {
|
||||
pushControllerImpl?(networkUsageStatsController(context: context))
|
||||
}, openProxy: {
|
||||
|
@ -222,7 +222,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
|
||||
}, messageSelected: { _, _, _, _ in}, groupSelected: { _ in }, addContact: { _ in }, setPeerIdWithRevealedOptions: { _, _ in }, setItemPinned: { _, _ in }, setPeerMuted: { _, _ in }, setPeerThreadMuted: { _, _, _ in }, deletePeer: { _, _ in }, deletePeerThread: { _, _ in }, setPeerThreadStopped: { _, _, _ in }, setPeerThreadPinned: { _, _, _ in }, setPeerThreadHidden: { _, _, _ in }, updatePeerGrouping: { _, _ in }, togglePeerMarkedUnread: { _, _ in}, toggleArchivedFolderHiddenByDefault: {}, toggleThreadsSelection: { _, _ in }, hidePsa: { _ in
|
||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in }, openForumThread: { _, _ in })
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {})
|
||||
|
||||
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
|
||||
|
@ -843,7 +843,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in })
|
||||
}, openForumThread: { _, _ in },
|
||||
openStorageManagement: {})
|
||||
let chatListPresentationData = ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
|
||||
func makeChatListItem(
|
||||
|
@ -367,7 +367,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in })
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {})
|
||||
|
||||
func makeChatListItem(
|
||||
peer: EnginePeer,
|
||||
|
@ -20,6 +20,7 @@ enum PendingMessageReuploadInfo {
|
||||
struct PendingMessageUploadedContentAndReuploadInfo {
|
||||
let content: PendingMessageUploadedContent
|
||||
let reuploadInfo: PendingMessageReuploadInfo?
|
||||
let cacheReferenceKey: CachedSentMediaReferenceKey?
|
||||
}
|
||||
|
||||
enum PendingMessageUploadedContentResult {
|
||||
@ -81,25 +82,25 @@ func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods
|
||||
}
|
||||
|
||||
if let media = media.first as? TelegramMediaAction, media.action == .historyScreenshot {
|
||||
return .immediate(.content(PendingMessageUploadedContentAndReuploadInfo(content: .messageScreenshot, reuploadInfo: nil)), .none)
|
||||
return .immediate(.content(PendingMessageUploadedContentAndReuploadInfo(content: .messageScreenshot, reuploadInfo: nil, cacheReferenceKey: nil)), .none)
|
||||
} else if let forwardInfo = forwardInfo {
|
||||
return .immediate(.content(PendingMessageUploadedContentAndReuploadInfo(content: .forward(forwardInfo), reuploadInfo: nil)), .text)
|
||||
return .immediate(.content(PendingMessageUploadedContentAndReuploadInfo(content: .forward(forwardInfo), reuploadInfo: nil, cacheReferenceKey: nil)), .text)
|
||||
} else if let contextResult = contextResult {
|
||||
return .immediate(.content(PendingMessageUploadedContentAndReuploadInfo(content: .chatContextResult(contextResult), reuploadInfo: nil)), .text)
|
||||
return .immediate(.content(PendingMessageUploadedContentAndReuploadInfo(content: .chatContextResult(contextResult), reuploadInfo: nil, cacheReferenceKey: nil)), .text)
|
||||
} else if let media = media.first, let mediaResult = mediaContentToUpload(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, isGrouped: isGrouped, peerId: peerId, media: media, text: text, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute, messageId: messageId, attributes: attributes) {
|
||||
return .signal(mediaResult, .media)
|
||||
} else {
|
||||
return .signal(.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil))), .text)
|
||||
return .signal(.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil, cacheReferenceKey: nil))), .text)
|
||||
}
|
||||
}
|
||||
|
||||
func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, isGrouped: Bool, peerId: PeerId, media: Media, text: String, autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?, messageId: MessageId?, attributes: [MessageAttribute]) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>? {
|
||||
if let image = media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) {
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat, let resource = largest.resource as? SecretFileMediaResource {
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(.inputEncryptedFile(id: resource.fileId, accessHash: resource.accessHash), resource.decryptedSize, resource.key), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(.inputEncryptedFile(id: resource.fileId, accessHash: resource.accessHash), resource.decryptedSize, resource.key), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
}
|
||||
if peerId.namespace != Namespaces.Peer.SecretChat, let reference = image.reference, case let .cloud(id, accessHash, maybeFileReference) = reference, let fileReference = maybeFileReference {
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaPhoto(flags: 0, id: Api.InputPhoto.inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil), text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaPhoto(flags: 0, id: Api.InputPhoto.inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil), text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
} else {
|
||||
return uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, forceReupload: forceReupload, isGrouped: isGrouped, peerId: peerId, image: image, messageId: messageId, text: text, attributes: attributes, autoremoveMessageAttribute: autoremoveMessageAttribute, autoclearMessageAttribute: autoclearMessageAttribute)
|
||||
}
|
||||
@ -109,7 +110,7 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods:
|
||||
for attribute in file.attributes {
|
||||
if case let .Sticker(_, packReferenceValue, _) = attribute {
|
||||
if let _ = packReferenceValue {
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: PendingMessageUploadedContent.text(text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: PendingMessageUploadedContent.text(text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,7 +129,7 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods:
|
||||
}
|
||||
|> mapToSignal { validatedResource -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
if let validatedResource = validatedResource.updatedResource as? TelegramCloudMediaResourceWithFileReference, let reference = validatedResource.fileReference {
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: reference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: reference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
@ -144,14 +145,14 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods:
|
||||
}
|
||||
}
|
||||
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: emojiSearchQuery), text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil, query: emojiSearchQuery), text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
}
|
||||
} else {
|
||||
return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, forceReupload: forceReupload, isGrouped: isGrouped, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file)
|
||||
}
|
||||
} else if let contact = media as? TelegramMediaContact {
|
||||
let input = Api.InputMedia.inputMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, vcard: contact.vCardData ?? "")
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
} else if let map = media as? TelegramMediaMap {
|
||||
let input: Api.InputMedia
|
||||
var flags: Int32 = 1 << 1
|
||||
@ -172,7 +173,7 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods:
|
||||
} else {
|
||||
input = .inputMediaGeoPoint(geoPoint: Api.InputGeoPoint.inputGeoPoint(flags: geoFlags, lat: map.latitude, long: map.longitude, accuracyRadius: map.accuracyRadius.flatMap({ Int32($0) })))
|
||||
}
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
} else if let poll = media as? TelegramMediaPoll {
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
return .fail(.generic)
|
||||
@ -209,10 +210,10 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods:
|
||||
pollMediaFlags |= 1 << 1
|
||||
}
|
||||
let inputPoll = Api.InputMedia.inputMediaPoll(flags: pollMediaFlags, poll: Api.Poll.poll(id: 0, flags: pollFlags, question: poll.text, answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities)
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
} else if let media = media as? TelegramMediaDice {
|
||||
let input = Api.InputMedia.inputMediaDice(emoticon: media.emoji)
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -220,7 +221,7 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods:
|
||||
|
||||
private enum PredownloadedResource {
|
||||
case localReference(CachedSentMediaReferenceKey?)
|
||||
case media(Media)
|
||||
case media(Media, CachedSentMediaReferenceKey?)
|
||||
case none
|
||||
}
|
||||
|
||||
@ -256,7 +257,7 @@ private func maybePredownloadedImageResource(postbox: Postbox, peerId: PeerId, r
|
||||
subscriber.putNext(cachedSentMediaReference(postbox: postbox, key: reference)
|
||||
|> mapError { _ -> PendingMessageUploadError in } |> map { media -> PredownloadedResource in
|
||||
if let media = media {
|
||||
return .media(media)
|
||||
return .media(media, reference)
|
||||
} else {
|
||||
return .localReference(reference)
|
||||
}
|
||||
@ -295,7 +296,7 @@ private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods:
|
||||
}
|
||||
return cachedSentMediaReference(postbox: postbox, key: reference) |> map { media -> PredownloadedResource in
|
||||
if let media = media {
|
||||
return .media(media)
|
||||
return .media(media, reference)
|
||||
} else {
|
||||
return .localReference(reference)
|
||||
}
|
||||
@ -320,7 +321,7 @@ private func maybeCacheUploadedResource(postbox: Postbox, key: CachedSentMediaRe
|
||||
|
||||
private func uploadedMediaImageContent(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, forceReupload: Bool, isGrouped: Bool, peerId: PeerId, image: TelegramMediaImage, messageId: MessageId?, text: String, attributes: [MessageAttribute], autoremoveMessageAttribute: AutoremoveTimeoutMessageAttribute?, autoclearMessageAttribute: AutoclearTimeoutMessageAttribute?) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
|
||||
guard let largestRepresentation = largestImageRepresentation(image.representations) else {
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
}
|
||||
|
||||
let predownloadedResource: Signal<PredownloadedResource, PendingMessageUploadError> = maybePredownloadedImageResource(postbox: postbox, peerId: peerId, resource: largestRepresentation.resource, forceRefresh: forceReupload)
|
||||
@ -328,7 +329,7 @@ private func uploadedMediaImageContent(network: Network, postbox: Postbox, trans
|
||||
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
var referenceKey: CachedSentMediaReferenceKey?
|
||||
switch result {
|
||||
case let .media(media):
|
||||
case let .media(media, key):
|
||||
if !forceReupload, let image = media as? TelegramMediaImage, let reference = image.reference, case let .cloud(id, accessHash, maybeFileReference) = reference, let fileReference = maybeFileReference {
|
||||
var flags: Int32 = 0
|
||||
var ttlSeconds: Int32?
|
||||
@ -336,11 +337,18 @@ private func uploadedMediaImageContent(network: Network, postbox: Postbox, trans
|
||||
flags |= 1 << 0
|
||||
ttlSeconds = autoclearMessageAttribute.timeout
|
||||
}
|
||||
|
||||
for attribute in attributes {
|
||||
if let _ = attribute as? MediaSpoilerMessageAttribute {
|
||||
flags |= 1 << 1
|
||||
}
|
||||
}
|
||||
return .single(.progress(1.0))
|
||||
|> then(
|
||||
.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds), text), reuploadInfo: nil)))
|
||||
.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
)
|
||||
}
|
||||
referenceKey = key
|
||||
case let .localReference(key):
|
||||
referenceKey = key
|
||||
case .none:
|
||||
@ -448,7 +456,7 @@ private func uploadedMediaImageContent(network: Network, postbox: Postbox, trans
|
||||
|> mapToSignal { inputPeer -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
if let inputPeer = inputPeer {
|
||||
if autoclearMessageAttribute != nil {
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedPhoto(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds), text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedPhoto(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
}
|
||||
|
||||
return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedPhoto(flags: flags, file: file, stickers: stickers, ttlSeconds: ttlSeconds)))
|
||||
@ -466,7 +474,7 @@ private func uploadedMediaImageContent(network: Network, postbox: Postbox, trans
|
||||
if hasSpoiler {
|
||||
flags |= 1 << 1
|
||||
}
|
||||
return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds), text), reuploadInfo: nil)), media: mediaImage)
|
||||
return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: nil)), media: mediaImage)
|
||||
}
|
||||
default:
|
||||
break
|
||||
@ -478,7 +486,7 @@ private func uploadedMediaImageContent(network: Network, postbox: Postbox, trans
|
||||
}
|
||||
}
|
||||
case let .inputSecretFile(file, size, key):
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(file, size, key), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(file, size, key), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -606,13 +614,21 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
var referenceKey: CachedSentMediaReferenceKey?
|
||||
switch result {
|
||||
case let .media(media):
|
||||
case let .media(media, key):
|
||||
if !forceReupload, let file = media as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference {
|
||||
var flags: Int32 = 0
|
||||
for attribute in attributes {
|
||||
if let _ = attribute as? MediaSpoilerMessageAttribute {
|
||||
flags |= 1 << 2
|
||||
}
|
||||
}
|
||||
|
||||
return .single(.progress(1.0))
|
||||
|> then(
|
||||
.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil)))
|
||||
.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: flags, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
)
|
||||
}
|
||||
referenceKey = key
|
||||
case let .localReference(key):
|
||||
referenceKey = key
|
||||
case .none:
|
||||
@ -779,11 +795,13 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
}
|
||||
|
||||
if ttlSeconds != nil {
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, ttlSeconds: ttlSeconds), text), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: referenceKey)))
|
||||
}
|
||||
|
||||
if !isGrouped {
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, ttlSeconds: ttlSeconds), text), reuploadInfo: nil)))
|
||||
let resultInfo = PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: stickers, ttlSeconds: ttlSeconds), text), reuploadInfo: nil, cacheReferenceKey: referenceKey)
|
||||
|
||||
return .single(.content(resultInfo))
|
||||
}
|
||||
|
||||
return postbox.transaction { transaction -> Api.InputPeer? in
|
||||
@ -800,9 +818,9 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
if let document = document, let mediaFile = telegramMediaFileFromApiDocument(document), let resource = mediaFile.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference {
|
||||
var flags: Int32 = 0
|
||||
if hasSpoiler {
|
||||
flags |= (1 << 1)
|
||||
flags |= (1 << 2)
|
||||
}
|
||||
return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaDocument(flags: flags, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil)), media: mediaFile)
|
||||
return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaDocument(flags: flags, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil, query: nil), text), reuploadInfo: nil, cacheReferenceKey: nil)), media: mediaFile)
|
||||
}
|
||||
default:
|
||||
break
|
||||
@ -818,7 +836,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
}
|
||||
case let .inputSecretFile(file, size, key):
|
||||
if case .done = fileAndThumbnailResult {
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(file, size, key), reuploadInfo: nil)))
|
||||
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(file, size, key), reuploadInfo: nil, cacheReferenceKey: nil)))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox, force:
|
||||
}
|
||||
}
|
||||
|
||||
func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates, accountPeerId: PeerId) -> Signal<Void, NoError> {
|
||||
func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, cacheReferenceKey: CachedSentMediaReferenceKey?, result: Api.Updates, accountPeerId: PeerId) -> Signal<Void, NoError> {
|
||||
return postbox.transaction { transaction -> Void in
|
||||
let messageId: Int32?
|
||||
var apiMessage: Api.Message?
|
||||
@ -284,6 +284,27 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if updatedMessage.id.namespace == Namespaces.Message.Cloud, let cacheReferenceKey = cacheReferenceKey {
|
||||
var storeMedia: Media?
|
||||
var mediaCount = 0
|
||||
for media in updatedMessage.media {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
storeMedia = image
|
||||
mediaCount += 1
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
storeMedia = file
|
||||
mediaCount += 1
|
||||
}
|
||||
}
|
||||
if mediaCount > 1 {
|
||||
storeMedia = nil
|
||||
}
|
||||
|
||||
if let storeMedia = storeMedia {
|
||||
storeCachedSentMediaReference(transaction: transaction, key: cacheReferenceKey, media: storeMedia)
|
||||
}
|
||||
}
|
||||
}
|
||||
for file in sentStickers {
|
||||
if let entry = CodableEntry(RecentMediaItem(file)) {
|
||||
|
@ -28,13 +28,27 @@ enum CachedSentMediaReferenceKey {
|
||||
}
|
||||
}
|
||||
|
||||
private struct CachedMediaReferenceEntry: Codable {
|
||||
var data: Data
|
||||
}
|
||||
|
||||
func cachedSentMediaReference(postbox: Postbox, key: CachedSentMediaReferenceKey) -> Signal<Media?, NoError> {
|
||||
return .single(nil)
|
||||
/*return postbox.transaction { transaction -> Media? in
|
||||
return transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedSentMediaReferences, key: key.key)) as? Media
|
||||
}*/
|
||||
return postbox.transaction { transaction -> Media? in
|
||||
guard let entry = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedSentMediaReferences, key: key.key))?.get(CachedMediaReferenceEntry.self) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return PostboxDecoder(buffer: MemoryBuffer(data: entry.data)).decodeRootObject() as? Media
|
||||
}
|
||||
}
|
||||
|
||||
func storeCachedSentMediaReference(transaction: Transaction, key: CachedSentMediaReferenceKey, media: Media) {
|
||||
//transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedSentMediaReferences, key: key.key), entry: media)
|
||||
let encoder = PostboxEncoder()
|
||||
encoder.encodeRootObject(media)
|
||||
let mediaData = encoder.makeData()
|
||||
|
||||
guard let entry = CodableEntry(CachedMediaReferenceEntry(data: mediaData)) else {
|
||||
return
|
||||
}
|
||||
transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedSentMediaReferences, key: key.key), entry: entry)
|
||||
}
|
||||
|
@ -450,7 +450,7 @@ public final class PendingMessageManager {
|
||||
}
|
||||
let sendMessage: Signal<PendingMessageResult, NoError> = strongSelf.sendGroupMessagesContent(network: strongSelf.network, postbox: strongSelf.postbox, stateManager: strongSelf.stateManager, accountPeerId: strongSelf.accountPeerId, group: messages.map { data in
|
||||
let (_, message, forwardInfo) = data
|
||||
return (message.id, PendingMessageUploadedContentAndReuploadInfo(content: .forward(forwardInfo), reuploadInfo: nil))
|
||||
return (message.id, PendingMessageUploadedContentAndReuploadInfo(content: .forward(forwardInfo), reuploadInfo: nil, cacheReferenceKey: nil))
|
||||
})
|
||||
|> map { next -> PendingMessageResult in
|
||||
return .progress(1.0)
|
||||
@ -1149,7 +1149,7 @@ public final class PendingMessageManager {
|
||||
|> mapError { _ -> MTRpcError in
|
||||
}
|
||||
case let .result(result):
|
||||
return strongSelf.applySentMessage(postbox: postbox, stateManager: stateManager, message: message, result: result)
|
||||
return strongSelf.applySentMessage(postbox: postbox, stateManager: stateManager, message: message, content: content, result: result)
|
||||
|> mapError { _ -> MTRpcError in
|
||||
}
|
||||
}
|
||||
@ -1232,7 +1232,7 @@ public final class PendingMessageManager {
|
||||
}
|
||||
}
|
||||
|
||||
private func applySentMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, result: Api.Updates) -> Signal<Void, NoError> {
|
||||
private func applySentMessage(postbox: Postbox, stateManager: AccountStateManager, message: Message, content: PendingMessageUploadedContentAndReuploadInfo, result: Api.Updates) -> Signal<Void, NoError> {
|
||||
var apiMessage: Api.Message?
|
||||
for resultMessage in result.messages {
|
||||
if let id = resultMessage.id(namespace: Namespaces.Message.allScheduled.contains(message.id.namespace) ? Namespaces.Message.ScheduledCloud : Namespaces.Message.Cloud) {
|
||||
@ -1249,7 +1249,7 @@ public final class PendingMessageManager {
|
||||
namespace = id.namespace
|
||||
}
|
||||
|
||||
return applyUpdateMessage(postbox: postbox, stateManager: stateManager, message: message, result: result, accountPeerId: self.accountPeerId)
|
||||
return applyUpdateMessage(postbox: postbox, stateManager: stateManager, message: message, cacheReferenceKey: content.cacheReferenceKey, result: result, accountPeerId: self.accountPeerId)
|
||||
|> afterDisposed { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.queue.async {
|
||||
|
@ -351,6 +351,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/ChatListHeaderComponent",
|
||||
"//submodules/TelegramUI/Components/ChatInputNode",
|
||||
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
|
||||
"//submodules/TelegramUI/Components/StorageUsageScreen",
|
||||
"//submodules/MediaPasteboardUI:MediaPasteboardUI",
|
||||
"//submodules/DrawingUI:DrawingUI",
|
||||
"//submodules/FeaturedStickersScreen:FeaturedStickersScreen",
|
||||
|
20
submodules/TelegramUI/Components/AnimatedTextComponent/BUILD
Normal file
@ -0,0 +1,20 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "AnimatedTextComponent",
|
||||
module_name = "AnimatedTextComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,190 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
|
||||
public final class AnimatedTextComponent: Component {
|
||||
public struct Item: Equatable {
|
||||
public enum Content: Equatable {
|
||||
case text(String)
|
||||
case number(Int)
|
||||
}
|
||||
|
||||
public var id: AnyHashable
|
||||
public var isUnbreakable: Bool
|
||||
public var content: Content
|
||||
|
||||
public init(id: AnyHashable, isUnbreakable: Bool = false, content: Content) {
|
||||
self.id = id
|
||||
self.isUnbreakable = isUnbreakable
|
||||
self.content = content
|
||||
}
|
||||
}
|
||||
|
||||
public let font: UIFont
|
||||
public let color: UIColor
|
||||
public let items: [Item]
|
||||
|
||||
public init(
|
||||
font: UIFont,
|
||||
color: UIColor,
|
||||
items: [Item]
|
||||
) {
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.items = items
|
||||
}
|
||||
|
||||
public static func ==(lhs: AnimatedTextComponent, rhs: AnimatedTextComponent) -> Bool {
|
||||
if lhs.font != rhs.font {
|
||||
return false
|
||||
}
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private struct CharacterKey: Hashable {
|
||||
var itemId: AnyHashable
|
||||
var index: Int
|
||||
var value: String
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private var characters: [CharacterKey: ComponentView<Empty>] = [:]
|
||||
|
||||
private var component: AnimatedTextComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: AnimatedTextComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var size = CGSize()
|
||||
|
||||
let delayNorm: CGFloat = 0.002
|
||||
|
||||
var validKeys: [CharacterKey] = []
|
||||
for item in component.items {
|
||||
var itemText: [String] = []
|
||||
switch item.content {
|
||||
case let .text(text):
|
||||
if item.isUnbreakable {
|
||||
itemText = [text]
|
||||
} else {
|
||||
itemText = text.map(String.init)
|
||||
}
|
||||
case let .number(value):
|
||||
if item.isUnbreakable {
|
||||
itemText = ["\(value)"]
|
||||
} else {
|
||||
itemText = "\(value)".map(String.init)
|
||||
}
|
||||
}
|
||||
var index = 0
|
||||
for character in itemText {
|
||||
let characterKey = CharacterKey(itemId: item.id, index: index, value: character)
|
||||
index += 1
|
||||
|
||||
validKeys.append(characterKey)
|
||||
|
||||
var characterTransition = transition
|
||||
let characterView: ComponentView<Empty>
|
||||
if let current = self.characters[characterKey] {
|
||||
characterView = current
|
||||
} else {
|
||||
characterTransition = .immediate
|
||||
characterView = ComponentView()
|
||||
self.characters[characterKey] = characterView
|
||||
}
|
||||
|
||||
let characterSize = characterView.update(
|
||||
transition: characterTransition,
|
||||
component: AnyComponent(Text(
|
||||
text: String(character),
|
||||
font: component.font,
|
||||
color: component.color
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let characterFrame = CGRect(origin: CGPoint(x: size.width, y: 0.0), size: characterSize)
|
||||
if let characterComponentView = characterView.view {
|
||||
var animateIn = false
|
||||
if characterComponentView.superview == nil {
|
||||
self.addSubview(characterComponentView)
|
||||
animateIn = true
|
||||
}
|
||||
|
||||
if characterComponentView.frame != characterFrame {
|
||||
if characterTransition.animation.isImmediate {
|
||||
characterComponentView.frame = characterFrame
|
||||
} else {
|
||||
characterComponentView.bounds = CGRect(origin: CGPoint(), size: characterFrame.size)
|
||||
let deltaPosition = CGPoint(x: characterFrame.midX - characterComponentView.frame.midX, y: characterFrame.midY - characterComponentView.frame.midY)
|
||||
characterComponentView.center = characterFrame.center
|
||||
characterComponentView.layer.animatePosition(from: CGPoint(x: -deltaPosition.x, y: -deltaPosition.y), to: CGPoint(), duration: 0.4, delay: delayNorm * size.width, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
}
|
||||
characterTransition.setFrame(view: characterComponentView, frame: characterFrame)
|
||||
|
||||
|
||||
if animateIn, !transition.animation.isImmediate {
|
||||
characterComponentView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, delay: delayNorm * size.width, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
//characterComponentView.layer.animateSpring(from: (characterSize.height * 0.5) as NSNumber, to: 0.0 as NSNumber, keyPath: "position.y", duration: 0.5, additive: true)
|
||||
characterComponentView.layer.animatePosition(from: CGPoint(x: 0.0, y: characterSize.height * 0.5), to: CGPoint(), duration: 0.4, delay: delayNorm * size.width, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
characterComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18, delay: delayNorm * size.width)
|
||||
}
|
||||
}
|
||||
|
||||
size.height = max(size.height, characterSize.height)
|
||||
size.width += max(0.0, characterSize.width - UIScreenPixel * 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
var removedKeys: [CharacterKey] = []
|
||||
for (key, characterView) in self.characters {
|
||||
if !validKeys.contains(key) {
|
||||
removedKeys.append(key)
|
||||
|
||||
if let characterComponentView = characterView.view {
|
||||
if !transition.animation.isImmediate {
|
||||
characterComponentView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.4, delay: delayNorm * characterComponentView.frame.minX, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
characterComponentView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -characterComponentView.bounds.height * 0.4), duration: 0.4, delay: delayNorm * characterComponentView.frame.minX, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true)
|
||||
characterComponentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, delay: delayNorm * characterComponentView.frame.minX, removeOnCompletion: false, completion: { [weak characterComponentView] _ in
|
||||
characterComponentView?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
characterComponentView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for removedKey in removedKeys {
|
||||
self.characters.removeValue(forKey: removedKey)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -6663,7 +6663,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if hasPremium {
|
||||
maxTopLineCount = 2
|
||||
} else {
|
||||
maxTopLineCount = 5
|
||||
maxTopLineCount = 6
|
||||
}
|
||||
|
||||
for reactionItem in topReactionItems {
|
||||
@ -6721,7 +6721,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
if hasPremium {
|
||||
maxRecentLineCount = 10
|
||||
} else {
|
||||
maxRecentLineCount = 5
|
||||
maxRecentLineCount = 10
|
||||
}
|
||||
|
||||
let popularTitle = hasRecent ? strings.Chat_ReactionSection_Recent : strings.Chat_ReactionSection_Popular
|
||||
|
@ -26,6 +26,7 @@ swift_library(
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/CheckNode",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/ContextUI",
|
||||
|
@ -40,6 +40,7 @@ final class StorageCategoriesComponent: Component {
|
||||
let strings: PresentationStrings
|
||||
let categories: [CategoryData]
|
||||
let isOtherExpanded: Bool
|
||||
let displayAction: Bool
|
||||
let toggleCategorySelection: (StorageUsageScreenComponent.Category) -> Void
|
||||
let toggleOtherExpanded: () -> Void
|
||||
let clearAction: () -> Void
|
||||
@ -49,6 +50,7 @@ final class StorageCategoriesComponent: Component {
|
||||
strings: PresentationStrings,
|
||||
categories: [CategoryData],
|
||||
isOtherExpanded: Bool,
|
||||
displayAction: Bool,
|
||||
toggleCategorySelection: @escaping (StorageUsageScreenComponent.Category) -> Void,
|
||||
toggleOtherExpanded: @escaping () -> Void,
|
||||
clearAction: @escaping () -> Void
|
||||
@ -57,6 +59,7 @@ final class StorageCategoriesComponent: Component {
|
||||
self.strings = strings
|
||||
self.categories = categories
|
||||
self.isOtherExpanded = isOtherExpanded
|
||||
self.displayAction = displayAction
|
||||
self.toggleCategorySelection = toggleCategorySelection
|
||||
self.toggleOtherExpanded = toggleOtherExpanded
|
||||
self.clearAction = clearAction
|
||||
@ -75,6 +78,9 @@ final class StorageCategoriesComponent: Component {
|
||||
if lhs.isOtherExpanded != rhs.isOtherExpanded {
|
||||
return false
|
||||
}
|
||||
if lhs.displayAction != rhs.displayAction {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -195,6 +201,7 @@ final class StorageCategoriesComponent: Component {
|
||||
self.itemViews.removeValue(forKey: key)
|
||||
}
|
||||
|
||||
if component.displayAction {
|
||||
let clearTitle: String
|
||||
let label: String?
|
||||
if totalSelectedSize == 0 {
|
||||
@ -239,7 +246,7 @@ final class StorageCategoriesComponent: Component {
|
||||
containerSize: CGSize(width: availableSize.width - 16.0 * 2.0, height: 50.0)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: 16.0, y: contentHeight), size: buttonSize)
|
||||
if let buttonView = button.view {
|
||||
if let buttonView = self.button.view {
|
||||
if buttonView.superview == nil {
|
||||
self.addSubview(buttonView)
|
||||
}
|
||||
@ -248,6 +255,11 @@ final class StorageCategoriesComponent: Component {
|
||||
contentHeight += buttonSize.height
|
||||
|
||||
contentHeight += 16.0
|
||||
} else {
|
||||
if let buttonView = self.button.view {
|
||||
buttonView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
self.backgroundColor = component.theme.list.itemBlocksBackgroundColor
|
||||
|
||||
|
@ -206,14 +206,18 @@ final class StorageCategoryItemComponent: Component {
|
||||
iconView.removeFromSuperview()
|
||||
}
|
||||
|
||||
let fractionValue: Double = floor(component.category.sizeFraction * 100.0 * 10.0) / 10.0
|
||||
let fractionString: String
|
||||
if component.category.sizeFraction != 0.0 {
|
||||
let fractionValue: Double = floor(component.category.sizeFraction * 100.0 * 10.0) / 10.0
|
||||
if fractionValue < 0.1 {
|
||||
fractionString = "<0.1"
|
||||
fractionString = "<0.1%"
|
||||
} else if abs(Double(Int(fractionValue)) - fractionValue) < 0.001 {
|
||||
fractionString = "\(Int(fractionValue))"
|
||||
fractionString = "\(Int(fractionValue))%"
|
||||
} else {
|
||||
fractionString = "\(fractionValue)"
|
||||
fractionString = "\(fractionValue)%"
|
||||
}
|
||||
} else {
|
||||
fractionString = ""
|
||||
}
|
||||
|
||||
let labelSize = self.label.update(
|
||||
@ -225,8 +229,8 @@ final class StorageCategoryItemComponent: Component {
|
||||
availableWidth = max(1.0, availableWidth - labelSize.width - 1.0)
|
||||
|
||||
let titleValueSize = self.titleValue.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "\(fractionString)%", font: Font.regular(17.0), textColor: component.theme.list.itemSecondaryTextColor)))),
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: fractionString, font: Font.regular(17.0), textColor: component.theme.list.itemSecondaryTextColor)))),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableWidth, height: 100.0)
|
||||
)
|
||||
@ -266,8 +270,13 @@ final class StorageCategoryItemComponent: Component {
|
||||
titleValueView.isUserInteractionEnabled = false
|
||||
self.addSubview(titleValueView)
|
||||
}
|
||||
|
||||
if titleValueView.bounds.size != titleValueFrame.size {
|
||||
titleValueView.frame = titleValueFrame
|
||||
} else {
|
||||
transition.setFrame(view: titleValueView, frame: titleValueFrame)
|
||||
}
|
||||
}
|
||||
if let labelView = self.label.view {
|
||||
if labelView.superview == nil {
|
||||
labelView.isUserInteractionEnabled = false
|
||||
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
12
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleAvatars.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Profile Photos.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / Profile Photos</title>
|
||||
<g id="Icon-/-Profile-Photos" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M36,10 C21.648,10 10,21.648 10,36 C10,50.352 21.648,62 36,62 C50.352,62 62,50.352 62,36 C62,21.648 50.352,10 36,10 Z M36,19.8 C40.316,19.8 43.8,23.284 43.8,27.6 C43.8,31.916 40.316,35.4 36,35.4 C31.684,35.4 28.2,31.916 28.2,27.6 C28.2,23.284 31.684,19.8 36,19.8 Z M36,54.72 C29.5,54.72 23.754,51.4742263 20.4,46.5548505 C20.478,42.4839792 29.826,39.72 36,39.72 C42.174,39.72 51.522,42.4839792 51.6,46.5548505 C48.246,51.4742263 42.5,54.72 36,54.72 Z" id="Shape" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 860 B |
12
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleDocuments.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Documents.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / Documents</title>
|
||||
<g id="Icon-/-Documents" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M43.15125,14.3808511 C42.24875,13.4914894 41.0375,13 39.77875,13 L24.7,13 C21.1375,13 18,16.106383 18,19.6170213 L18,50.3829787 C18,53.893617 21.11375,57 24.67625,57 L49.3,57 C52.8625,57 56,53.893617 56,50.3829787 L56,28.9851064 C56,27.7446809 55.50125,26.5510638 54.59875,25.6851064 L43.15125,14.3808511 Z M39.375,27.0425532 L39.375,16.5106383 L52.4375,29.3829787 L41.75,29.3829787 C40.44375,29.3829787 39.375,28.3297872 39.375,27.0425532 Z" id="Shape" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 842 B |
12
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleMusic.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Music.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
7
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleMusic.imageset/Music.svg
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / Music</title>
|
||||
<g id="Icon-/-Music" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M36,10 C50.352,10 62,21.648 62,36 C62,50.352 50.352,62 36,62 C21.648,62 10,50.352 10,36 C10,21.648 21.648,10 36,10 Z M33.1111111,23.950754 C31.5156218,23.950754 30.2222222,25.2441536 30.2222222,26.8396429 L30.2222222,45.6296296 C30.2222222,46.2546991 30.4249583,46.8629074 30.8,47.362963 C31.7572936,48.6393544 33.568053,48.8980343 34.8444444,47.9407407 L46.5881862,39.1329344 C46.7600033,39.0040716 46.9169145,38.8564504 47.0560109,38.6928077 C48.089327,37.4771416 47.9415032,35.6539814 46.7258372,34.6206653 L34.9820954,24.6384848 C34.4597827,24.194519 33.7966155,23.950754 33.1111111,23.950754 Z" id="Shape" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 991 B |
12
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleOther.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Other.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
7
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleOther.imageset/Other.svg
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / Other</title>
|
||||
<g id="Icon-/-Other" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M31.2684326,15 L17.9882353,15 C15.2447059,15 13.0249412,17.25 13.0249412,20 L13,49.0697674 C13,52.75 15.2447059,55 18.9294118,55 L55.0705882,55 C58.7552941,55 61,52.75 61,49.0697674 L61,25.9302326 C61,22.25 58.7552941,20.5813953 55.0705882,20.5813953 L40.0468122,20.5813953 C38.7070557,20.5813953 37.430813,20.0103436 36.5380314,19.0113998 L34.0754572,16.2559964 C33.361232,15.4568414 32.3402378,15 31.2684326,15 Z" id="Path" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 806 B |
12
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticlePhotos.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Photos.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
7
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticlePhotos.imageset/Photos.svg
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / Photos</title>
|
||||
<g id="Icon-/-Photos" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M48,12 C54.627417,12 60,17.372583 60,24 L60,48 C60,54.627417 54.627417,60 48,60 L24,60 C17.372583,60 12,54.627417 12,48 L12,24 C12,17.372583 17.372583,12 24,12 L48,12 Z M44.3651844,34.6306685 C43.8183724,33.9010116 42.7247485,33.9010116 42.1779366,34.6036442 L33.7023513,45.3863509 L27.9608258,38.549196 C27.3866733,37.8735878 26.3477306,37.9006121 25.8282592,38.6032446 L19.9804504,47.2510295 C19.2695949,48.1428323 19.8984286,50.4 21.0467337,50.4 L51.9081103,50.4 C52.9904208,50.4 53.639443,48.3210078 53.0676996,47.374937 L44.3651844,34.6306685 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 924 B |
12
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleStickers.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Stickers.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / Stickers</title>
|
||||
<g id="Icon-/-Stickers" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M60,35.5 L59.9953966,35.670698 C59.9067791,37.309531 58.5498341,38.6111111 56.8888889,38.6111111 L50.6666667,38.6111111 L50.3912615,38.6142092 C45.9287855,38.7147025 42.0676082,41.2512307 40.0852923,44.9462311 C39.7167534,44.8455073 39.3161253,44.8387068 38.9195771,44.9463572 C37.9769648,45.202247 36.9982344,45.3333333 36,45.3333333 C33.4531941,45.3333333 29.5907927,43.2780139 28.1265456,41.3638425 L27.9058916,41.062472 C27.1856257,40.1544843 25.8744537,39.9480152 24.9057896,40.6151336 C23.8950097,41.3112571 23.6399302,42.6949756 24.3360537,43.7057556 C26.5046744,46.8546196 31.8881746,49.7777778 36,49.7777778 C36.9171651,49.7777778 37.8247038,49.6981543 38.7149707,49.541295 C38.6826363,49.8921863 38.6666667,50.2497837 38.6666667,50.6111111 L38.6666667,56.8888889 L38.6620632,57.0595868 C38.5734457,58.6984199 37.2165008,60 35.5555556,60 L24.4137931,60 C17.5578445,60 12,54.4421555 12,47.5862069 L12,24.4137931 C12,17.5578445 17.5578445,12 24.4137931,12 L47.5862069,12 C54.4421555,12 60,17.5578445 60,24.4137931 L60,35.5 Z M42.6383189,59.9550937 L42.457984,59.9664377 C42.8777423,59.0264139 43.1111111,57.9849045 43.1111111,56.8888889 L43.1111111,50.6111111 L43.1144503,50.384302 C43.2343809,46.3164026 46.569718,43.0555556 50.6666667,43.0555556 L56.8888889,43.0555556 L57.115698,43.0522164 C58.130088,43.0223099 59.0942932,42.7924609 59.9702649,42.4007181 C59.4478271,51.6700337 52.1614105,59.1320852 42.9674765,59.929458 L42.6383189,59.9550937 Z M44.4444444,28 C42.7262252,28 41.3333333,29.790861 41.3333333,32 C41.3333333,34.209139 42.7262252,36 44.4444444,36 C46.1626637,36 47.5555556,34.209139 47.5555556,32 C47.5555556,29.790861 46.1626637,28 44.4444444,28 Z M27.5555556,28 C25.8373363,28 24.4444444,29.790861 24.4444444,32 C24.4444444,34.209139 25.8373363,36 27.5555556,36 C29.2737748,36 30.6666667,34.209139 30.6666667,32 C30.6666667,29.790861 29.2737748,28 27.5555556,28 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
12
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleVideos.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Videos.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
7
submodules/TelegramUI/Images.xcassets/Settings/Storage/ParticleVideos.imageset/Videos.svg
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>Icon / Videos</title>
|
||||
<g id="Icon-/-Videos" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<path d="M18.5095398,18.85 L36.9719416,18.85 C40.5670612,18.85 43.4814815,21.7644203 43.4814815,25.3595398 L43.4814815,45.6404602 C43.4814815,49.2355797 40.5670612,52.15 36.9719416,52.15 L18.5095398,52.15 C14.9144203,52.15 12,49.2355797 12,45.6404602 L12,25.3595398 C12,21.7644203 14.9144203,18.85 18.5095398,18.85 Z M49.5847133,27.1084323 L56.6902242,21.3231792 C58.0841802,20.1882308 60.1342623,20.3981979 61.2692107,21.7921539 C61.741904,22.3727209 62,23.0984963 62,23.8471598 L62,47.6096485 C62,49.4072083 60.5427899,50.8644185 58.7452301,50.8644185 C58.0575374,50.8644185 57.3875158,50.6465978 56.831289,50.2422056 L49.8668429,45.1788626 C48.1820423,43.9539665 47.1851852,41.9967567 47.1851852,39.9137485 L47.1851852,32.1563935 C47.1851852,30.1985078 48.0664292,28.3446077 49.5847133,27.1084323 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -262,6 +262,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
||||
}
|
||||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in
|
||||
}, openStorageManagement: {
|
||||
})
|
||||
interaction.searchTextHighightState = searchQuery
|
||||
self.interaction = interaction
|
||||
|
@ -29,6 +29,7 @@ import PremiumUI
|
||||
import StickerPackPreviewUI
|
||||
import ChatControllerInteraction
|
||||
import ChatPresentationInterfaceState
|
||||
import StorageUsageScreen
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -1424,6 +1425,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return SettingsUI.makePrivacyAndSecurityController(context: context)
|
||||
}
|
||||
|
||||
public func makeStorageManagementController(context: AccountContext) -> ViewController {
|
||||
return StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { [weak context] category in
|
||||
guard let context else {
|
||||
return nil
|
||||
}
|
||||
return storageUsageExceptionsScreen(context: context, category: category)
|
||||
})
|
||||
}
|
||||
|
||||
public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController {
|
||||
let mappedSource: PremiumSource
|
||||
switch source {
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 2f773d98bdfd60d55247b2b3adb8ec70a1fc5748
|
||||
Subproject commit b0f186935c88e09be2106c80b9880ba92fb6b2d3
|
1
submodules/lottie-ios/BUILD
vendored
@ -10,6 +10,7 @@ swift_library(
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -8,6 +8,7 @@
|
||||
import Foundation
|
||||
import QuartzCore
|
||||
import UIKit
|
||||
import Display
|
||||
|
||||
// MARK: - LottieBackgroundBehavior
|
||||
|
||||
@ -395,31 +396,15 @@ final public class AnimationView: AnimationViewBase {
|
||||
}
|
||||
}
|
||||
|
||||
private var workaroundDisplayLink: CADisplayLink?
|
||||
private var workaroundDisplayLink: SharedDisplayLinkDriver.Link?
|
||||
private var needsWorkaroundDisplayLink: Bool = false {
|
||||
didSet {
|
||||
if self.needsWorkaroundDisplayLink != oldValue {
|
||||
if self.needsWorkaroundDisplayLink {
|
||||
if workaroundDisplayLink == nil {
|
||||
class WorkaroundDisplayLinkTarget {
|
||||
private let f: () -> Void
|
||||
|
||||
init(_ f: @escaping () -> Void) {
|
||||
self.f = f
|
||||
}
|
||||
|
||||
@objc func update() {
|
||||
self.f()
|
||||
}
|
||||
}
|
||||
/*self.workaroundDisplayLink = CADisplayLink(target: WorkaroundDisplayLinkTarget { [weak self] in
|
||||
if self.workaroundDisplayLink == nil {
|
||||
self.workaroundDisplayLink = SharedDisplayLinkDriver.shared.add { [weak self] in
|
||||
let _ = self?.realtimeAnimationProgress
|
||||
}, selector: #selector(WorkaroundDisplayLinkTarget.update))
|
||||
if #available(iOS 15.0, *) {
|
||||
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||
self.workaroundDisplayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps)
|
||||
}
|
||||
self.workaroundDisplayLink?.add(to: .main, forMode: .common)*/
|
||||
}
|
||||
} else {
|
||||
if let workaroundDisplayLink = self.workaroundDisplayLink {
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "9.3",
|
||||
"app": "9.3.1",
|
||||
"bazel": "5.3.1",
|
||||
"xcode": "14.1"
|
||||
}
|
||||
|