mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
e08e7b2ecb
38 changed files with 577 additions and 160 deletions
|
|
@ -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 makeSetupTwoFactorAuthController(context: AccountContext) -> ViewController
|
||||
func makeStorageManagementController(context: AccountContext) -> ViewController
|
||||
func navigateToChatController(_ params: NavigateToChatControllerParams)
|
||||
func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController)
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ private func fetchCategoryForFile(_ file: TelegramMediaFile) -> FetchManagerCate
|
|||
}
|
||||
}
|
||||
|
||||
public func messageMediaFileInteractiveFetched(context: AccountContext, message: Message, file: TelegramMediaFile, userInitiated: Bool) -> Signal<Void, NoError> {
|
||||
return messageMediaFileInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), file: file, userInitiated: userInitiated, priority: .userInitiated)
|
||||
public func messageMediaFileInteractiveFetched(context: AccountContext, message: Message, file: TelegramMediaFile, userInitiated: Bool, storeToDownloadsPeerType: MediaAutoDownloadPeerType? = nil) -> Signal<Void, NoError> {
|
||||
return messageMediaFileInteractiveFetched(fetchManager: context.fetchManager, messageId: message.id, messageReference: MessageReference(message), file: file, userInitiated: userInitiated, priority: .userInitiated, storeToDownloadsPeerType: storeToDownloadsPeerType)
|
||||
}
|
||||
|
||||
public func messageMediaFileInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, file: TelegramMediaFile, ranges: RangeSet<Int64> = RangeSet<Int64>(0 ..< Int64.max), userInitiated: Bool, priority: FetchManagerPriority) -> Signal<Void, NoError> {
|
||||
public func messageMediaFileInteractiveFetched(fetchManager: FetchManager, messageId: MessageId, messageReference: MessageReference, file: TelegramMediaFile, ranges: RangeSet<Int64> = RangeSet<Int64>(0 ..< Int64.max), userInitiated: Bool, priority: FetchManagerPriority, storeToDownloadsPeerType: MediaAutoDownloadPeerType? = nil) -> Signal<Void, NoError> {
|
||||
let mediaReference = AnyMediaReference.message(message: messageReference, media: file)
|
||||
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: ranges, statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: nil)
|
||||
return fetchManager.interactivelyFetched(category: fetchCategoryForFile(file), location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(file.resource), ranges: ranges, statsCategory: statsCategoryForFileWithAttributes(file.attributes), elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: storeToDownloadsPeerType)
|
||||
}
|
||||
|
||||
public func messageMediaFileCancelInteractiveFetch(context: AccountContext, messageId: MessageId, file: TelegramMediaFile) {
|
||||
|
|
@ -57,7 +57,7 @@ public func messageMediaImageInteractiveFetched(fetchManager: FetchManager, mess
|
|||
if let range = range {
|
||||
ranges = RangeSet(range.lowerBound ..< range.upperBound)
|
||||
} else {
|
||||
ranges = RangeSet(0 ..< Int64(Int32.max))
|
||||
ranges = RangeSet(0 ..< Int64.max)
|
||||
}
|
||||
return fetchManager.interactivelyFetched(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), mediaReference: mediaReference, resourceReference: mediaReference.resourceReference(resource), ranges: ranges, statsCategory: .image, elevatedPriority: false, userInitiated: userInitiated, priority: priority, storeToDownloadsPeerType: storeToDownloadsPeerType)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }, openStorageManagement: {})
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {})
|
||||
interaction.isInlineMode = isInlineMode
|
||||
|
||||
let items = (0 ..< 2).map { _ -> ChatListItem in
|
||||
|
|
|
|||
|
|
@ -2005,6 +2005,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
self.listNode.clearHighlightAnimated(true)
|
||||
})
|
||||
}, openStorageManagement: {
|
||||
}, openPasswordSetup: {
|
||||
})
|
||||
chatListInteraction.isSearchMode = true
|
||||
|
||||
|
|
@ -3206,7 +3207,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 }, openStorageManagement: {})
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {})
|
||||
var isInlineMode = false
|
||||
if case .topics = key {
|
||||
isInlineMode = false
|
||||
|
|
|
|||
|
|
@ -2358,6 +2358,10 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
|||
if item.interaction.isInlineMode {
|
||||
titleLeftCutout = 22.0
|
||||
}
|
||||
|
||||
if let titleAttributedStringValue = titleAttributedString, titleAttributedStringValue.length == 0 {
|
||||
titleAttributedString = NSAttributedString(string: " ", font: titleFont, textColor: theme.titleColor)
|
||||
}
|
||||
|
||||
let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth
|
||||
var titleCutout: TextNodeCutout?
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ public final class ChatListNodeInteraction {
|
|||
let present: (ViewController) -> Void
|
||||
let openForumThread: (EnginePeer.Id, Int64) -> Void
|
||||
let openStorageManagement: () -> Void
|
||||
let openPasswordSetup: () -> Void
|
||||
|
||||
public var searchTextHighightState: String?
|
||||
var highlightedChatLocation: ChatListHighlightedLocation?
|
||||
|
|
@ -134,7 +135,8 @@ public final class ChatListNodeInteraction {
|
|||
activateChatPreview: @escaping (ChatListItem, Int64?, ASDisplayNode, ContextGesture?, CGPoint?) -> Void,
|
||||
present: @escaping (ViewController) -> Void,
|
||||
openForumThread: @escaping (EnginePeer.Id, Int64) -> Void,
|
||||
openStorageManagement: @escaping () -> Void
|
||||
openStorageManagement: @escaping () -> Void,
|
||||
openPasswordSetup: @escaping () -> Void
|
||||
) {
|
||||
self.activateSearch = activateSearch
|
||||
self.peerSelected = peerSelected
|
||||
|
|
@ -165,6 +167,7 @@ public final class ChatListNodeInteraction {
|
|||
self.animationRenderer = animationRenderer
|
||||
self.openForumThread = openForumThread
|
||||
self.openStorageManagement = openStorageManagement
|
||||
self.openPasswordSetup = openPasswordSetup
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -570,9 +573,14 @@ 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()
|
||||
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] in
|
||||
switch notice {
|
||||
case .clearStorage:
|
||||
nodeInteraction?.openStorageManagement()
|
||||
case .setupPassword:
|
||||
nodeInteraction?.openPasswordSetup()
|
||||
}
|
||||
}), directionHint: entry.directionHint)
|
||||
}
|
||||
}
|
||||
|
|
@ -785,9 +793,14 @@ 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()
|
||||
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] in
|
||||
switch notice {
|
||||
case .clearStorage:
|
||||
nodeInteraction?.openStorageManagement()
|
||||
case .setupPassword:
|
||||
nodeInteraction?.openPasswordSetup()
|
||||
}
|
||||
}), directionHint: entry.directionHint)
|
||||
case .HeaderEntry:
|
||||
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyHeaderItem(), directionHint: entry.directionHint)
|
||||
|
|
@ -1277,6 +1290,20 @@ public final class ChatListNode: ListView {
|
|||
}
|
||||
let controller = self.context.sharedContext.makeStorageManagementController(context: self.context)
|
||||
self.push?(controller)
|
||||
}, openPasswordSetup: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.6, execute: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = dismissServerProvidedSuggestion(account: self.context.account, suggestion: .setupPassword).start()
|
||||
})
|
||||
|
||||
let controller = self.context.sharedContext.makeSetupTwoFactorAuthController(context: self.context)
|
||||
self.push?(controller)
|
||||
})
|
||||
nodeInteraction.isInlineMode = isInlineMode
|
||||
|
||||
|
|
@ -1342,6 +1369,32 @@ public final class ChatListNode: ListView {
|
|||
displayArchiveIntro = .single(false)
|
||||
}
|
||||
|
||||
let suggestPasswordSetup: Signal<Bool, NoError>
|
||||
if case .chatList(groupId: .root) = location, chatListFilter == nil {
|
||||
suggestPasswordSetup = combineLatest(
|
||||
getServerProvidedSuggestions(account: context.account),
|
||||
context.engine.auth.twoStepVerificationConfiguration()
|
||||
)
|
||||
|> map { suggestions, configuration -> Bool in
|
||||
var notSet = false
|
||||
switch configuration {
|
||||
case let .notSet(pendingEmail):
|
||||
if pendingEmail == nil {
|
||||
notSet = true
|
||||
}
|
||||
case .set:
|
||||
break
|
||||
}
|
||||
if !notSet {
|
||||
return false
|
||||
}
|
||||
return suggestions.contains(.setupPassword)
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
suggestPasswordSetup = .single(false)
|
||||
}
|
||||
|
||||
let storageInfo: Signal<Double?, NoError>
|
||||
if !"".isEmpty, case .chatList(groupId: .root) = location, chatListFilter == nil {
|
||||
let totalSizeSignal = combineLatest(context.account.postbox.mediaBox.storageBox.totalSize(), context.account.postbox.mediaBox.cacheStorageBox.totalSize())
|
||||
|
|
@ -1431,13 +1484,13 @@ public final class ChatListNode: ListView {
|
|||
|
||||
let currentPeerId: EnginePeer.Id = context.account.peerId
|
||||
|
||||
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 chatListNodeViewTransition = combineLatest(queue: viewProcessingQueue, hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestPasswordSetup, savedMessagesPeer, chatListViewUpdate, self.statePromise.get())
|
||||
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestPasswordSetup, 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, storageInfo: storageInfo, mode: mode, chatListLocation: location)
|
||||
let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, storageInfo: storageInfo, suggestPasswordSetup: suggestPasswordSetup, mode: mode, chatListLocation: location)
|
||||
let entries = rawEntries.filter { entry in
|
||||
switch entry {
|
||||
case let .PeerEntry(peerEntry):
|
||||
|
|
@ -2467,7 +2520,7 @@ public final class ChatListNode: ListView {
|
|||
} else {
|
||||
break loop
|
||||
}
|
||||
case .ArchiveIntro, .StorageInfo, .HeaderEntry, .AdditionalCategory:
|
||||
case .ArchiveIntro, .Notice, .HeaderEntry, .AdditionalCategory:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ enum ChatListNodeEntryId: Hashable {
|
|||
case ThreadId(Int64)
|
||||
case GroupId(EngineChatList.Group)
|
||||
case ArchiveIntro
|
||||
case StorageInfo
|
||||
case Notice
|
||||
case additionalCategory(Int)
|
||||
}
|
||||
|
||||
|
|
@ -46,6 +46,11 @@ public enum ChatListNodeEntryPromoInfo: Equatable {
|
|||
case psa(type: String, message: String?)
|
||||
}
|
||||
|
||||
enum ChatListNotice: Equatable {
|
||||
case clearStorage(sizeFraction: Double)
|
||||
case setupPassword
|
||||
}
|
||||
|
||||
enum ChatListNodeEntry: Comparable, Identifiable {
|
||||
struct PeerEntryData: Equatable {
|
||||
var index: EngineChatList.Item.Index
|
||||
|
|
@ -235,7 +240,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 Notice(presentationData: ChatListPresentationData, notice: ChatListNotice)
|
||||
case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData)
|
||||
|
||||
var sortIndex: ChatListNodeEntrySortIndex {
|
||||
|
|
@ -250,7 +255,7 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||
return .index(index)
|
||||
case .ArchiveIntro:
|
||||
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
|
||||
case .StorageInfo:
|
||||
case .Notice:
|
||||
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor.successor))
|
||||
case let .AdditionalCategory(index, _, _, _, _, _, _):
|
||||
return .additionalCategory(index)
|
||||
|
|
@ -274,8 +279,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||
return .GroupId(groupId)
|
||||
case .ArchiveIntro:
|
||||
return .ArchiveIntro
|
||||
case .StorageInfo:
|
||||
return .StorageInfo
|
||||
case .Notice:
|
||||
return .Notice
|
||||
case let .AdditionalCategory(_, id, _, _, _, _, _):
|
||||
return .additionalCategory(id)
|
||||
}
|
||||
|
|
@ -348,8 +353,8 @@ enum ChatListNodeEntry: Comparable, Identifiable {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .StorageInfo(lhsPresentationData, lhsInfo):
|
||||
if case let .StorageInfo(rhsPresentationData, rhsInfo) = rhs {
|
||||
case let .Notice(lhsPresentationData, lhsInfo):
|
||||
if case let .Notice(rhsPresentationData, rhsInfo) = rhs {
|
||||
if lhsPresentationData !== rhsPresentationData {
|
||||
return false
|
||||
}
|
||||
|
|
@ -399,7 +404,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, storageInfo: Double?, 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?, suggestPasswordSetup: Bool, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation) -> (entries: [ChatListNodeEntry], loading: Bool) {
|
||||
var result: [ChatListNodeEntry] = []
|
||||
|
||||
var pinnedIndexOffset: UInt16 = 0
|
||||
|
|
@ -661,8 +666,10 @@ 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))
|
||||
if suggestPasswordSetup {
|
||||
result.append(.Notice(presentationData: state.presentationData, notice: .setupPassword))
|
||||
} else if let storageInfo {
|
||||
result.append(.Notice(presentationData: state.presentationData, notice: .clearStorage(sizeFraction: storageInfo)))
|
||||
}
|
||||
|
||||
result.append(.HeaderEntry)
|
||||
|
|
|
|||
|
|
@ -11,15 +11,15 @@ import AppBundle
|
|||
class ChatListStorageInfoItem: ListViewItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let sizeFraction: Double
|
||||
let notice: ChatListNotice
|
||||
let action: () -> Void
|
||||
|
||||
let selectable: Bool = true
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, sizeFraction: Double, action: @escaping () -> Void) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, notice: ChatListNotice, action: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.sizeFraction = sizeFraction
|
||||
self.notice = notice
|
||||
self.action = action
|
||||
}
|
||||
|
||||
|
|
@ -117,23 +117,37 @@ class ChatListStorageInfoItemNode: ListViewItemNode {
|
|||
let _ = baseWidth
|
||||
|
||||
let sideInset: CGFloat = params.leftInset + 16.0
|
||||
let height: CGFloat = 54.0
|
||||
let rightInset: CGFloat = sideInset + 24.0
|
||||
let verticalInset: CGFloat = 8.0
|
||||
let spacing: CGFloat = 0.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 titleString: NSAttributedString
|
||||
let textString: NSAttributedString
|
||||
|
||||
switch item.notice {
|
||||
case let .clearStorage(sizeFraction):
|
||||
let sizeString = dataSizeString(Int64(sizeFraction), formatting: DataSizeStringFormatting(strings: item.strings, decimalSeparator: "."))
|
||||
let rawTitleString = item.strings.ChatList_StorageHintTitle(sizeString)
|
||||
let titleStringValue = NSMutableAttributedString(attributedString: NSAttributedString(string: rawTitleString.string, font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor))
|
||||
if let range = rawTitleString.ranges.first {
|
||||
titleStringValue.addAttribute(.foregroundColor, value: item.theme.rootController.navigationBar.accentTextColor, range: range.range)
|
||||
}
|
||||
titleString = titleStringValue
|
||||
|
||||
textString = NSAttributedString(string: item.strings.ChatList_StorageHintText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||
case .setupPassword:
|
||||
//TODO:localize
|
||||
titleString = NSAttributedString(string: "Protect Your Account", font: titleFont, textColor: item.theme.rootController.navigationBar.primaryTextColor)
|
||||
textString = NSAttributedString(string: "Set a password that will be required each time you log in with this phone number.", font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
|
||||
}
|
||||
|
||||
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 textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: params.width - sideInset - rightInset, height: 100.0)))
|
||||
|
||||
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: height), insets: UIEdgeInsets())
|
||||
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.0.size.height + textLayout.0.size.height), insets: UIEdgeInsets())
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
|
|
@ -148,10 +162,10 @@ class ChatListStorageInfoItemNode: ListViewItemNode {
|
|||
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)
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: sideInset, y: verticalInset), 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)
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: sideInset, y: strongSelf.titleNode.frame.maxY + spacing), 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)
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ public final class MultilineTextComponent: Component {
|
|||
attributedString = parseMarkdownIntoAttributedString(text, attributes: attributes)
|
||||
}
|
||||
|
||||
let previousText = self.attributedText?.string
|
||||
//let previousText = self.attributedText?.string
|
||||
|
||||
self.attributedText = attributedString
|
||||
self.maximumNumberOfLines = component.maximumNumberOfLines
|
||||
|
|
@ -140,7 +140,7 @@ public final class MultilineTextComponent: Component {
|
|||
self.tapAttributeAction = component.tapAction
|
||||
self.longTapAttributeAction = component.longTapAction
|
||||
|
||||
if case let .curve(duration, _) = transition.animation, let previousText = previousText, previousText != attributedString.string {
|
||||
/*if case let .curve(duration, _) = transition.animation, let previousText = previousText, previousText != attributedString.string {
|
||||
if let snapshotView = self.snapshotView(afterScreenUpdates: false) {
|
||||
snapshotView.center = self.center
|
||||
self.superview?.addSubview(snapshotView)
|
||||
|
|
@ -150,7 +150,7 @@ public final class MultilineTextComponent: Component {
|
|||
})
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: duration)
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
let size = self.updateLayout(availableSize)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public protocol SharedDisplayLinkDriverLink: AnyObject {
|
||||
var isPaused: Bool { get set }
|
||||
|
||||
func invalidate()
|
||||
}
|
||||
|
||||
public final class SharedDisplayLinkDriver {
|
||||
public typealias Link = SharedDisplayLinkDriverLink
|
||||
|
||||
public static let shared = SharedDisplayLinkDriver()
|
||||
|
||||
public final class Link {
|
||||
private let useNative: Bool
|
||||
|
||||
public final class LinkImpl: Link {
|
||||
private let driver: SharedDisplayLinkDriver
|
||||
public let needsHighestFramerate: Bool
|
||||
let update: () -> Void
|
||||
|
|
@ -28,10 +38,52 @@ public final class SharedDisplayLinkDriver {
|
|||
}
|
||||
}
|
||||
|
||||
private final class RequestContext {
|
||||
weak var link: Link?
|
||||
public final class NativeLinkImpl: Link {
|
||||
private var displayLink: CADisplayLink?
|
||||
|
||||
init(link: Link) {
|
||||
public var isPaused: Bool = false {
|
||||
didSet {
|
||||
self.displayLink?.isPaused = self.isPaused
|
||||
}
|
||||
}
|
||||
|
||||
init(needsHighestFramerate: Bool, update: @escaping () -> Void) {
|
||||
let displayLink = CADisplayLink(target: DisplayLinkTarget {
|
||||
update()
|
||||
}, selector: #selector(DisplayLinkTarget.event))
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||
if maxFps > 61.0 {
|
||||
let frameRateRange: CAFrameRateRange
|
||||
if needsHighestFramerate {
|
||||
frameRateRange = CAFrameRateRange(minimum: 30.0, maximum: 120.0, preferred: 120.0)
|
||||
} else {
|
||||
frameRateRange = .default
|
||||
}
|
||||
if displayLink.preferredFrameRateRange != frameRateRange {
|
||||
displayLink.preferredFrameRateRange = frameRateRange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.displayLink = displayLink
|
||||
displayLink.add(to: .main, forMode: .common)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.displayLink?.invalidate()
|
||||
}
|
||||
|
||||
public func invalidate() {
|
||||
self.displayLink?.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
private final class RequestContext {
|
||||
weak var link: LinkImpl?
|
||||
|
||||
init(link: LinkImpl) {
|
||||
self.link = link
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +95,8 @@ public final class SharedDisplayLinkDriver {
|
|||
private var isInForeground: Bool = false
|
||||
|
||||
private init() {
|
||||
self.useNative = false
|
||||
|
||||
let _ = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil, using: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -99,14 +153,17 @@ public final class SharedDisplayLinkDriver {
|
|||
displayLink.add(to: .main, forMode: .common)
|
||||
}
|
||||
if #available(iOS 15.0, *) {
|
||||
let frameRateRange: CAFrameRateRange
|
||||
if needHighestFramerate {
|
||||
frameRateRange = CAFrameRateRange(minimum: 30.0, maximum: 120.0, preferred: 120.0)
|
||||
} else {
|
||||
frameRateRange = .default
|
||||
}
|
||||
if displayLink.preferredFrameRateRange != frameRateRange {
|
||||
displayLink.preferredFrameRateRange = frameRateRange
|
||||
let maxFps = Float(UIScreen.main.maximumFramesPerSecond)
|
||||
if maxFps > 61.0 {
|
||||
let frameRateRange: CAFrameRateRange
|
||||
if needHighestFramerate {
|
||||
frameRateRange = CAFrameRateRange(minimum: 30.0, maximum: 120.0, preferred: 120.0)
|
||||
} else {
|
||||
frameRateRange = .default
|
||||
}
|
||||
if displayLink.preferredFrameRateRange != frameRateRange {
|
||||
displayLink.preferredFrameRateRange = frameRateRange
|
||||
}
|
||||
}
|
||||
}
|
||||
displayLink.isPaused = false
|
||||
|
|
@ -143,12 +200,16 @@ public final class SharedDisplayLinkDriver {
|
|||
}
|
||||
|
||||
public func add(needsHighestFramerate: Bool = true, _ update: @escaping () -> Void) -> Link {
|
||||
let link = Link(driver: self, needsHighestFramerate: needsHighestFramerate, update: update)
|
||||
self.requests.append(RequestContext(link: link))
|
||||
|
||||
self.update()
|
||||
|
||||
return link
|
||||
if self.useNative {
|
||||
return NativeLinkImpl(needsHighestFramerate: needsHighestFramerate, update: update)
|
||||
} else {
|
||||
let link = LinkImpl(driver: self, needsHighestFramerate: needsHighestFramerate, update: update)
|
||||
self.requests.append(RequestContext(link: link))
|
||||
|
||||
self.update()
|
||||
|
||||
return link
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ public final class HashtagSearchController: TelegramBaseController {
|
|||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in
|
||||
}, openStorageManagement: {
|
||||
}, openPasswordSetup: {
|
||||
})
|
||||
|
||||
let previousSearchItems = Atomic<[ChatListSearchEntry]?>(value: nil)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,14 @@ typedef struct {
|
|||
NSUInteger outgoingBytes;
|
||||
} MTNetworkUsageManagerInterfaceStats;
|
||||
|
||||
@interface MTNetworkUsageManager : NSObject
|
||||
@protocol MTNetworkUsageManagerProtocol <NSObject>
|
||||
|
||||
- (void)addIncomingBytes:(NSUInteger)incomingBytes interface:(MTNetworkUsageManagerInterface)interface;
|
||||
- (void)addOutgoingBytes:(NSUInteger)outgoingBytes interface:(MTNetworkUsageManagerInterface)interface;
|
||||
|
||||
@end
|
||||
|
||||
@interface MTNetworkUsageManager : NSObject <MTNetworkUsageManagerProtocol>
|
||||
|
||||
- (instancetype)initWithInfo:(MTNetworkUsageCalculationInfo *)info;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ swift_library(
|
|||
"//submodules/ManagedFile:ManagedFile",
|
||||
"//submodules/Utils/RangeSet:RangeSet",
|
||||
"//submodules/CryptoUtils",
|
||||
"//submodules/Utils/DarwinDirStat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import DarwinDirStat
|
||||
|
||||
private typealias SignalKitTimer = SwiftSignalKit.Timer
|
||||
|
||||
struct InodeInfo {
|
||||
|
|
@ -52,7 +54,7 @@ private final class TempScanDatabase {
|
|||
|
||||
init?(queue: Queue, basePath: String) {
|
||||
self.queue = queue
|
||||
guard let valueBox = SqliteValueBox(basePath: basePath, queue: queue, isTemporary: true, isReadOnly: false, useCaches: true, removeDatabaseOnError: true, encryptionParameters: nil, upgradeProgress: { _ in }) else {
|
||||
guard let valueBox = SqliteValueBox(basePath: basePath, queue: queue, isTemporary: true, isReadOnly: false, useCaches: true, removeDatabaseOnError: true, encryptionParameters: nil, upgradeProgress: { _ in }, inMemory: true) else {
|
||||
return nil
|
||||
}
|
||||
self.valueBox = valueBox
|
||||
|
|
@ -127,9 +129,11 @@ private final class TempScanDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSubdirectories: Bool, tempDatabase: TempScanDatabase) -> ScanFilesResult {
|
||||
private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSubdirectories: Bool, performSizeMapping: Bool, tempDatabase: TempScanDatabase) -> ScanFilesResult {
|
||||
var result = ScanFilesResult()
|
||||
|
||||
var subdirectories: [String] = []
|
||||
|
||||
if let dp = opendir(path) {
|
||||
let pathBuffer = malloc(2048).assumingMemoryBound(to: Int8.self)
|
||||
defer {
|
||||
|
|
@ -156,9 +160,7 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSu
|
|||
if (((value.st_mode) & S_IFMT) == S_IFDIR) {
|
||||
if includeSubdirectories {
|
||||
if let subPath = String(data: Data(bytes: pathBuffer, count: strnlen(pathBuffer, 1024)), encoding: .utf8) {
|
||||
let subResult = scanFiles(at: subPath, olderThan: minTimestamp, includeSubdirectories: true, tempDatabase: tempDatabase)
|
||||
result.totalSize += subResult.totalSize
|
||||
result.unlinkedCount += subResult.unlinkedCount
|
||||
subdirectories.append(subPath)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -167,7 +169,9 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSu
|
|||
result.unlinkedCount += 1
|
||||
} else {
|
||||
result.totalSize += UInt64(value.st_size)
|
||||
tempDatabase.add(pathBuffer: pathBuffer, pathSize: strnlen(pathBuffer, 1024), size: Int64(value.st_size), timestamp: Int32(value.st_mtimespec.tv_sec))
|
||||
if performSizeMapping {
|
||||
tempDatabase.add(pathBuffer: pathBuffer, pathSize: strnlen(pathBuffer, 1024), size: Int64(value.st_size), timestamp: Int32(value.st_mtimespec.tv_sec))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -175,6 +179,14 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSu
|
|||
closedir(dp)
|
||||
}
|
||||
|
||||
if includeSubdirectories {
|
||||
for subPath in subdirectories {
|
||||
let subResult = scanFiles(at: subPath, olderThan: minTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
|
||||
result.totalSize += subResult.totalSize
|
||||
result.unlinkedCount += subResult.unlinkedCount
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
@ -259,6 +271,21 @@ private func scanFiles(at path: String, olderThan minTimestamp: Int32, includeSu
|
|||
}
|
||||
}*/
|
||||
|
||||
private func statForDirectory(path: String) -> Int64 {
|
||||
var s = darwin_dirstat()
|
||||
var result = dirstat_np(path, 1, &s, MemoryLayout<darwin_dirstat>.size)
|
||||
if result != -1 {
|
||||
return Int64(s.total_size)
|
||||
} else {
|
||||
result = dirstat_np(path, 0, &s, MemoryLayout<darwin_dirstat>.size)
|
||||
if result != -1 {
|
||||
return Int64(s.total_size)
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class TimeBasedCleanupImpl {
|
||||
private let queue: Queue
|
||||
private let storageBox: StorageBox
|
||||
|
|
@ -298,6 +325,11 @@ private final class TimeBasedCleanupImpl {
|
|||
}
|
||||
|
||||
private func resetScan(general: Int32, shortLived: Int32, gigabytesLimit: Int32) {
|
||||
if "".isEmpty {
|
||||
//TODO:remove debugging
|
||||
return
|
||||
}
|
||||
|
||||
let generalPaths = self.generalPaths
|
||||
let totalSizeBasedPath = self.totalSizeBasedPath
|
||||
let shortLivedPaths = self.shortLivedPaths
|
||||
|
|
@ -329,10 +361,42 @@ private final class TimeBasedCleanupImpl {
|
|||
let bytesLimit = UInt64(gigabytesLimit) * 1024 * 1024 * 1024
|
||||
//#endif
|
||||
|
||||
var totalApproximateSize: Int64 = 0
|
||||
for path in shortLivedPaths {
|
||||
totalApproximateSize += statForDirectory(path: path)
|
||||
}
|
||||
for path in generalPaths {
|
||||
totalApproximateSize += statForDirectory(path: path)
|
||||
}
|
||||
|
||||
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: totalSizeBasedPath), includingPropertiesForKeys: [.fileSizeKey, .fileResourceIdentifierKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
|
||||
var fileIds = Set<Data>()
|
||||
loop: for url in enumerator {
|
||||
guard let url = url as? URL else {
|
||||
continue
|
||||
}
|
||||
if let fileId = (try? url.resourceValues(forKeys: Set([.fileResourceIdentifierKey])))?.fileResourceIdentifier as? Data {
|
||||
if fileIds.contains(fileId) {
|
||||
continue loop
|
||||
}
|
||||
|
||||
if let value = (try? url.resourceValues(forKeys: Set([.fileSizeKey])))?.fileSize, value != 0 {
|
||||
fileIds.insert(fileId)
|
||||
totalApproximateSize += Int64(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var performSizeMapping = true
|
||||
if totalApproximateSize <= bytesLimit {
|
||||
performSizeMapping = false
|
||||
}
|
||||
|
||||
let oldestShortLivedTimestamp = timestamp - shortLived
|
||||
let oldestGeneralTimestamp = timestamp - general
|
||||
for path in shortLivedPaths {
|
||||
let scanResult = scanFiles(at: path, olderThan: oldestShortLivedTimestamp, includeSubdirectories: true, tempDatabase: tempDatabase)
|
||||
let scanResult = scanFiles(at: path, olderThan: oldestShortLivedTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
|
||||
if !paths.contains(path) {
|
||||
paths.append(path)
|
||||
}
|
||||
|
|
@ -342,7 +406,7 @@ private final class TimeBasedCleanupImpl {
|
|||
var totalLimitSize: UInt64 = 0
|
||||
|
||||
for path in generalPaths {
|
||||
let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, tempDatabase: tempDatabase)
|
||||
let scanResult = scanFiles(at: path, olderThan: oldestGeneralTimestamp, includeSubdirectories: true, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
|
||||
if !paths.contains(path) {
|
||||
paths.append(path)
|
||||
}
|
||||
|
|
@ -350,7 +414,7 @@ private final class TimeBasedCleanupImpl {
|
|||
totalLimitSize += scanResult.totalSize
|
||||
}
|
||||
do {
|
||||
let scanResult = scanFiles(at: totalSizeBasedPath, olderThan: 0, includeSubdirectories: false, tempDatabase: tempDatabase)
|
||||
let scanResult = scanFiles(at: totalSizeBasedPath, olderThan: 0, includeSubdirectories: false, performSizeMapping: performSizeMapping, tempDatabase: tempDatabase)
|
||||
if !paths.contains(totalSizeBasedPath) {
|
||||
paths.append(totalSizeBasedPath)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -660,11 +660,18 @@ public func dataAndStorageController(context: AccountContext, focusOnItemTag: Da
|
|||
return storageUsageExceptionsScreen(context: context, category: category)
|
||||
}))
|
||||
}, openNetworkUsage: {
|
||||
//pushControllerImpl?(networkUsageStatsController(context: context))
|
||||
|
||||
let _ = (accountNetworkUsageStats(account: context.account, reset: [])
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { stats in
|
||||
var stats = stats
|
||||
|
||||
if stats.resetWifiTimestamp == 0 {
|
||||
var value = stat()
|
||||
if stat(context.account.basePath, &value) == 0 {
|
||||
stats.resetWifiTimestamp = Int32(value.st_ctimespec.tv_sec)
|
||||
}
|
||||
}
|
||||
|
||||
pushControllerImpl?(DataUsageScreen(context: context, stats: stats))
|
||||
})
|
||||
}, openProxy: {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Display
|
|||
import Postbox
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import PasswordSetupUI
|
||||
|
||||
public protocol SettingsController: AnyObject {
|
||||
func updateContext(context: AccountContext)
|
||||
|
|
@ -13,3 +14,14 @@ public protocol SettingsController: AnyObject {
|
|||
public func makePrivacyAndSecurityController(context: AccountContext) -> ViewController {
|
||||
return privacyAndSecurityController(context: context, focusOnItemTag: PrivacyAndSecurityEntryTag.autoArchive)
|
||||
}
|
||||
|
||||
public func makeSetupTwoFactorAuthController(context: AccountContext) -> ViewController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = TwoFactorAuthSplashScreen(sharedContext: context.sharedContext, engine: .authorized(context.engine), mode: .intro(.init(
|
||||
title: presentationData.strings.TwoFactorSetup_Intro_Title,
|
||||
text: presentationData.strings.TwoFactorSetup_Intro_Text,
|
||||
actionText: presentationData.strings.TwoFactorSetup_Intro_Action,
|
||||
doneText: presentationData.strings.TwoFactorSetup_Done_Action
|
||||
)))
|
||||
return controller
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,7 +222,8 @@ 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 }, openStorageManagement: {})
|
||||
}, present: { _ in }, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -844,7 +844,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
|
|||
gesture?.cancel()
|
||||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in },
|
||||
openStorageManagement: {})
|
||||
openStorageManagement: {}, openPasswordSetup: {
|
||||
})
|
||||
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,8 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
|||
}, activateChatPreview: { _, _, _, gesture, _ in
|
||||
gesture?.cancel()
|
||||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {})
|
||||
}, openForumThread: { _, _ in }, openStorageManagement: {}, openPasswordSetup: {
|
||||
})
|
||||
|
||||
func makeChatListItem(
|
||||
peer: EnginePeer,
|
||||
|
|
|
|||
|
|
@ -95,6 +95,12 @@ public func fetchedMediaResource(
|
|||
|
||||
let location = MediaResourceStorageLocation(userLocation: userLocation, reference: reference)
|
||||
|
||||
var ranges = ranges
|
||||
|
||||
if let rangesValue = ranges, rangesValue.count == 1, rangesValue[0].0 == 0 ..< Int64.max {
|
||||
ranges = nil
|
||||
}
|
||||
|
||||
if let ranges = ranges {
|
||||
let signals = ranges.map { (range, priority) -> Signal<Void, FetchResourceError> in
|
||||
return mediaBox.fetchedResourceData(reference.resource, in: range, priority: priority, parameters: MediaResourceFetchParameters(
|
||||
|
|
|
|||
|
|
@ -787,6 +787,11 @@ private final class MultipartFetchManager {
|
|||
case .revalidateMediaReference:
|
||||
if !strongSelf.revalidatingMediaReference && !strongSelf.revalidatedMediaReference {
|
||||
strongSelf.revalidatingMediaReference = true
|
||||
for (_, part) in strongSelf.fetchingParts {
|
||||
part.1.dispose()
|
||||
}
|
||||
strongSelf.fetchingParts.removeAll()
|
||||
|
||||
if let info = strongSelf.parameters?.info as? TelegramCloudMediaResourceFetchInfo, let revalidationContext = strongSelf.revalidationContext {
|
||||
strongSelf.revalidateMediaReferenceDisposable.set((revalidateMediaResourceReference(postbox: strongSelf.postbox, network: strongSelf.network, revalidationContext: revalidationContext, info: info, resource: strongSelf.resource)
|
||||
|> deliverOn(strongSelf.queue)).start(next: { validationResult in
|
||||
|
|
|
|||
|
|
@ -234,21 +234,17 @@ public struct NetworkUsageStatsConnectionsEntry: Equatable {
|
|||
}
|
||||
|
||||
public struct NetworkUsageStats: Equatable {
|
||||
public let generic: NetworkUsageStatsConnectionsEntry
|
||||
public let image: NetworkUsageStatsConnectionsEntry
|
||||
public let video: NetworkUsageStatsConnectionsEntry
|
||||
public let audio: NetworkUsageStatsConnectionsEntry
|
||||
public let file: NetworkUsageStatsConnectionsEntry
|
||||
public let call: NetworkUsageStatsConnectionsEntry
|
||||
public let sticker: NetworkUsageStatsConnectionsEntry
|
||||
public let voiceMessage: NetworkUsageStatsConnectionsEntry
|
||||
public var generic: NetworkUsageStatsConnectionsEntry
|
||||
public var image: NetworkUsageStatsConnectionsEntry
|
||||
public var video: NetworkUsageStatsConnectionsEntry
|
||||
public var audio: NetworkUsageStatsConnectionsEntry
|
||||
public var file: NetworkUsageStatsConnectionsEntry
|
||||
public var call: NetworkUsageStatsConnectionsEntry
|
||||
public var sticker: NetworkUsageStatsConnectionsEntry
|
||||
public var voiceMessage: NetworkUsageStatsConnectionsEntry
|
||||
|
||||
public let resetWifiTimestamp: Int32
|
||||
public let resetCellularTimestamp: Int32
|
||||
|
||||
public static func ==(lhs: NetworkUsageStats, rhs: NetworkUsageStats) -> Bool {
|
||||
return lhs.generic == rhs.generic && lhs.image == rhs.image && lhs.video == rhs.video && lhs.audio == rhs.audio && lhs.file == rhs.file && lhs.call == rhs.call && lhs.resetWifiTimestamp == rhs.resetWifiTimestamp && lhs.resetCellularTimestamp == rhs.resetCellularTimestamp && lhs.sticker == rhs.sticker && lhs.voiceMessage == rhs.voiceMessage
|
||||
}
|
||||
public var resetWifiTimestamp: Int32
|
||||
public var resetCellularTimestamp: Int32
|
||||
}
|
||||
|
||||
public struct ResetNetworkUsageStats: OptionSet {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ public enum ServerProvidedSuggestion: String {
|
|||
case newcomerTicks = "NEWCOMER_TICKS"
|
||||
case validatePhoneNumber = "VALIDATE_PHONE_NUMBER"
|
||||
case validatePassword = "VALIDATE_PASSWORD"
|
||||
case setupPassword = "SETUP_2FA"
|
||||
}
|
||||
|
||||
private var dismissedSuggestionsPromise = ValuePromise<[AccountRecordId: Set<ServerProvidedSuggestion>]>([:])
|
||||
|
|
@ -28,9 +29,17 @@ public func getServerProvidedSuggestions(account: Account) -> Signal<[ServerProv
|
|||
guard let appConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) else {
|
||||
return []
|
||||
}
|
||||
guard let data = appConfiguration.data, let list = data["pending_suggestions"] as? [String] else {
|
||||
guard let data = appConfiguration.data, let listItems = data["pending_suggestions"] as? [String] else {
|
||||
return []
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
var list = listItems
|
||||
list.append(ServerProvidedSuggestion.setupPassword.rawValue)
|
||||
#else
|
||||
let list = listItems
|
||||
#endif
|
||||
|
||||
return list.compactMap { item -> ServerProvidedSuggestion? in
|
||||
return ServerProvidedSuggestion(rawValue: item)
|
||||
}.filter { !dismissedSuggestions.contains($0) }
|
||||
|
|
|
|||
|
|
@ -6204,10 +6204,10 @@ public final class EmojiPagerContentComponent: Component {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if visibleSearchHeader.superview != self.scrollView {
|
||||
/*if visibleSearchHeader.superview != self.scrollView {
|
||||
self.scrollView.addSubview(visibleSearchHeader)
|
||||
self.mirrorContentScrollView.addSubview(visibleSearchHeader.tintContainerView)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
} else {
|
||||
visibleSearchHeader = EmojiSearchHeaderView(activated: { [weak self] in
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ final class DataCategoriesComponent: Component {
|
|||
}
|
||||
|
||||
self.backgroundColor = component.theme.list.itemBlocksBackgroundColor
|
||||
self.containerView.backgroundColor = component.theme.list.itemBlocksBackgroundColor
|
||||
|
||||
self.containerView.frame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: contentHeight))
|
||||
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ final class DataUsageScreenComponent: Component {
|
|||
init(stats: NetworkUsageStats) {
|
||||
self.wifi = Stats(stats: stats, isWifi: true)
|
||||
self.cellular = Stats(stats: stats, isWifi: false)
|
||||
self.resetTimestamp = stats.resetWifiTimestamp
|
||||
self.resetTimestamp = max(stats.resetWifiTimestamp, stats.resetCellularTimestamp)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -293,7 +293,8 @@ final class DataUsageScreenComponent: Component {
|
|||
private let headerOffsetContainer: UIView
|
||||
private let headerDescriptionView = ComponentView<Empty>()
|
||||
|
||||
private var doneStatusNode: RadialStatusNode?
|
||||
private var doneLabel: ComponentView<Empty>?
|
||||
private var doneSupLabel: ComponentView<Empty>?
|
||||
|
||||
private let scrollContainerView: UIView
|
||||
|
||||
|
|
@ -566,8 +567,17 @@ final class DataUsageScreenComponent: Component {
|
|||
chartItems.append(PieChartComponent.ChartData.Item(id: AnyHashable(listCategory.key), displayValue: listCategory.sizeFraction, displaySize: listCategory.size, value: categoryChartFraction, color: listCategory.color, particle: nil, title: listCategory.key.title(strings: environment.strings), mergeable: false, mergeFactor: 1.0))
|
||||
}
|
||||
|
||||
var emptyValue: CGFloat = 0.0
|
||||
if totalSize == 0 {
|
||||
for i in 0 ..< chartItems.count {
|
||||
chartItems[i].value = 0.0
|
||||
}
|
||||
emptyValue = 1.0
|
||||
}
|
||||
if let allStats = self.allStats, allStats.wifi.isEmpty && allStats.cellular.isEmpty {
|
||||
chartItems.removeAll()
|
||||
} else {
|
||||
chartItems.append(PieChartComponent.ChartData.Item(id: "empty", displayValue: 0.0, displaySize: 0, value: emptyValue, color: UIColor(rgb: 0xC4C4C6), particle: nil, title: "", mergeable: false, mergeFactor: 1.0))
|
||||
}
|
||||
|
||||
let totalCategories: [DataCategoriesComponent.CategoryData] = [
|
||||
|
|
@ -611,6 +621,7 @@ final class DataUsageScreenComponent: Component {
|
|||
component: AnyComponent(PieChartComponent(
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
emptyColor: environment.theme.list.itemAccentColor,
|
||||
chartData: chartData
|
||||
)),
|
||||
environment: {},
|
||||
|
|
@ -625,42 +636,75 @@ final class DataUsageScreenComponent: Component {
|
|||
pieChartTransition.setFrame(view: pieChartComponentView, frame: pieChartFrame)
|
||||
}
|
||||
if let allStats = self.allStats, allStats.wifi.isEmpty && allStats.cellular.isEmpty {
|
||||
let checkColor = UIColor(rgb: 0x34C759)
|
||||
let checkColor = environment.theme.list.itemAccentColor
|
||||
|
||||
let doneStatusNode: RadialStatusNode
|
||||
var animateIn = false
|
||||
if let current = self.doneStatusNode {
|
||||
doneStatusNode = current
|
||||
var doneLabelTransition = transition
|
||||
let doneLabel: ComponentView<Empty>
|
||||
if let current = self.doneLabel {
|
||||
doneLabel = current
|
||||
} else {
|
||||
doneStatusNode = RadialStatusNode(backgroundNodeColor: .clear)
|
||||
self.doneStatusNode = doneStatusNode
|
||||
self.scrollView.addSubnode(doneStatusNode)
|
||||
animateIn = true
|
||||
}
|
||||
let doneSize = CGSize(width: 100.0, height: 100.0)
|
||||
doneStatusNode.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - doneSize.width) / 2.0), y: contentHeight), size: doneSize)
|
||||
|
||||
if animateIn {
|
||||
Queue.mainQueue().after(0.18, {
|
||||
doneStatusNode.transitionToState(.check(checkColor), animated: true)
|
||||
})
|
||||
doneLabelTransition = .immediate
|
||||
doneLabel = ComponentView()
|
||||
self.doneLabel = doneLabel
|
||||
}
|
||||
|
||||
contentHeight += doneSize.height
|
||||
let doneSupLabel: ComponentView<Empty>
|
||||
if let current = self.doneSupLabel {
|
||||
doneSupLabel = current
|
||||
} else {
|
||||
doneSupLabel = ComponentView()
|
||||
self.doneSupLabel = doneSupLabel
|
||||
}
|
||||
|
||||
let doneLabelSize = doneLabel.update(transition: doneLabelTransition, component: AnyComponent(Text(text: "0", font: UIFont.systemFont(ofSize: 50.0, weight: UIFont.Weight(0.25)), color: checkColor)), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0))
|
||||
let doneLabelFrame = CGRect(origin: CGPoint(x: pieChartFrame.minX + floor((pieChartFrame.width - doneLabelSize.width) * 0.5), y: pieChartFrame.minY + 16.0), size: doneLabelSize)
|
||||
if let doneLabelView = doneLabel.view {
|
||||
var animateIn = false
|
||||
if doneLabelView.superview == nil {
|
||||
self.scrollView.addSubview(doneLabelView)
|
||||
animateIn = true
|
||||
}
|
||||
doneLabelTransition.setFrame(view: doneLabelView, frame: doneLabelFrame)
|
||||
|
||||
if animateIn {
|
||||
doneLabelView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
let doneSupLabelSize = doneSupLabel.update(transition: doneLabelTransition, component: AnyComponent(Text(text: "KB", font: avatarPlaceholderFont(size: 12.0), color: checkColor)), environment: {}, containerSize: CGSize(width: 100.0, height: 100.0))
|
||||
let doneSupLabelFrame = CGRect(origin: CGPoint(x: doneLabelFrame.maxX + 1.0, y: doneLabelFrame.minY + 10.0), size: doneSupLabelSize)
|
||||
if let doneSupLabelView = doneSupLabel.view {
|
||||
var animateIn = false
|
||||
if doneSupLabelView.superview == nil {
|
||||
self.scrollView.addSubview(doneSupLabelView)
|
||||
animateIn = true
|
||||
}
|
||||
doneLabelTransition.setFrame(view: doneSupLabelView, frame: doneSupLabelFrame)
|
||||
|
||||
if animateIn {
|
||||
doneSupLabelView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
contentHeight += 100.0
|
||||
} else {
|
||||
contentHeight += pieChartSize.height
|
||||
|
||||
if let doneStatusNode = self.doneStatusNode {
|
||||
self.doneStatusNode = nil
|
||||
doneStatusNode.removeFromSupernode()
|
||||
if let doneLabel = self.doneLabel {
|
||||
self.doneLabel = nil
|
||||
doneLabel.view?.removeFromSuperview()
|
||||
}
|
||||
if let doneSupLabel = self.doneSupLabel {
|
||||
self.doneSupLabel = nil
|
||||
doneSupLabel.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
contentHeight += 23.0
|
||||
|
||||
let headerText: String
|
||||
if listCategories.isEmpty {
|
||||
headerText = "Data Usage Reset"
|
||||
if totalSize == 0 {
|
||||
headerText = "No Data Used"
|
||||
} else {
|
||||
headerText = "Data Usage"
|
||||
}
|
||||
|
|
@ -686,15 +730,19 @@ final class DataUsageScreenComponent: Component {
|
|||
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor)
|
||||
|
||||
//TODO:localize
|
||||
|
||||
let timestampString: String
|
||||
if let allStats = self.allStats, allStats.resetTimestamp != 0 {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "E, d MMM yyyy HH:mm"
|
||||
let dateStringPlain = formatter.string(from: Date(timeIntervalSince1970: Double(allStats.resetTimestamp)))
|
||||
timestampString = "Your network usage since \(dateStringPlain)"
|
||||
let dateStringPlain = stringForFullDate(timestamp: allStats.resetTimestamp, strings: environment.strings, dateTimeFormat: PresentationDateTimeFormat())
|
||||
switch self.selectedStats {
|
||||
case .all:
|
||||
timestampString = "Your data usage since \(dateStringPlain)"
|
||||
case .mobile:
|
||||
timestampString = "Your mobile data usage since \(dateStringPlain)"
|
||||
case .wifi:
|
||||
timestampString = "Your Wi-Fi data usage since \(dateStringPlain)"
|
||||
}
|
||||
} else {
|
||||
timestampString = "Your network usage"
|
||||
timestampString = ""
|
||||
}
|
||||
|
||||
let totalUsageText: String = timestampString
|
||||
|
|
@ -743,8 +791,13 @@ final class DataUsageScreenComponent: Component {
|
|||
animatedTextItems.append(AnimatedTextComponent.Item(id: "rest", isUnbreakable: true, content: .text(remainingSizeText)))
|
||||
}
|
||||
|
||||
var labelTransition = transition
|
||||
if labelTransition.animation.isImmediate, let animationHint, animationHint.value == .modeChanged {
|
||||
labelTransition = Transition(animation: .curve(duration: 0.3, curve: .easeInOut))
|
||||
}
|
||||
|
||||
let chartTotalLabelSize = self.chartTotalLabel.update(
|
||||
transition: transition,
|
||||
transition: labelTransition,
|
||||
component: AnyComponent(AnimatedTextComponent(
|
||||
font: Font.with(size: 20.0, design: .round, weight: .bold),
|
||||
color: environment.theme.list.itemPrimaryTextColor,
|
||||
|
|
@ -758,8 +811,8 @@ final class DataUsageScreenComponent: Component {
|
|||
self.scrollContainerView.addSubview(chartTotalLabelView)
|
||||
}
|
||||
let totalLabelFrame = CGRect(origin: CGPoint(x: pieChartFrame.minX + floor((pieChartFrame.width - chartTotalLabelSize.width) / 2.0), y: pieChartFrame.minY + floor((pieChartFrame.height - chartTotalLabelSize.height) / 2.0)), size: chartTotalLabelSize)
|
||||
transition.setFrame(view: chartTotalLabelView, frame: totalLabelFrame)
|
||||
transition.setAlpha(view: chartTotalLabelView, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||
labelTransition.setFrame(view: chartTotalLabelView, frame: totalLabelFrame)
|
||||
labelTransition.setAlpha(view: chartTotalLabelView, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -848,9 +901,18 @@ final class DataUsageScreenComponent: Component {
|
|||
contentHeight += 40.0
|
||||
|
||||
//TODO:localize
|
||||
let totalTitle: String
|
||||
switch self.selectedStats {
|
||||
case .all:
|
||||
totalTitle = "TOTAL NETWORK USAGE"
|
||||
case .mobile:
|
||||
totalTitle = "MOBILE NETWORK USAGE"
|
||||
case .wifi:
|
||||
totalTitle = "WI-FI NETWORK USAGE"
|
||||
}
|
||||
let totalCategoriesTitleSize = self.totalCategoriesTitleView.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MultilineTextComponent(text: .markdown(text: "TOTAL NETWORK USAGE", attributes: MarkdownAttributes(
|
||||
component: AnyComponent(MultilineTextComponent(text: .markdown(text: totalTitle, attributes: MarkdownAttributes(
|
||||
body: body,
|
||||
bold: bold,
|
||||
link: body,
|
||||
|
|
|
|||
|
|
@ -242,15 +242,18 @@ final class PieChartComponent: Component {
|
|||
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let emptyColor: UIColor
|
||||
let chartData: ChartData
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
emptyColor: UIColor,
|
||||
chartData: ChartData
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.emptyColor = emptyColor
|
||||
self.chartData = chartData
|
||||
}
|
||||
|
||||
|
|
@ -261,6 +264,9 @@ final class PieChartComponent: Component {
|
|||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.emptyColor != rhs.emptyColor {
|
||||
return false
|
||||
}
|
||||
if lhs.chartData != rhs.chartData {
|
||||
return false
|
||||
}
|
||||
|
|
@ -366,6 +372,8 @@ final class PieChartComponent: Component {
|
|||
var label: CalculatedLabel?
|
||||
if let leftLabel = left.label, let rightLabel = right.label {
|
||||
label = leftLabel.interpolateTo(rightLabel, amount: progress)
|
||||
} else {
|
||||
label = right.label
|
||||
}
|
||||
|
||||
self.sections.append(CalculatedSection(
|
||||
|
|
@ -385,7 +393,7 @@ final class PieChartComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
init(size: CGSize, items: [ChartData.Item], selectedKey: AnyHashable?, isEmpty: Bool) {
|
||||
init(size: CGSize, items: [ChartData.Item], selectedKey: AnyHashable?, isEmpty: Bool, emptyColor: UIColor) {
|
||||
self.size = size
|
||||
self.sections = []
|
||||
self.isEmpty = isEmpty
|
||||
|
|
@ -447,7 +455,7 @@ final class PieChartComponent: Component {
|
|||
var arcOuterEndAngle = startAngle + angleValue - angleSpacing * 0.5 * afterSpacingFraction
|
||||
arcOuterEndAngle = max(arcOuterEndAngle, arcOuterStartAngle)
|
||||
|
||||
let itemColor: UIColor = isEmpty ? UIColor(rgb: 0x34C759) : item.color
|
||||
let itemColor: UIColor = isEmpty ? emptyColor : item.color
|
||||
|
||||
self.sections.append(CalculatedSection(
|
||||
id: item.id,
|
||||
|
|
@ -504,15 +512,17 @@ final class PieChartComponent: Component {
|
|||
|
||||
let fractionValue: Double = floor(displayValue * 100.0 * 10.0) / 10.0
|
||||
let fractionString: String
|
||||
if fractionValue < 0.1 {
|
||||
fractionString = "<0.1"
|
||||
if fractionValue == 0.0 {
|
||||
fractionString = ""
|
||||
} else if fractionValue < 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)%"
|
||||
}
|
||||
|
||||
let labelString = NSAttributedString(string: "\(fractionString)%", font: chartLabelFont, textColor: .white)
|
||||
let labelString = NSAttributedString(string: fractionString, font: chartLabelFont, textColor: .white)
|
||||
let labelBounds = labelString.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: [.usesLineFragmentOrigin], context: nil)
|
||||
let labelSize = CGSize(width: ceil(labelBounds.width), height: ceil(labelBounds.height))
|
||||
guard let labelImage = generateImage(labelSize, rotatedContext: { size, context in
|
||||
|
|
@ -922,12 +932,15 @@ final class PieChartComponent: Component {
|
|||
}
|
||||
|
||||
private final class DoneLayer: SimpleLayer {
|
||||
private let particleColor: UIColor
|
||||
private let maskShapeLayer: CAShapeLayer
|
||||
private var particleImage: UIImage?
|
||||
private var particleSet: ParticleSet?
|
||||
private var particleLayers: [SimpleLayer] = []
|
||||
|
||||
override init() {
|
||||
init(particleColor: UIColor) {
|
||||
self.particleColor = particleColor
|
||||
|
||||
self.maskShapeLayer = CAShapeLayer()
|
||||
self.maskShapeLayer.fillColor = UIColor.black.cgColor
|
||||
self.maskShapeLayer.fillRule = .evenOdd
|
||||
|
|
@ -948,6 +961,7 @@ final class PieChartComponent: Component {
|
|||
}
|
||||
|
||||
override init(layer: Any) {
|
||||
self.particleColor = .white
|
||||
self.maskShapeLayer = CAShapeLayer()
|
||||
|
||||
super.init(layer: layer)
|
||||
|
|
@ -982,7 +996,7 @@ final class PieChartComponent: Component {
|
|||
self.particleLayers.append(particleLayer)
|
||||
self.addSublayer(particleLayer)
|
||||
|
||||
particleLayer.layerTintColor = UIColor(rgb: 0x34C759).cgColor
|
||||
particleLayer.layerTintColor = self.particleColor.cgColor
|
||||
}
|
||||
|
||||
particleLayer.position = particle.position
|
||||
|
|
@ -1010,6 +1024,7 @@ final class PieChartComponent: Component {
|
|||
private final class ChartDataView: UIView {
|
||||
private(set) var theme: PresentationTheme?
|
||||
private(set) var data: ChartData?
|
||||
private var emptyColor: UIColor?
|
||||
private(set) var selectedKey: AnyHashable?
|
||||
|
||||
private var currentAnimation: (start: CalculatedLayout, startTime: Double, duration: Double)?
|
||||
|
|
@ -1077,7 +1092,9 @@ final class PieChartComponent: Component {
|
|||
return nil
|
||||
}
|
||||
|
||||
func setItems(theme: PresentationTheme, data: ChartData, selectedKey: AnyHashable?, animated: Bool) {
|
||||
func setItems(theme: PresentationTheme, emptyColor: UIColor, data: ChartData, selectedKey: AnyHashable?, animated: Bool) {
|
||||
self.emptyColor = emptyColor
|
||||
|
||||
let data = processChartData(data: data)
|
||||
|
||||
if self.theme !== theme || self.data != data || self.selectedKey != selectedKey {
|
||||
|
|
@ -1099,14 +1116,16 @@ final class PieChartComponent: Component {
|
|||
size: CGSize(width: 200.0, height: 200.0),
|
||||
items: previousData.items,
|
||||
selectedKey: self.selectedKey,
|
||||
isEmpty: true
|
||||
isEmpty: true,
|
||||
emptyColor: emptyColor
|
||||
)
|
||||
} else {
|
||||
targetLayout = CalculatedLayout(
|
||||
size: CGSize(width: 200.0, height: 200.0),
|
||||
items: data.items,
|
||||
selectedKey: self.selectedKey,
|
||||
isEmpty: false
|
||||
isEmpty: false,
|
||||
emptyColor: emptyColor
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1118,14 +1137,16 @@ final class PieChartComponent: Component {
|
|||
size: CGSize(width: 200.0, height: 200.0),
|
||||
items: [.init(id: AnyHashable(StorageUsageScreenComponent.Category.other), displayValue: 0.0, displaySize: 0, value: 1.0, color: .green, particle: "Settings/Storage/ParticleOther", title: "", mergeable: false, mergeFactor: 1.0)],
|
||||
selectedKey: self.selectedKey,
|
||||
isEmpty: true
|
||||
isEmpty: true,
|
||||
emptyColor: emptyColor
|
||||
)
|
||||
} else {
|
||||
self.currentLayout = CalculatedLayout(
|
||||
size: CGSize(width: 200.0, height: 200.0),
|
||||
items: data.items,
|
||||
selectedKey: self.selectedKey,
|
||||
isEmpty: data.items.isEmpty
|
||||
isEmpty: data.items.isEmpty,
|
||||
emptyColor: emptyColor
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1140,7 +1161,7 @@ final class PieChartComponent: Component {
|
|||
self.particleSet.update(deltaTime: deltaTime)
|
||||
|
||||
var validIds: [AnyHashable] = []
|
||||
if let currentLayout = self.currentLayout {
|
||||
if let currentLayout = self.currentLayout, let emptyColor = self.emptyColor {
|
||||
var effectiveLayout = currentLayout
|
||||
var verticalOffset: CGFloat = 0.0
|
||||
var particleAlpha: CGFloat = 1.0
|
||||
|
|
@ -1195,7 +1216,7 @@ final class PieChartComponent: Component {
|
|||
if let current = self.doneLayer {
|
||||
doneLayer = current
|
||||
} else {
|
||||
doneLayer = DoneLayer()
|
||||
doneLayer = DoneLayer(particleColor: emptyColor)
|
||||
self.doneLayer = doneLayer
|
||||
self.layer.insertSublayer(doneLayer, at: 0)
|
||||
}
|
||||
|
|
@ -1269,7 +1290,7 @@ final class PieChartComponent: Component {
|
|||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
let point = recognizer.location(in: self.dataView)
|
||||
if let key = self.dataView.sectionKey(at: point) {
|
||||
if let key = self.dataView.sectionKey(at: point), key != AnyHashable("empty") {
|
||||
if self.selectedKey == key {
|
||||
self.selectedKey = nil
|
||||
} else {
|
||||
|
|
@ -1293,7 +1314,7 @@ final class PieChartComponent: Component {
|
|||
}
|
||||
|
||||
transition.setFrame(view: self.dataView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - 200.0) / 2.0), y: 0.0), size: CGSize(width: 200.0, height: 200.0)))
|
||||
self.dataView.setItems(theme: component.theme, data: component.chartData, selectedKey: self.selectedKey, animated: !transition.animation.isImmediate)
|
||||
self.dataView.setItems(theme: component.theme, emptyColor: component.emptyColor, data: component.chartData, selectedKey: self.selectedKey, animated: !transition.animation.isImmediate)
|
||||
|
||||
if let selectedKey = self.selectedKey, let item = component.chartData.items.first(where: { $0.id == selectedKey }) {
|
||||
let tooltip: ComponentView<Empty>
|
||||
|
|
|
|||
|
|
@ -1392,6 +1392,7 @@ final class StorageUsageScreenComponent: Component {
|
|||
component: AnyComponent(PieChartComponent(
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
emptyColor: UIColor(rgb: 0x34C759),
|
||||
chartData: chartData
|
||||
)),
|
||||
environment: {},
|
||||
|
|
@ -1404,7 +1405,6 @@ final class StorageUsageScreenComponent: Component {
|
|||
}
|
||||
|
||||
pieChartTransition.setFrame(view: pieChartComponentView, frame: pieChartFrame)
|
||||
//transition.setAlpha(view: pieChartComponentView, alpha: listCategories.isEmpty ? 0.0 : 1.0)
|
||||
}
|
||||
if let _ = self.aggregatedData, listCategories.isEmpty {
|
||||
let checkColor = UIColor(rgb: 0x34C759)
|
||||
|
|
|
|||
|
|
@ -904,7 +904,9 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||
return .single(nil)
|
||||
}
|
||||
}
|
||||
let wakeupManager = SharedWakeupManager(beginBackgroundTask: { name, expiration in application.beginBackgroundTask(withName: name, expirationHandler: expiration) }, endBackgroundTask: { id in application.endBackgroundTask(id) }, backgroundTimeRemaining: { application.backgroundTimeRemaining }, activeAccounts: sharedContext.activeAccountContexts |> map { ($0.0?.account, $0.1.map { ($0.0, $0.1.account) }) }, liveLocationPolling: liveLocationPolling, watchTasks: watchTasks, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: self.hasActiveAudioSession.get(), notificationManager: notificationManager, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in
|
||||
let wakeupManager = SharedWakeupManager(beginBackgroundTask: { name, expiration in application.beginBackgroundTask(withName: name, expirationHandler: expiration) }, endBackgroundTask: { id in application.endBackgroundTask(id) }, backgroundTimeRemaining: { application.backgroundTimeRemaining }, acquireIdleExtension: {
|
||||
return applicationBindings.pushIdleTimerExtension()
|
||||
}, activeAccounts: sharedContext.activeAccountContexts |> map { ($0.0?.account, $0.1.map { ($0.0, $0.1.account) }) }, liveLocationPolling: liveLocationPolling, watchTasks: watchTasks, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: self.hasActiveAudioSession.get(), notificationManager: notificationManager, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in
|
||||
return sharedContext.accountUserInterfaceInUse(id)
|
||||
})
|
||||
let sharedApplicationContext = SharedApplicationContext(sharedContext: sharedContext, notificationManager: notificationManager, wakeupManager: wakeupManager)
|
||||
|
|
@ -1335,16 +1337,17 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||
})
|
||||
}
|
||||
|
||||
let timestamp = Int(CFAbsoluteTimeGetCurrent())
|
||||
/*let timestamp = Int(CFAbsoluteTimeGetCurrent())
|
||||
let minReindexTimestamp = timestamp - 2 * 24 * 60 * 60
|
||||
if let indexTimestamp = UserDefaults.standard.object(forKey: "TelegramCacheIndexTimestamp") as? NSNumber, indexTimestamp.intValue >= minReindexTimestamp {
|
||||
} else {
|
||||
UserDefaults.standard.set(timestamp as NSNumber, forKey: "TelegramCacheIndexTimestamp")
|
||||
|
||||
Logger.shared.log("App \(self.episodeId)", "Executing low-impact cache reindex in foreground")
|
||||
let _ = self.runCacheReindexTasks(lowImpact: true, completion: {
|
||||
Logger.shared.log("App \(self.episodeId)", "Executing low-impact cache reindex in foreground — done")
|
||||
UserDefaults.standard.set(timestamp as NSNumber, forKey: "TelegramCacheIndexTimestamp")
|
||||
})
|
||||
}
|
||||
}*/
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,8 +79,15 @@ func inputContextPanelForChatPresentationIntefaceState(_ chatPresentationInterfa
|
|||
}
|
||||
|
||||
switch inputQueryResult {
|
||||
case let .stickers(results):
|
||||
if !results.isEmpty {
|
||||
case let .stickers(unfilteredResults):
|
||||
if !unfilteredResults.isEmpty {
|
||||
var results: [FoundStickerItem] = []
|
||||
for result in unfilteredResults {
|
||||
if !results.contains(where: { $0.file.fileId == result.file.fileId }) {
|
||||
results.append(result)
|
||||
}
|
||||
}
|
||||
|
||||
let query = chatPresentationInterfaceState.interfaceState.composeInputState.inputText.string
|
||||
|
||||
if let currentPanel = currentPanel as? InlineReactionSearchPanel {
|
||||
|
|
|
|||
|
|
@ -1122,7 +1122,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||
if file.isAnimated {
|
||||
strongSelf.fetchDisposable.set(fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: file), reference: AnyMediaReference.message(message: MessageReference(message), media: file).resourceReference(file.resource), statsCategory: statsCategoryForFileWithAttributes(file.attributes)).start())
|
||||
} else {
|
||||
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: manual).start())
|
||||
strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: manual, storeToDownloadsPeerType: storeToDownloadsPeerType).start())
|
||||
}
|
||||
}
|
||||
}, cancel: {
|
||||
|
|
|
|||
|
|
@ -263,6 +263,7 @@ class ChatSearchResultsControllerNode: ViewControllerTracingNode, UIScrollViewDe
|
|||
}, present: { _ in
|
||||
}, openForumThread: { _, _ in
|
||||
}, openStorageManagement: {
|
||||
}, openPasswordSetup: {
|
||||
})
|
||||
interaction.searchTextHighightState = searchQuery
|
||||
self.interaction = interaction
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ final class PeerInfoState {
|
|||
final class TelegramGlobalSettings {
|
||||
let suggestPhoneNumberConfirmation: Bool
|
||||
let suggestPasswordConfirmation: Bool
|
||||
let suggestPasswordSetup: Bool
|
||||
let accountsAndPeers: [(AccountContext, EnginePeer, Int32)]
|
||||
let activeSessionsContext: ActiveSessionsContext?
|
||||
let webSessionsContext: WebSessionsContext?
|
||||
|
|
@ -132,6 +133,7 @@ final class TelegramGlobalSettings {
|
|||
init(
|
||||
suggestPhoneNumberConfirmation: Bool,
|
||||
suggestPasswordConfirmation: Bool,
|
||||
suggestPasswordSetup: Bool,
|
||||
accountsAndPeers: [(AccountContext, EnginePeer, Int32)],
|
||||
activeSessionsContext: ActiveSessionsContext?,
|
||||
webSessionsContext: WebSessionsContext?,
|
||||
|
|
@ -151,6 +153,7 @@ final class TelegramGlobalSettings {
|
|||
) {
|
||||
self.suggestPhoneNumberConfirmation = suggestPhoneNumberConfirmation
|
||||
self.suggestPasswordConfirmation = suggestPasswordConfirmation
|
||||
self.suggestPasswordSetup = suggestPasswordSetup
|
||||
self.accountsAndPeers = accountsAndPeers
|
||||
self.activeSessionsContext = activeSessionsContext
|
||||
self.webSessionsContext = webSessionsContext
|
||||
|
|
@ -409,6 +412,23 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
|||
)
|
||||
}
|
||||
|
||||
let hasPassword: Signal<Bool?, NoError> = .single(nil) |> then(
|
||||
context.engine.auth.twoStepVerificationConfiguration()
|
||||
|> map { configuration -> Bool? in
|
||||
var notSet = false
|
||||
switch configuration {
|
||||
case let .notSet(pendingEmail):
|
||||
if pendingEmail == nil {
|
||||
notSet = true
|
||||
}
|
||||
case .set:
|
||||
break
|
||||
}
|
||||
return !notSet
|
||||
}
|
||||
)
|
||||
|> distinctUntilChanged
|
||||
|
||||
return combineLatest(
|
||||
context.account.viewTracker.peerView(peerId, updateData: true),
|
||||
accountsAndPeers,
|
||||
|
|
@ -424,9 +444,10 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
|||
context.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
|
||||
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
|
||||
)
|
||||
),
|
||||
hasPassword
|
||||
)
|
||||
|> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, hasWatchApp, accountPreferences, suggestions, limits -> PeerInfoScreenData in
|
||||
|> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, hasWatchApp, accountPreferences, suggestions, limits, hasPassword -> PeerInfoScreenData in
|
||||
let (notificationExceptions, notificationsAuthorizationStatus, notificationsWarningSuppressed) = notifications
|
||||
let (featuredStickerPacks, archivedStickerPacks) = stickerPacks
|
||||
|
||||
|
|
@ -443,10 +464,16 @@ func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id,
|
|||
enableQRLogin = true
|
||||
}
|
||||
|
||||
var suggestPasswordSetup = false
|
||||
if suggestions.contains(.setupPassword), let hasPassword, !hasPassword {
|
||||
suggestPasswordSetup = true
|
||||
}
|
||||
|
||||
let peer = peerView.peers[peerId]
|
||||
let globalSettings = TelegramGlobalSettings(
|
||||
suggestPhoneNumberConfirmation: suggestions.contains(.validatePhoneNumber),
|
||||
suggestPasswordConfirmation: suggestions.contains(.validatePassword),
|
||||
suggestPasswordSetup: suggestPasswordSetup,
|
||||
accountsAndPeers: accountsAndPeers,
|
||||
activeSessionsContext: accountSessions?.0,
|
||||
webSessionsContext: accountSessions?.2,
|
||||
|
|
|
|||
|
|
@ -454,6 +454,7 @@ private enum PeerInfoSettingsSection {
|
|||
case chatFolders
|
||||
case notificationsAndSounds
|
||||
case privacyAndSecurity
|
||||
case passwordSetup
|
||||
case dataAndStorage
|
||||
case appearance
|
||||
case language
|
||||
|
|
@ -721,6 +722,13 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p
|
|||
items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: presentationData.strings.Settings_TryEnterPassword, action: {
|
||||
interaction.openSettings(.rememberPassword)
|
||||
}))
|
||||
} else if settings.suggestPasswordSetup {
|
||||
//TODO:localize
|
||||
items[.phone]!.append(PeerInfoScreenInfoItem(id: 0, title: "Protect Your Account", text: .markdown("Set a password that will be required each time log in with this phone number."), linkAction: { _ in
|
||||
}))
|
||||
items[.phone]!.append(PeerInfoScreenActionItem(id: 2, text: "Set Additional Password", action: {
|
||||
interaction.openSettings(.passwordSetup)
|
||||
}))
|
||||
}
|
||||
|
||||
if !settings.accountsAndPeers.isEmpty {
|
||||
|
|
@ -7466,6 +7474,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||
}
|
||||
})
|
||||
}
|
||||
case .passwordSetup:
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.6, execute: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = dismissServerProvidedSuggestion(account: self.context.account, suggestion: .setupPassword).start()
|
||||
})
|
||||
|
||||
let controller = self.context.sharedContext.makeSetupTwoFactorAuthController(context: self.context)
|
||||
push(controller)
|
||||
case .dataAndStorage:
|
||||
push(dataAndStorageController(context: self.context))
|
||||
case .appearance:
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@ private final class InternalContext {
|
|||
|
||||
init(sharedContext: SharedAccountContextImpl) {
|
||||
self.sharedContext = sharedContext
|
||||
self.wakeupManager = SharedWakeupManager(beginBackgroundTask: { _, _ in nil }, endBackgroundTask: { _ in }, backgroundTimeRemaining: { 0.0 }, activeAccounts: sharedContext.activeAccountContexts |> map { ($0.0?.account, $0.1.map { ($0.0, $0.1.account) }) }, liveLocationPolling: .single(nil), watchTasks: .single(nil), inForeground: inForeground.get(), hasActiveAudioSession: .single(false), notificationManager: nil, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in
|
||||
self.wakeupManager = SharedWakeupManager(beginBackgroundTask: { _, _ in nil }, endBackgroundTask: { _ in }, backgroundTimeRemaining: { 0.0 }, acquireIdleExtension: {
|
||||
return nil
|
||||
}, activeAccounts: sharedContext.activeAccountContexts |> map { ($0.0?.account, $0.1.map { ($0.0, $0.1.account) }) }, liveLocationPolling: .single(nil), watchTasks: .single(nil), inForeground: inForeground.get(), hasActiveAudioSession: .single(false), notificationManager: nil, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in
|
||||
return sharedContext.accountUserInterfaceInUse(id)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1425,6 +1425,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return SettingsUI.makePrivacyAndSecurityController(context: context)
|
||||
}
|
||||
|
||||
public func makeSetupTwoFactorAuthController(context: AccountContext) -> ViewController {
|
||||
return SettingsUI.makeSetupTwoFactorAuthController(context: context)
|
||||
}
|
||||
|
||||
public func makeStorageManagementController(context: AccountContext) -> ViewController {
|
||||
return StorageUsageScreen(context: context, makeStorageUsageExceptionsScreen: { [weak context] category in
|
||||
guard let context else {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ public final class SharedWakeupManager {
|
|||
private let beginBackgroundTask: (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?
|
||||
private let endBackgroundTask: (UIBackgroundTaskIdentifier) -> Void
|
||||
private let backgroundTimeRemaining: () -> Double
|
||||
private let acquireIdleExtension: () -> Disposable?
|
||||
|
||||
private var inForeground: Bool = false
|
||||
private var hasActiveAudioSession: Bool = false
|
||||
|
|
@ -65,15 +66,17 @@ public final class SharedWakeupManager {
|
|||
private var currentExternalCompletionValidationTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var managedPausedInBackgroundPlayer: Disposable?
|
||||
private var keepIdleDisposable: Disposable?
|
||||
|
||||
private var accountsAndTasks: [(Account, Bool, AccountTasks)] = []
|
||||
|
||||
public init(beginBackgroundTask: @escaping (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?, endBackgroundTask: @escaping (UIBackgroundTaskIdentifier) -> Void, backgroundTimeRemaining: @escaping () -> Double, activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, liveLocationPolling: Signal<AccountRecordId?, NoError>, watchTasks: Signal<AccountRecordId?, NoError>, inForeground: Signal<Bool, NoError>, hasActiveAudioSession: Signal<Bool, NoError>, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal<Bool, NoError>) {
|
||||
public init(beginBackgroundTask: @escaping (String, @escaping () -> Void) -> UIBackgroundTaskIdentifier?, endBackgroundTask: @escaping (UIBackgroundTaskIdentifier) -> Void, backgroundTimeRemaining: @escaping () -> Double, acquireIdleExtension: @escaping () -> Disposable?, activeAccounts: Signal<(primary: Account?, accounts: [(AccountRecordId, Account)]), NoError>, liveLocationPolling: Signal<AccountRecordId?, NoError>, watchTasks: Signal<AccountRecordId?, NoError>, inForeground: Signal<Bool, NoError>, hasActiveAudioSession: Signal<Bool, NoError>, notificationManager: SharedNotificationManager?, mediaManager: MediaManager, callManager: PresentationCallManager?, accountUserInterfaceInUse: @escaping (AccountRecordId) -> Signal<Bool, NoError>) {
|
||||
assert(Queue.mainQueue().isCurrent())
|
||||
|
||||
self.beginBackgroundTask = beginBackgroundTask
|
||||
self.endBackgroundTask = endBackgroundTask
|
||||
self.backgroundTimeRemaining = backgroundTimeRemaining
|
||||
self.acquireIdleExtension = acquireIdleExtension
|
||||
|
||||
self.inForegroundDisposable = (inForeground
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
|
|
@ -186,6 +189,7 @@ public final class SharedWakeupManager {
|
|||
self.hasActiveAudioSessionDisposable?.dispose()
|
||||
self.tasksDisposable?.dispose()
|
||||
self.managedPausedInBackgroundPlayer?.dispose()
|
||||
self.keepIdleDisposable?.dispose()
|
||||
if let (taskId, _, timer) = self.currentTask {
|
||||
timer.invalidate()
|
||||
self.endBackgroundTask(taskId)
|
||||
|
|
@ -265,11 +269,15 @@ public final class SharedWakeupManager {
|
|||
var hasTasksForBackgroundExtension = false
|
||||
|
||||
var hasActiveCalls = false
|
||||
var hasPendingMessages = false
|
||||
for (_, _, tasks) in self.accountsAndTasks {
|
||||
if tasks.activeCalls {
|
||||
hasActiveCalls = true
|
||||
break
|
||||
}
|
||||
if tasks.importantTasks.contains(.pendingMessages) {
|
||||
hasPendingMessages = true
|
||||
}
|
||||
}
|
||||
|
||||
if self.inForeground || self.hasActiveAudioSession || hasActiveCalls {
|
||||
|
|
@ -350,6 +358,17 @@ public final class SharedWakeupManager {
|
|||
}
|
||||
}
|
||||
self.updateAccounts(hasTasks: hasTasksForBackgroundExtension)
|
||||
|
||||
if hasPendingMessages {
|
||||
if self.keepIdleDisposable == nil {
|
||||
self.keepIdleDisposable = self.acquireIdleExtension()
|
||||
}
|
||||
} else {
|
||||
if let keepIdleDisposable = self.keepIdleDisposable {
|
||||
self.keepIdleDisposable = nil
|
||||
keepIdleDisposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAccounts(hasTasks: Bool) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"app": "9.3.1",
|
||||
"app": "9.3.3",
|
||||
"bazel": "5.3.1",
|
||||
"xcode": "14.1"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue